zoukankan      html  css  js  c++  java
  • JavaScript 的 4 种数组遍历方法: for VS forEach() VS for/in VS for/of

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

    参考

    关于Fundebug

    Fundebug专注于JS、微信小程序、微信小游戏、支付宝小程序、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/

  • 相关阅读:
    nodejs处理get请求
    ubuntu中chromium无法播放flash,安装flash
    JQuery日历控件
    深度理解Jquery 中 offset() 方法
    javascrip中parentNode和offsetParent之间的区别
    JQuery判断元素是否存在
    javascript日历控件——纯javascript版
    canvas作为背景
    background-position控制背景位置
    javascript日历控件
  • 原文地址:https://www.cnblogs.com/fundebug/p/4-ways-to-loop-array-inj-javascript.html
Copyright © 2011-2022 走看看