你所不知道該如何回答的面試題(一)
JS分為哪兩大型別?都有什麼各自的特點?你該如何判斷正確的型別?
發散思維,舉一反三的回答問題,將很多碎片化的知識點進行串聯。
JS分為基本資料型別和引用資料型別。
基本資料型別:String、Number、Boolean、Null、Undefined、Bigint、Symbol
引用資料型別:Object
其中 Number
型別 是浮點型別,在使用中會遇到某些bug,比如 0.1 + 0.2 != 0.3。這是由於計算機採用二進位制語言,0.1 和 0.2 在由十進位制轉為二進位制時是無限迴圈的小數,計算機的記憶體是有限的,會在某個精度點直接捨棄,導致計算機還沒開始計算,一個很小的舍入錯誤就產生了。
對於 Null
型別,雖然 typeof null
會輸入 object
,但是這只是JS存在的一個悠久的bug,最初版本的 JS
使用的是32位系統,為了效能考慮使用低位儲存變數的型別資訊,000
開頭的是物件型別,然而 null
表示為全零,所以將它錯誤的判斷為 object
。
各自的特點:原始型別儲存的是值,存在於棧中;物件型別儲存的是指標,存在於堆中(因為儲存在棧記憶體的必須是大小固定的資料)。並且針對原始型別和物件型別,複製拷貝也是不一樣的。原始型別的複製是深拷貝,而物件型別的複製,根據複製層級分淺拷貝和深拷貝,這是因為對於物件型別,棧中只存放地址(指標),複製的時候,其實是複製地址,而非具體的內容。
淺拷貝的方式:
- ES6 的 Object.assign()
- ES7 的...解構運算子
- 只拷貝一層
深拷貝的方式:
-
JSON.parse(JSON.stringify(src))
-
採用遞迴呼叫
-
藉助第三方庫
lodash 的cloneDeep(src)
jq 的extend(true, result, src1, src2[ ,src3])
深淺拷貝的實現原理 見 原型模式 筆記
物件一般存放在堆空間中,因為堆空間很大,能存很多大的資料,但缺點是分配記憶體和回收記憶體會佔用一定的時間。所以針對棧和堆的回收是不一樣的。
呼叫棧中的資料回收機制是:當一個函式執行結束之後,JavaScript引擎會通過向下移動ESP來銷燬該函式儲存在棧中的執行上下文。
堆中的資料回收機制是建立在代際假說的基礎之上。
代際假說有兩個特點:
- 不死的物件會活的更久
- 大部分物件在記憶體中存在的時間很短,簡單來說就是很多物件一經分配記憶體,很快就變得不可訪問。
因此V8中會把堆分為新生代和老生代兩個區域,新生代中存放生存時間短的物件(1 ~ 8M),老生代中存放生存時間久的物件,採用兩種演算法進行回收。
新生代中使用Scavenge演算法
。該演算法是把新生代空間對半劃分為兩個區域,一半是物件區域,一半是空閒區域。然後進行迴圈複製(複製的過程中相當於完成了記憶體整理)和翻轉。並且採取了物件晉升策略,凡是經過兩次垃圾回收依然還存活的物件就會被移動到老生區中。
主垃圾回收器回收老生區中的程式碼,由於老生區中物件佔用空間大並且物件存活時間長。所以採用標記-清除
的演算法進行回收。
針對型別的判斷,可以通過 typeof
和 instanceof
來進行判斷。
其中 typeof
的缺點是針對Object、Array和Null
型別不能區分,他們都返回 Object
而 instanceof
雖然能夠區分Array、Object和Function
,適合用於判斷自定義的類例項物件,但沒法判斷 Number,Boolean,String
等基本型別。
因此需要視具體情況來使用。
不過Object.prototype.toString.call()
可以精準判斷資料的各種型別。但寫法比較繁瑣