給初學者:JavaScript 中陣列操作注意點

發表於2017-12-27

不要用 for_in 遍歷陣列


這是 JavaScript 初學者常見的誤區。for_in 用於遍歷物件中包括原型鏈上的所有可列舉的(enumerable)的 key,本來不是為遍歷陣列而存在。

使用 for_in 遍歷陣列有三點問題:

遍歷順序不固定

JavaScript 引擎不保證物件的遍歷順序。當把陣列作為普通物件遍歷時同樣不保證遍歷出的索引順序。

會遍歷出物件原型鏈上的值。

如果你改變了陣列的原型物件(比如 polyfill)而沒有將其設為 enumerable: false,for_in 會把這些東西遍歷出來。

執行效率低下。

儘管理論上 JavaScript 使用物件的形式儲存陣列,JavaScript 引擎還是會對陣列這一非常常用的內建物件特別優化。 https://jsperf.com/for-in-vs-…
可以看到使用 for_in 遍歷陣列要比使用下標遍歷陣列慢 50 倍以上

PS:你可能是想找 for_of

不要用 JSON.parse(JSON.stringify()) 深拷貝陣列


有人使用 JSON 中深拷貝物件或陣列。這雖然在多數情況是個簡單方便的手段,但也可能引發未知 bug,因為:

會使某些特定值轉換為 null

NaN, undefined, Infinity 對於 JSON 中不支援的這些值,會在序列化 JSON 時被轉換為 null,反序列化回來後自然也就是 null

會丟失值為 undefined 的鍵值對

JSON 序列化時會忽略值為 undefined 的 key,反序列化回來後自然也就丟失了

會將 Date 物件轉換為字串

JSON 不支援物件型別,對於 JS 中 Date 物件的處理方式為轉換為 ISO8601 格式的字串。然而反序列化並不會把時間格式的字串轉化為 Date 物件

執行效率低下。

作為原生函式,JSON.stringify 和 JSON.parse 自身操作 JSON 字串的速度是很快的。然而為了深拷貝陣列把物件序列化成 JSON 再反序列化回來完全沒有必要。

我花了一些時間寫了一個簡單的深拷貝陣列或物件的函式,測試發現執行速度差不多是使用 JSON 中轉的 6 倍左右,順便還支援了 TypedArray、RegExp 的物件的複製

https://jsperf.com/deep-clone…

不要用 arr.find 代替 arr.some


Array.prototype.find 是 ES2015 中新增的陣列查詢函式,與 Array.prototype.some 有相似之處,但不能替代後者。

Array.prototype.find 返回第一個符合條件的值,直接拿這個值做 if 判斷是否存在,如果這個符合條件的值恰好是 0 怎麼辦?

arr.find 是找到陣列中的值後對其進一步處理,一般用於物件陣列的情況;arr.some 才是檢查存在性;兩者不可混用。

不要用 arr.map 代替 arr.forEach


也是一個 JavaScript 初學者常常犯的錯誤,他們往往並沒有分清 Array.prototype.map 和 Array.prototype.forEach 的實際含義。

map 中文叫做 對映,它通過將某個序列依次執行某個函式匯出另一個新的序列。這個函式通常是不含副作用的,更不會修改原始的陣列(所謂純函式)。

forEach 就沒有那麼多說法,它就是簡單的把陣列中所有項都用某個函式處理一遍。由於 forEach 沒有返回值(返回 undefined),所以它的回撥函式通常是包含副作用的,否則這個 forEach 寫了毫無意義。

確實 map 比 forEach 更加強大,但是 map 會建立一個新的陣列,佔用記憶體。如果你不用 map 的返回值,那你就應當使用 forEach

補:forEach 與 break


ES6 以前,遍歷陣列主要就是兩種方法:手寫迴圈用下標迭代,使用 Array.prototype.forEach。前者萬能,效率最高,可就是寫起來比較繁瑣——它不能直接獲取到陣列中的值。

筆者個人是喜歡後者的:可以直接獲取到迭代的下標和值,而且函式式風格(注意 FP 注重的是不可變資料結構,forEach 天生為副作用存在,所以只有 FP 的形而沒有神)寫起來爽快無比。但是!不知各位同學注意過沒有:forEach 一旦開始就停不下來了。。。

forEach 接受一個回撥函式,你可以提前 return,相當於手寫迴圈中的 continue。但是你不能 break——因為回撥函式中沒有迴圈讓你去 break:

解決方案還是有的。其他函數語言程式設計語言例如 scala 就遇到了類似問題,它提供了一個函式break,作用是丟擲一個異常。bV0sRJ

我們可以仿照這樣的做法,來實現 arr.forEach 的 break:

噁心的一B對不對。還有其他方法,比如用 Array.prototype.some 代替 Array.prototype.forEach。

考慮 Array.prototype.some 的特性,當 some 找到一個符合條件的值(回撥函式返回 true)時會立即終止迴圈,利用這樣的特性可以模擬 break:

some 的返回值被忽略掉了,它已經脫離了判斷陣列中是否有元素符合給出的條件這一原始的含義。

在 ES6 前,筆者主要使用該法(其實因為 Babel 程式碼膨脹的緣故,現在也偶爾使用),ES6 不一樣了,我們有了 for…of。for…of 是真正的迴圈,可以 break:

但是有個問題,for…of 似乎拿不到迴圈的下標。其實 JavaScript 語言制定者想到了這個問題,可以如下解決:

Array.prototype.entries

for…of 和 forEach 的效能測試:https://jsperf.com/array-fore… Chrome 中 for…of 要快一些哦?
如果有更多建議歡迎留言指出

相關文章