Dreamer Dreamer
首页
  • 分类
  • 标签
  • 归档
关于
GitHub (opens new window)

lycpan233

白日梦想家
首页
  • 分类
  • 标签
  • 归档
关于
GitHub (opens new window)
  • Vue

  • JavaScript

    • JavaScript部分特殊值判断记录
    • JavaScript各遍历方法对比
      • 防抖函数 Debounce
      • JavaScript 手撕深拷贝
      • JavaScript位运算应用场景
      • 浅谈Cookie和Session的区别
      • js 快速创建二维数组并初始化
      • js 获取变量准确类型
    • 前端
    • JavaScript
    lycpan233
    2023-12-28
    目录

    JavaScript各遍历方法对比

    # 各遍历方法简要对比

    支持的数据类型 break、continue return throw async、await 特点
    forI Array 支持 支持 支持 支持 最基础的循环,语法繁琐。三个条件,均可省略。
    forIn Array,Object 支持 支持 支持 支持 主要用于对象的遍历,获取其key键,但获取的key键均为String类型。不建议对数组使用,特定情况下循环会出错。
    forOf Array,Set,Map,String,引用类型等(更多类型可以通过iterator定义) 支持 支持 支持 支持 for ... of 修复了for ... in 对数组操作的若干问题。其主要遍历对象的值,但在对数组遍历时可以借助ES6数组提供的方法实现key、value的遍历。for ... of 可以通过实现Iterator接口对任意数据类型进行遍历。
    forEach Array,Set,Map,引用类型 不支持 可以使用,仅能跳出当前循环,与continue效果相似。 支持 不支持 在循环中break、continue俩个关键字都无效,但是可以通过return结束当轮循环,通过throw退出循环体。forEach 不会直接改变调用它的对象,但是那个对象可能会被 callback 函数改变。在使用promise 或 async 作为callback函数时会出现执行顺序的问题。

    # for ... in

    该方法常用于取对象的key键,但在对数组使用时会暴露出几个缺点。

    # 对数组操作的若干问题

    数组的key键都是Number,但for ... in 取出的均为String

    const arr = [ 5, 4, 3, 2, 1 ];
    
    for (const i in arr) {
      console.log(i); // '0', '1', '2', '3', '4'
    }
    
    1
    2
    3
    4
    5

    循环不仅遍历数字键名,还会遍历手动添加的其他键,甚至包括原型链上的键。 导致遍历的次数与arr.length得到的长度两者不符。

    const arr = [ 5, 4, 3, 2, 1 ];
    
    arr.name = '男';
    for (const i in arr) {
      console.log(i); // '0', '1', '2', '3', '4', 'name'
    }                 // 遍历6次
    console.log(arr.length); // 5
    
    1
    2
    3
    4
    5
    6
    7

    # for ... of

    该方法可以遍历大多常用数据类型,其中Object迭代时需要进行处理,一些复杂类型也可以通过自定义钩子进行定制迭代方式。同时其在for ... in 的基础上完善了对数组的操作。

    # 修复for ... in 操作数组的缺点

    let arr = [3, 5, 7];
    arr.foo = 'hello';
    
    for (let i in arr) {
      console.log(i); // "0", "1", "2", "foo"
    }
    
    for (let i of arr) {
      console.log(i); //  "3", "5", "7"
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    # 遍历中可修改原数组的值

    定义循环变量时用const不可修改原对象,通过let定义即可。

    let iterable = [10, 20, 30];
    
    for (const value of iterable) { 
      console.log(value); // 10 ,20 ,30 
    }
    
    for (let value of iterable) {
        value += 1;
        console.log(value); // 11 ,21 ,31 
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10

    # 在遍历数组时获取数组的key键和value值

    可以借助ES6数组示例的entries(),keys()和values()方法,实现遍历数组的key和value。

    const iterable = [ 10, 20, 30, 'A', 'B', 'C' ];
    
    for (const index of iterable.keys()) {
      console.log(index); // 0, 1, 2, 3, 4, 5
    }
    
    for (const elem of iterable.values()) {
      console.log(elem); // 10, 20, 30, 'A', 'B', 'C'
    }
    
    
    for (const [ index, elem ] of iterable.entries()) {
      console.log(index, elem);// 0 10, 1 20, 2 30, 3 'A', 4 'B', 5 'C'
    }
    
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15

    # Object对象的遍历和Iterator的实现

    该方法默认无法遍历Object对象,因为其不确定遍历属性应该采用哪种顺序,故而需要进行自定义。

    这块笔者理解不透彻,感兴趣的小伙伴可以移步该连接:Iterator和for ... of (opens new window)


    # forEach

    该方法适用于Array、Set、Map、引用类型的遍历,但不支持break,continue和return方法,以及该改变数组值等操作。

    # break、continue、returen、throw 使用示例

    在循环中无法通过break,continue结束当前循环,但是可以通过return结束当轮循环,通过throw退出循环体。

    const arr = [ 1, 2, 3, 4, 5 ];
    
    arr.forEach(function(item) {
      if (item === 3) {
        break; // Illegal break statement
      }
      console.log(item);
    });
    
    arr.forEach(function(item) {
      if (item === 3) {
        continue;; // Illegal continue statement: no surrounding iteration statement
      }
      console.log(item);
    });
    
    arr.forEach(function(item) {
      if (item === 3) {
        return;
      }
      console.log(item); // 1 , 2 , 4 , 5
    });
    
    try {
      arr.forEach(function(item) {
        if (item === 3) {
          throw new Error('EndIterative');
        }
        console.log(item);  // 1 , 2 
      });
    } catch (error) {
      if (error.message !== 'EndIterative') throw error;
    }
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    31
    32
    33

    # 修改迭代对象的方法

    forEach被调用时,不会改变原数组,也就是调用它的数组(尽管 callback 函数在被调用时可能会改变原数组)。即,forEach 不会直接改变调用它的对象,但是那个对象可能会被 callback 函数改变。

    实际调用过程中发现,forEach迭代是对原对象进行复制,在其副本上进行操作。 如果是基本数据类型在callback不能对其进行改变,如果是引用类型,只能改变部分属性。 但不论那种数据类型,都可以通过forEach的第三个参数对原对象进行修改。

    对基本类型的数组进行操作,无法改变其值。

    const arr = [ 1, 2, 3, 4, 5 ];
    
    arr.forEach(ele => {
      ele = ele * 3;
    });
    console.log(arr); // [ 1, 2, 3, 4, 5 ]
    
    1
    2
    3
    4
    5
    6

    对引用类型进行操作,可以改变其部分属性。

    const objArr = [{
      name: 'wxw',
      age: 22,
    }, {
      name: 'wxw2',
      age: 33,
    }];
    
    objArr.forEach(ele => {
      if (ele.name === 'wxw2') {
        ele.age = 88;
      }
    });
    console.log(objArr); // [ { name: 'wxw', age: 22 }, { name: 'wxw2', age: 88 } ]
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14

    对引用类型进行操作,无法改变整个当前item。

    const changeItemArr = [{
      name: 'wxw',
      age: 22,
    }, {
      name: 'wxw2',
      age: 33,
    }];
    
    changeItemArr.forEach(ele => {
      if (ele.name === 'wxw2') {
        ele = {
          name: 'change',
          age: 77,
        };
      }
    });
    console.log(changeItemArr); //[ { name: 'wxw', age: 22 }, { name: 'wxw2', age: 33 } ]
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17

    采用第三个指针参数,可以进行任意改变。

    
    // 基本类型
    const numArr = [ 33, 4, 55 ];
    numArr.forEach((ele, index, arr) => {
      if (ele === 33) {
        arr[index] = 999;
      }
    });
    console.log(numArr); // [999, 4, 55]
    
    // 引用类型
    const allChangeArr = [{
      name: 'wxw',
      age: 22,
    }, {
      name: 'wxw2',
      age: 33,
    }];
    allChangeArr.forEach((ele, index, arr) => {
      if (ele.name === 'wxw2') {
        arr[index] = {
          name: 'change',
          age: 77,
        };
      }
    });
    console.log(allChangeArr); // [{name: "wxw", age: 22},{name: "change", age: 77}]
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27

    # promise、async、await 使用示例

    forEach对promise不敏感,其会同时出发所有的promise对象,这样速度会快,但会造成并发。例如:查询数据库中百万条数据,同时触发百万个调用。

    当采用promise、async、await作为forEach的callback函数时会出现执行顺序错误的问题。

    const ratings = [ 5, 4, 5 ];
    let sum = 0;
    
    const sumFunction = async function(a, b) {
      return a + b;
    };
    
    ratings.forEach(async function(rating) {
      sum = await sumFunction(sum, rating);
    });
    
    console.log(sum);
    // Expected output: 14
    // Actual output: 0
    
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14

    # 总结

       过以上对比发现,`for ... of` 没有`for i`的繁琐语句,也不像`forEach`那样不支持异步和中断操作,其还可以对各种数据类型自定义迭代方式,功能强大,适用场景也多。通过结合数组的`entries()`、`keys()`、`values()`等方法,还可实现key键和value值的同时遍历,在修复`for ... in`若干Bug的基础上完美将其取代。
    

    最后推荐大家遍历时首选for ... of,根据情况需要选择for i。多数情况下forEach和for...in可以被替代。

    编辑 (opens new window)
    上次更新: 2025/04/15, 03:48:14
    JavaScript部分特殊值判断记录
    防抖函数 Debounce

    ← JavaScript部分特殊值判断记录 防抖函数 Debounce→

    最近更新
    01
    docker基础概念
    02-26
    02
    js 获取变量准确类型
    02-19
    03
    Mysql SQL 优化思路
    02-18
    更多文章>
    Theme by Vdoing | Copyright © 2023-2025 Dreamer | MIT License
    粤ICP备2025379918号
    • 跟随系统
    • 浅色模式
    • 深色模式
    • 阅读模式