JavaScript 的 4 種數(shù)組遍歷方法
for
for (let i = 0; i < arr.length; ++i)
forEach()
arr.forEach((v, i) => { /* ... */ })
for/in
for (let i in arr)
for/of
for (const v of arr)
語法
使用for和for/in少态,我們可以訪問數(shù)組的下標(biāo),而不是實(shí)際的數(shù)組元素值:
for (let i = 0; i < arr.length; ++i) {
console.log(arr[i]);
}
for (let i in arr) {
console.log(arr[i]);
}
//使用for/of易遣,則可以直接訪問數(shù)組的元素值:
for (const v of arr) {
console.log(v);
}
//使用forEach()彼妻,則可以同時(shí)訪問數(shù)組的下標(biāo)與元素值:
arr.forEach((v, i) => console.log(v));
非數(shù)字屬性
JavaScript 的數(shù)組就是 Object,這就意味著我們可以給數(shù)組添加字符串屬性:
const arr = ["a", "b", "c"];
typeof arr; // 'object'
arr.test = "bad"; // 添加非數(shù)字屬性
arr.test; // 'abc'
arr[1] === arr["1"]; // true, JavaScript數(shù)組只是特殊的Object
4 種循環(huán)語法豆茫,只有for/in不會忽略非數(shù)字屬性:
const arr = ["a", "b", "c"];
arr.test = "bad";
for (let i in arr) {
console.log(arr[i]); // 打印"a, b, c, bad"
}
正因?yàn)槿绱饲惹福褂胒or/in遍歷數(shù)組并不好。
要點(diǎn): 避免使用for/in來遍歷數(shù)組揩魂,除非你真的要想要遍歷非數(shù)字屬性幽邓。可以使用 ESLint 的guard-for-in規(guī)則來禁止使用for/in火脉。
數(shù)組的空元素
JavaScript 數(shù)組可以有空元素牵舵。以下代碼語法是正確的柒啤,且數(shù)組長度為 3:
const arr = ["a", , "c"];
arr.length; // 3
讓人更加不解的一點(diǎn)是,循環(huán)語句處理['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 種循環(huán)語法一致,打印的都是"a, undefined, c"送火。
還有一種添加空元素的方式:
// 等價(jià)于`['a', 'b', 'c',, 'e']`
const arr = ["a", "b", "c"];
arr[5] = "e";
還有一點(diǎn)拳话,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
要點(diǎn): for/in與forEach會跳過空元素,數(shù)組中的空元素被稱為"holes"漾脂。如果你想避免這個(gè)問題假颇,可以考慮禁用forEach:
parserOptions:
ecmaVersion: 2018
rules:
no-restricted-syntax:
- error
- selector: CallExpression[callee.property.name="forEach"]
message: Do not use `forEach()`, use `for/of` instead
函數(shù)的 this
for,for/in與for/of會保留外部作用域的this骨稿。
對于forEach笨鸡, 除非使用箭頭函數(shù),它的回調(diào)函數(shù)的 this 將會變化坦冠。
使用 Node v11.8.0 測試下面的代碼形耗,結(jié)果如下:
"use strict";
const arr = ["a"];
arr.forEach(function() {
console.log(this); // 打印undefined
});
arr.forEach(() => {
console.log(this); // 打印{}
});
要點(diǎn): 使用 ESLint 的no-arrow-callback規(guī)則要求所有回調(diào)函數(shù)必須使用箭頭函數(shù)。
Async/Await 與 Generators
還有一點(diǎn)辙浑,forEach()不能與 Async/Await 及 Generators 很好的"合作"激涤。
不能在forEach回調(diào)函數(shù)中使用 await:
async function run() {
const arr = ['a', 'b', 'c'];
arr.forEach(el => {
// SyntaxError
await new Promise(resolve => setTimeout(resolve, 1000));
console.log(el);
});
}
不能在forEach回調(diào)函數(shù)中使用 yield:
function run() {
const arr = ['a', 'b', 'c'];
arr.forEach(el => {
// SyntaxError
yield new Promise(resolve => setTimeout(resolve, 1000));
console.log(el);
});
}
對于for/of來說,則沒有這個(gè)問題:
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);
}
}
當(dāng)然判呕,你如果將forEach()的回調(diào)函數(shù)定義為 async 函數(shù)就不會報(bào)錯(cuò)了倦踢,但是,如果你想讓forEach按照順序執(zhí)行侠草,則會比較頭疼辱挥。
下面的代碼會按照從大到小打印 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();
要點(diǎn): 盡量不要在forEach中使用 aysnc/await 以及 generators边涕。
結(jié)論
簡單地說晤碘,for/of是遍歷數(shù)組最可靠的方式,它比for循環(huán)簡潔功蜓,并且沒有for/in和forEach()那么多奇怪的特例园爷。for/of的缺點(diǎn)是我們?nèi)∷饕挡环奖悖也荒苓@樣鏈?zhǔn)秸{(diào)用forEach(). forEach()式撼。
使用for/of獲取數(shù)組索引童社,可以這樣寫:
for (const [i, v] of arr.entries()) {
console.log(i, v);
}