比较for/foreach/for in/for of

作者: Kris_lee | 来源:发表于2019-03-12 09:28 被阅读2次

    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"
    }
    

    相关文章

      网友评论

        本文标题:比较for/foreach/for in/for of

        本文链接:https://www.haomeiwen.com/subject/dezwpqtx.html