比较for/foreach/for in/for of


title: 比较for/foreach/for in/for of
date: 2019-03-12 08:57:46
tags: js


alesia-kazantceva-283285-unsplash (1).jpg

本文翻译自 编辑:code_barbarian For vs forEach() vs for/in vs for/of in JavaScript

在JS中有许多方式去循环数组和对象。但在实际使用中容易产生使用上的混淆,这需要大家去权衡。在某些方式上甚至禁止某些循环结构。(详情请看),本文我将重点描述四个循环结构迭代的方式:

  • for (let i = 0; i < arr.length; ++i)
  • arr.forEach((v, i) => { /* ... */ })
  • for (let i in arr)
  • for (const v of arr)

我将使用几种不同的边缘情况概述这些循环结构之间的区别。我还将链接到相关的ESLint规则,您可以使用这些规则来强制在项目中实行从而达到循环的最佳实践。

语法概述

在for与for/in循环结构给你访问索引数组中,而不是实际的值。例如,假设您要打印出以下数组中存储的值:

const arr = ['a', 'b', 'c'];

使用for 和 for/in ,如果需要打印出值,则需要arr[i],例如:

for (let i = 0; i < arr.length; ++i) {
  console.log(arr[i]);
}

for (let i in arr) {
  console.log(arr[i]);
}

与其他两个结构相比,forEach()并且for/of,您可以访问数组元素本身。有了forEach()你可以访问数组索引i,for/of不能。

arr.forEach((v, i) => console.log(v));

for (const v of arr) {
  console.log(v);
}

非数值属性

js中数组是一种特殊的对象,这就意味着你可以添加string类型的属性值在你的数组里,不仅仅是数字。

const arr = ['a', 'b', 'c'];

typeof arr; // 'object'

// Assign to a non-numeric property
arr.test = 'bad';

4种循环结构有3种忽视非值属性,但是,for/in 却会打印出“bad”

function ab(){
    const arr = ['a','b','c'];
    arr.test = 'bad';

    
    for(let i in arr){
        console.log(arr[i]);
    }

    // for(let j of arr){
    //  console.log(j);
    // }

    // for(let i=0;i<arr.length;i++){
    //  console.log(arr[i]);
    // }

    // arr.forEach((v,i)=>{
    //  console.log(v);
    // })

}
ab();

打印的结果只有for/in 会展示“bad”的值

显而易见,这也是为什么在循环的时候,通常大家不会去选择for/in的原因,因为其他的循环方式都可以正确的忽视非数值的值 。

摘要:除非你要确定要迭代非数字键和继承键,否则请避免使用for/in

空元素
js数组允许空元素,在语法中这是合法的,并且下面的例子长度是3.

const arr = ['a',,'c'];

arr.length; //3

令人困惑的是,循环结构的处理['a','c'] 也有不同的['a',undefined,'c']。下面是4个循环结构如何处理的呢。for/in 、forEach会跳过空元素,但是for,for/of并不会。

function ab(){
    const arr = ['a',,'c'];

    for(let i in arr){
        console.log(arr[i]);
    }

    arr.forEach((v,i)=>{
        console.log(v);
    })

    for(let i of arr){
        console.log(i);
    }

    for(let i=0;i<arr.length;i++){
        console.log(arr[i]);
    }
}
ab();

此外还有另一种方式将空元素添加到数组中:

const arr = ['a', 'b', 'c'];
arr[5] = 'e';  //`['a', 'b', 'c',, 'e']`

forEach()并且for/in跳过在阵列中的空元素,for并且for/of没有。这种forEach()行为可能会导致问题,但JavaScript数组中的漏洞通常很少见,因为它们在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

函数上下文

函数上下文是js中很其他的一种方式this。for for/in for/of都可以保持自身之外的this的值,但是forEach()却有些不同,除非你使用剪头函数。

'use strict'; //严格模式下

const arr = ['a'];

// Prints "undefined"
arr.forEach(function() {
  console.log(this);
});

Async/Await and Generators

另外一个边缘情况对于forEach()就是它不会很好的工作,当与async/await一起使用的时候,如果说你的forEach()的回调是异步的这没关系,但是你不能在await里使用forEach();

async function run() {
  const arr = ['a', 'b', 'c'];
  arr.forEach(el => {
    // SyntaxError
    await new Promise(resolve => setTimeout(resolve, 1000));
    console.log(el);
  });
}

同时你也不可以使用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,你也在异步中遇到很大的麻烦,下面这个列子将相反的顺序打印0-9

async function print(n) {
  // Wait 1 second before printing 0, 0.9 seconds before printing 1, etc.
  await new Promise(resolve => setTimeout(() => resolve(), 1000 - n * 100));
  // Will usually print 9, 8, 7, 6, 5, 4, 3, 2, 1, 0 but order is not strictly
  // guaranteed.
  console.log(n);
}

async function test() {
  [0, 1, 2, 3, 4, 5, 6, 7, 8, 9].forEach(print);
}

test();
如果你正在使用async/await或者generator,请记住这forEach()是语法糖,你应该谨慎使用使用。

结论

通常,for/of是JavaScript中迭代数组的最强大的方法。它比传统的更简洁的for循环,并没有许多边缘事例 for/in和forEach()。主要的缺点fro/of是你需要做额外的工作来访问索引(1),forEach()有几个边缘案例,应该谨慎使用,但是很多情况下,它使代码更简洁。

tips: 要在for/of循环中访问当前数组索引,可以使用Array#entries()函数。

for (const [i, v] of arr.entries()) {
  console.log(i, v); // Prints "0 a", "1 b", "2 c"
}

推荐阅读更多精彩内容

  • 由于贵人王琼的极力及三番两次推荐,王阳明的再次使用心学威力的机会来了——南赣剿匪。 1517年,王阳明到江西赣州剿...
    娟姐的心语话廊阅读 485评论 1赞 1
  • 爱,究竟是什么?它能让你哭、让你笑、让你痴、让你欲罢不能。多少年轻男女,一旦遭遇爱情,便犹如雾里看花、水中望月,只...
    好想和你说TellingYou阅读 191评论 0赞 0
  • 好久没有给你写信了,这期间发生了蛮多的事。你终于把婚礼办了,我的生活里也发生了些不大不小不明晰的变化。 ...
    蜗瓦阅读 16评论 0赞 0
  • 栖居于此时 真觉得山穷水尽了 小民生涯 平素唯知米价分厘间涨跌 一把二手自行车,漫寨游转 偷菜摸香蕉之在下啊 逃逛...
    昆南阅读 148评论 0赞 4