譯者按: JS 騷操作。
本文采用意譯,版權歸原作者所有
我們有多種方法來遍歷 JavaScript 的陣列或者物件,而它們之間的區別非常讓人疑惑。Airbnb 編碼風格禁止使用 for/in 與 for/of,你知道為什麼嗎?
這篇文章將詳細介紹以下 4 種迴圈語法的區別:
for (let i = 0; i < arr.length; ++i)
arr.forEach((v, i) => { /* ... */ })
for (let i in arr)
for (const v of arr)
語法
使用for
和for/in
,我們可以訪問陣列的下標,而不是實際的陣列元素值:
for (let i = 0; i < arr.length; ++i) {
console.log(arr[i]);
}
for (let i in arr) {
console.log(arr[i]);
}
複製程式碼
使用for/of
,則可以直接訪問陣列的元素值:
for (const v of arr) {
console.log(v);
}
複製程式碼
使用forEach()
,則可以同時訪問陣列的下標與元素值:
arr.forEach((v, i) => console.log(v));
複製程式碼
非數字屬性
JavaScript 的陣列就是 Object,這就意味著我們可以給陣列新增字串屬性:
const arr = ["a", "b", "c"];
typeof arr; // 'object'
arr.test = "bad"; // 新增非數字屬性
arr.test; // 'abc'
arr[1] === arr["1"]; // true, JavaScript陣列只是特殊的Object
複製程式碼
4 種迴圈語法,只有for/in
不會忽略非數字屬性:
const arr = ["a", "b", "c"];
arr.test = "bad";
for (let i in arr) {
console.log(arr[i]); // 列印"a, b, c, bad"
}
複製程式碼
正因為如此,使用for/in
遍歷陣列並不好。
其他 3 種迴圈語法,都會忽略非數字屬性:
const arr = ["a", "b", "c"];
arr.test = "abc";
// 列印 "a, b, c"
for (let i = 0; i < arr.length; ++i) {
console.log(arr[i]);
}
// 列印 "a, b, c"
arr.forEach((el, i) => console.log(i, el));
// 列印 "a, b, c"
for (const el of arr) {
console.log(el);
}
複製程式碼
要點: 避免使用for/in
來遍歷陣列,除非你真的要想要遍歷非數字屬性。可以使用 ESLint 的guard-for-in規則來禁止使用for/in
。
陣列的空元素
JavaScript 陣列可以有空元素。以下程式碼語法是正確的,且陣列長度為 3:
const arr = ["a", , "c"];
arr.length; // 3
複製程式碼
讓人更加不解的一點是,迴圈語句處理['a',, 'c']
與['a', undefined, 'c']
的方式並不相同。
對於['a',, 'c']
,for/in
與forEach
會跳過空元素,而for
與for/of
則不會跳過。
// 列印"a, undefined, c"
for (let i = 0; i < arr.length; ++i) {
console.log(arr[i]);
}
// 列印"a, c"
arr.forEach(v => console.log(v));
// 列印"a, c"
for (let i in arr) {
console.log(arr[i]);
}
// 列印"a, undefined, c"
for (const v of arr) {
console.log(v);
}
複製程式碼
對於['a', undefined, 'c']
,4 種迴圈語法一致,列印的都是"a, undefined, c"。
還有一種新增空元素的方式:
// 等價於`['a', 'b', 'c',, 'e']`
const arr = ["a", "b", "c"];
arr[5] = "e";
複製程式碼
還有一點,JSON 也不支援空元素:
JSON.parse('{"arr":["a","b","c"]}');
// { arr: [ 'a', 'b', 'c' ] }
JSON.parse('{"arr":["a",null,"c"]}');
// { arr: [ 'a', null, 'c' ] }
JSON.parse('{"arr":["a",,"c"]}');
// SyntaxError: Unexpected token , in JSON at position 12
複製程式碼
要點: for/in
與forEach
會跳過空元素,陣列中的空元素被稱為"holes"。如果你想避免這個問題,可以考慮禁用forEach
:
parserOptions:
ecmaVersion: 2018
rules:
no-restricted-syntax:
- error
- selector: CallExpression[callee.property.name="forEach"]
message: Do not use `forEach()`, use `for/of` instead
複製程式碼
函式的 this
for
,for/in
與for/of
會保留外部作用域的this
。
對於forEach
, 除非使用箭頭函式,它的回撥函式的 this 將會變化。
使用 Node v11.8.0 測試下面的程式碼,結果如下:
"use strict";
const arr = ["a"];
arr.forEach(function() {
console.log(this); // 列印undefined
});
arr.forEach(() => {
console.log(this); // 列印{}
});
複製程式碼
要點: 使用 ESLint 的no-arrow-callback
規則要求所有回撥函式必須使用箭頭函式。
Async/Await 與 Generators
還有一點,forEach()
不能與 Async/Await 及 Generators 很好的"合作"。
不能在forEach
回撥函式中使用 await:
async function run() {
const arr = ['a', 'b', 'c'];
arr.forEach(el => {
// SyntaxError
await new Promise(resolve => setTimeout(resolve, 1000));
console.log(el);
});
}
複製程式碼
不能在forEach
回撥函式中使用 yield:
function* run() {
const arr = ['a', 'b', 'c'];
arr.forEach(el => {
// SyntaxError
yield new Promise(resolve => setTimeout(resolve, 1000));
console.log(el);
});
}
複製程式碼
對於for/of
來說,則沒有這個問題:
async function asyncFn() {
const arr = ["a", "b", "c"];
for (const el of arr) {
await new Promise(resolve => setTimeout(resolve, 1000));
console.log(el);
}
}
function* generatorFn() {
const arr = ["a", "b", "c"];
for (const el of arr) {
yield new Promise(resolve => setTimeout(resolve, 1000));
console.log(el);
}
}
複製程式碼
當然,你如果將forEach()
的回撥函式定義為 async 函式就不會報錯了,但是,如果你想讓forEach
按照順序執行,則會比較頭疼。
下面的程式碼會按照從大到小列印 0-9:
async function print(n) {
// 列印0之前等待1秒,列印1之前等待0.9秒
await new Promise(resolve => setTimeout(() => resolve(), 1000 - n * 100));
console.log(n);
}
async function test() {
[0, 1, 2, 3, 4, 5, 6, 7, 8, 9].forEach(print);
}
test();
複製程式碼
要點: 儘量不要在forEach
中使用 aysnc/await 以及 generators。
結論
簡單地說,for/of
是遍歷陣列最可靠的方式,它比for
迴圈簡潔,並且沒有for/in
和forEach()
那麼多奇怪的特例。for/of
的缺點是我們取索引值不方便,而且不能這樣鏈式呼叫forEach()
. forEach()
。
使用for/of
獲取陣列索引,可以這樣寫:
for (const [i, v] of arr.entries()) {
console.log(i, v);
}
複製程式碼
參考
- For-each over an array in JavaScript?
- Why is using “for…in” with array iteration a bad idea?
- Array iteration and holes in JavaScript
關於Fundebug
Fundebug專注於JavaScript、微信小程式、微信小遊戲、支付寶小程式、React Native、Node.js和Java線上應用實時BUG監控。 自從2016年雙十一正式上線,Fundebug累計處理了10億+錯誤事件,付費客戶有Google、360、金山軟體、百姓網等眾多品牌企業。歡迎大家免費試用!
版權宣告
轉載時請註明作者Fundebug以及本文地址: blog.fundebug.com/2019/03/11/…