资讯专栏INFORMATION COLUMN

JavaScript 的 4 种数组遍历方法: for VS forEach() VS for/in

joyqi / 792人阅读

摘要:对于,除非使用箭头函数,它的回调函数的将会变化。使用测试下面的代码,结果如下打印打印要点使用的规则要求所有回调函数必须使用箭头函数。

译者按: JS 骚操作。

原文:For vs forEach() vs for/in vs for/of in JavaScript

译者: Fundebug

本文采用意译,版权归原作者所有

我们有多种方法来遍历 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)

语法

使用forfor/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/inforEach会跳过空元素,而forfor/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/inforEach会跳过空元素,数组中的空元素被称为"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

forfor/infor/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/inforEach()那么多奇怪的特例。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以及本文地址:
https://blog.fundebug.com/2019/03/11/4-ways-to-loop-array-inj-javascript/

文章版权归作者所有,未经允许请勿转载,若此文章存在违规行为,您可以联系管理员删除。

转载请注明本文地址:https://www.ucloud.cn/yun/109102.html

相关文章

  • for...in VS. for...of

    摘要:在种和一样都具有遍历数组的功能,但是他们的返回结果并不相同,使用遍历数组时,无法使用中断循环,也无法使用中断,当然使用也不能返回到外层函数。枚举对象循环不支持普通对象,如果想迭代一个对象的属性,可以使用内建的方法使用方法获取对象的数组 先看下面这两种写法:1. tabList.forEach((tab, i) => { if (tab.is_main) { th...

    Anchorer 评论0 收藏0
  • 一文彻底弄懂 for forEach for-in for-of 区别

    基本语法 下面列出了这几个遍历语法规则: for (let index = 0; index < array.length; index++) { const element = array[index] // ... } array.forEach(element => { // ... }) for (const key in array) { // ......

    newsning 评论0 收藏0
  • js--遍历数组和对象方法及其效率--for-forof--foreach--forin

    摘要:数组优化版优化版谷歌版本正式版本位优化版火狐版本优化版结论这是一个尴尬的结论,谷歌火狐,同样是最新版本或许谷歌才是王道,火狐跳舞吧,颤抖吧,你也太慢了吧,以谷歌为准相对来说作为新的遍历方法具有可观的效率对象 数组 var arr = new Array(10000000); var arr1 = []; var arr2 = []; var arr3 =...

    ThinkSNS 评论0 收藏0
  • .map() vs .forEach() vs for() 如何选择?

    摘要:但是总是会被很多朋友说,这些人认为的速度要比快一点。如果你想用函数式编程写个链式表达式来装个逼,将会是你不二的选择。来看下面这个例子链式风格接不起来,断了最后根据上面的代码,大家应该了解到跟的局限。 访问原文地址 .map() vs .forEach() vs for() 笔者说,自己基本没怎么用过for()来遍历,主要是用.forEach()。 但是总是会被很多朋友说,这些人认为fo...

    iliyaku 评论0 收藏0

发表评论

0条评论

最新活动
阅读需要支付1元查看
<