zoukankan      html  css  js  c++  java
  • JS遍历循环方法性能对比:for/while/for in/for of/map/foreach/every

    这周codeReview例会,又遇到map与foreach到底谁问题。单独图方便,我会选择用map一个函数搞定一切。但是从语义的角度来讲,如果只是单纯遍历,还是推荐选择foreach。其实formap 与foreach,性能相差不大(个人测试数据在10000000,最后有测试案例)。如果用foreach 去实现map的效果,性能上就会比map差(因为需要操作另外一个数组).

    使用for,变量提前声明,性能会有一丢丢提升。如果循环变量i挂在全局变量上,也会造成性能损耗

    如果i是挂在全局上的,因为他每次loop完都要从全局中找回i值,i++ 和 判断

    而封装在 function里面的,对比与在全局里找i,单单在function 里找起来比较快

    ——《javascript循环时间判断优化!

    从性能上考量,我从eslint上禁止 for in。

    之前在gem代码重构的过程中,讲了很多次 for in for map foreach等遍历情况,但是没有过系统性地解析。

    这次决定 把之前看的东西,东拼西凑地再来一篇总结。

    遍历数组性能分析

    对数组的遍历大家最常用的就是for循环,ES5的话也可以使用forEach,ES5具有遍历数组功能的还有map、filter、some、every、reduce、reduceRight等,只不过他们的返回结果不一样。

    如果都做同样的遍历,他们的性能是怎么样的呢?

    { name: 'time-While', value: 18 },

    { name: 'time-ForFilter', value: 123 },

    { name: 'time-ForEvery', value: 139 },

    { name: 'time-ForSome', value: 140 },

    { name: 'time-ForOf', value: 158 },

    { name: 'time-ForEach', value: 174 },

    { name: 'time-ForMap', value: 190 },

    { name: 'time-For', value: 544 },

    { name: 'time-ForIn', value: 6119 }

    结果是 while 是最快的(理论上,感觉for与while应该是等效的)。 formap等es5 函数快于 for,formap 快于foreach . for in 最慢

    为什么for in 这么慢?

    使用for in会遍历数组所有的可枚举属性,包括原型。例如上栗的原型方法method和name属性

    解释器遇到for...in 循环时,在后台需要为对象建立一个枚举器(enumerator),这是一个昂贵的操作!

    for in 注意事项

    • index索引为字符串型数字,不能直接进行几何运算

    • 遍历顺序有可能不是按照实际数组的内部顺序

    for in遍历的是数组的索引(即键名),而for of遍历的是数组元素值。 所以for in更适合遍历对象,不要使用for in遍历数组

    for in 遍历顺序问题

    关于for in 属性问题,可以看下面两段代码

    const arr = [100, 'B', 4, '5', 3,  'A', 0];
    for (const key in arr) {
      console.log(`index:${key} value:${arr[key]}`);
    }
    console.log('________ ');
    function Foo() {
      this[100] = 100;
      this.B = 'B';
      this[4] = 4;
      this['5'] = '5';
      this[3] = 3;
      this.A = 'A';
      this[0] = 0;
    }
    const bar = new Foo();
    for (const key in bar) {
      console.log(`index:${key} value:${bar[key]}`);
    }

    在ECMAScript规范中定义了 「数字属性应该按照索引值⼤⼩升序排列,字符 串属性根据创建时的顺序升序排列。」

    V8内部,为了有效地提升存储和访问这两种属性的性能,分别使⽤了两个 线性数据结构来分别保存排序 属性和常规属性,具体结构如下图所⽰:

    js V8 排序属 常规属性

    对象中的数字属性称为 「排序属性」,在V8中被称为 elements,字符串属性就被称为 「常规属性」, 在V8中被称为 properties。

    在elements对象中,会按照顺序存放排序属性,properties属性则指向了properties对 象,在properties对象中,会按照创建时的顺序保存了常规属性。关于 for in 与 for of更详细的,参看  https://zhuanlan.zhihu.com/p/161892289

    for ..in 与 for..of区别

    一句话概括:for in是遍历(object)键名,for of是遍历(array)键值——for of 循环用来获取一对键值对中的值,而 for in 获取的是 键名。

    • for in 循环出的是key(并且key的类型是string),for of 循环出的是value。

    • for of 是es6引新引入的特性,修复了es5引入的for in 的不足。

    • for of 不能循环普通的对象,需要通过Object.keys搭配使用。

    对于他们的区别,一般就看下面一段代码就可:

    {
      const b = [1, 2, 3, 4];    // 创建一个数组
      b.name = '小明';               // 给数组添加一个属性
      Array.prototype.age = 12;      // 给数组的原型也添加一个属性
      console.log('for in ---------------');
      for (const key in b) {
        console.log(key);
      }
      console.log('for of ---------------');
      for (const key of b) {
        console.log(key);
      }
      console.log('forEach ---------------');
      b.forEach((item) => {
        console.log(item);
      });
    }
    console.log('______________ ');
    {
      const b = { a: 1, b: 2 };    // 创建一个对象
      b.name = '小明';               // 给对象添加一个属性
      Object.prototype.age = 12;      // 给对象的原型也添加一个属性
      console.log('for in ---------------');
      for (const key in b) {
        console.log(key);
      }
      console.log('forEach ---------------');
      Object.keys(b).forEach((item) => {
        console.log(item);
      });
    }

    可以通过hasOwnProperty限制for..in 遍历范围。

    for...in

    for...in 循环只遍历可枚举属性(包括它的原型链上的可枚举属性)。这个代码是为普通对象设计的,不适用于数组的遍历

    JavaScript中的可枚举属性与不可枚举属性

    在JavaScript中,对象的属性分为可枚举和不可枚举之分,它们是由属性的enumerable值决定的。可枚举性决定了这个属性能否被for…in查找遍历到。

    像 Array和Object使用内置构造函数所创建的对象都会继承自Object.prototype和String.prototype的不可枚举属性,例如 String 的 indexOf()  方法或 Object的toString()方法。循环将遍历对象本身的所有可枚举属性,以及对象从其构造函数原型中继承的属性(更接近原型链中对象的属性覆盖原型属性)。

    枚举性属性的影响
    1. for in (遍历所有可枚举属性,不仅是 own properties 也包括原型链上的所有属性)

    2. Object.keys(只返回对象本身具有的可枚举的属性)

    3. JSON.stringify() (只读取对象本身可枚举属性,并序列化为JSON字符串)

    4. Object.assign() (复制自身可枚举的属性,进行浅拷贝)

    引入enumerable的最初目的,就是让某些属性可以规避掉for...in操作。比如,对象原型的toString方法,以及数组的length属性,就通过这种手段,不会被for...in遍历到。

    for...of

    for of 是es6引新引入的特性,修复了es5引入的for in 的不足。

    for...of 只可遍历可迭代对象,for...of 语句在可迭代对象(包括Array,Map,Set,String,TypedArray,arguments 对象等等)上创建一个迭代循环,调用自定义迭代钩子,并为每个不同属性的值执行语句

    什么数据可以for of遍历

    一个数据结构只要部署了 Symbol.iterator 属性, 就被视为具有 iterator接口, 就可以使用 for of循环。

    些数据结构部署了 Symbol.iteratoer属性了呢?

    只要有 iterator 接口的数据结构,都可以使用 for of循环。

    • 数组 Array

    • Map

    • Set

    • String

    • arguments对象

    • Nodelist对象, 就是获取的dom列表集合

    -以上这些都可以直接使用 for of 循环。 凡是部署了 iterator 接口的数据结构也都可以使用数组的 扩展运算符(...)、和解构赋值等操作。

    for of不可以遍历普通对象,想要遍历对象的属性,可以用for in循环, 或内建的Object.keys()方法。

    for循环与ES5新增的foreach/map 等方法有何区别?

    forEach 不支持在循环中添加删除操作,因为在使用 forEach 循环的时候数组(集合)就已经被锁定不能被修改。(改了也没用)

    在 for 循环中可以使用 continue,break 来控制循环和跳出循环,这个是 forEach 所不具备的。【在这种情况下,从性能的角度考虑,for 是要比 forEach 有优势的。 替代方法是 filter、some等专用方法。

    遍历对象性能分析

    遍历对象,之前用for in,我现在一般用Object.keys来获取值数组。再来遍历对象。他们的性能对比如何?

    { name: 'Object.keys.map', value: 21 },

    { name: 'forIn', value: 30 }

    Object.keys来遍历对象,也比for in 要快

    数组测试代码

    const size = 10000000;

    let times = [];
    {
      const arrFor = new Array(size).fill(1);
      let timeFor = +new Date();
      console.time('arrFor');
      for (let i = 0;i < arrFor.length;i++) {
        const b = arrFor[i];
        //
      }
      console.timeEnd('arrFor');
      timeFor = new Date().getTime() - timeFor;
      times.push({ name: 'time-For', value: timeFor });
    }

    {
      const arrWhile = new Array(size).fill(1);
      let timeWhile = +new Date();
      console.time('timeWhile');
      let i = arrWhile.length - 1;
      while (i > -1) {
        const b = arrWhile[i];
        i--;
      }
      console.timeEnd('timeWhile');
      timeWhile = new Date().getTime() - timeWhile;
      times.push({ name: 'time-While', value: timeWhile });
    }

    {
      const arrForOf = new Array(size).fill(1);
      let timeForOf = +new Date();
      console.time('timeForOf');
      for (const item of arrForOf) {

      }
      console.timeEnd('timeForOf');
      timeForOf = new Date().getTime() - timeForOf;
      times.push({ name: 'time-ForOf', value: timeForOf });
    }
    {
      const arrForIn = new Array(size).fill(1);
      let timeForIn = +new Date();
      console.time('timeForIn');
      for (const key in arrForIn) {
        // 注意key不是
      }
      console.timeEnd('timeForIn');
      timeForIn = new Date().getTime() - timeForIn;
      times.push({ name: 'time-ForIn', value: timeForIn });
    }
    {
      const arrForEach = new Array(size).fill(1);
      let timeForEach = +new Date();
      console.time('timeForEach');
      arrForEach.forEach((item, index) => {

      });
      console.timeEnd('timeForEach');
      timeForEach = new Date().getTime() - timeForEach;
      times.push({ name: 'time-ForEach', value: timeForEach });
    }
    {
      const arrForMap = new Array(size).fill(1);
      let timeForMap = +new Date();
      console.time('timeForMap');
      arrForMap.map((item, index) => {

      });
      console.timeEnd('timeForMap');
      timeForMap = new Date().getTime() - timeForMap;
      times.push({ name: 'time-ForMap', value: timeForMap });
    }
    {
      const arrForEvery = new Array(size).fill(1);
      let timeForEvery = +new Date();
      console.time('timeForEvery');
      arrForEvery.every((item, index) => true);
      console.timeEnd('timeForEvery');
      timeForEvery = new Date().getTime() - timeForEvery;
      times.push({ name: 'time-ForEvery', value: timeForEvery });
    }

    {
      const arrForEvery = new Array(size).fill(1);
      let timeForEvery = +new Date();
      console.time('timeForSome');
      arrForEvery.some((item, index) => false);
      console.timeEnd('timeForSome');
      timeForEvery = new Date().getTime() - timeForEvery;
      times.push({ name: 'time-ForSome', value: timeForEvery });
    }
    {
      const arrForEvery = new Array(size).fill(1);
      let timeForEvery = +new Date();
      console.time('timeForFilter');
      arrForEvery.filter((item, index) => false);
      console.timeEnd('timeForFilter');
      timeForEvery = new Date().getTime() - timeForEvery;
      times.push({ name: 'time-ForFilter', value: timeForEvery });
    }
    times = times.sort((a, b) =>     a.value - b.value);
    console.log(times);

    不知道这个测试代码是否可以改进。

    foreach与map获得一个新数组

    const size = 10000000;

    let times = [];

    {
      const arrForEach = new Array(size).fill(1);
      let timeForEach = +new Date();
      console.time('timeForEach');
      const arr1 = [];
      arrForEach.forEach((item, index) => {
        arr1.push(item + 1);
      });
      console.timeEnd('timeForEach');
      timeForEach = new Date().getTime() - timeForEach;
      times.push({ name: 'time-ForEach', value: timeForEach });
    }
    {
      const arrForMap = new Array(size).fill(1);
      let timeForMap = +new Date();
      console.time('timeForMap');
      const arr1 = arrForMap.map((item, index) => item + 1);
      console.timeEnd('timeForMap');
      timeForMap = new Date().getTime() - timeForMap;
      times.push({ name: 'time-ForMap', value: timeForMap });
    }
    times = times.sort((a, b) =>     a.value - b.value);
    console.log(times);

    因为map直接返回了。foreach需要操作另外一个数组,造成性能损耗。我猜的哈。

    for变量提前声明与while性能对比

    const size = 10000000;

    let times = [];
    {
      const arrFor = new Array(size).fill(1);
      let timeFor = +new Date();
      console.time('arrFor0');
      for (let i = 0 ;i < arrFor.length;i++) {
        const b = arrFor[i];
        //
      }
      console.timeEnd('arrFor0');
      timeFor = new Date().getTime() - timeFor;
      times.push({ name: 'time-For0', value: timeFor });
    }
    {
      const arrFor = new Array(size).fill(1);
      let timeFor = +new Date();
      console.time('arrFor');
      for (let i = size - 1;i > -1;i--) {
        const b = arrFor[i];
        //
      }
      console.timeEnd('arrFor');
      timeFor = new Date().getTime() - timeFor;
      times.push({ name: 'time-For', value: timeFor });
    }
    {
      const arrFor = new Array(size).fill(1);
      let timeFor = +new Date();
      console.time('arrFor1');
      let i = 0;
      for (;i < size;i++) {
        const b = arrFor[i];
        //
      }
      console.timeEnd('arrFor1');
      timeFor = new Date().getTime() - timeFor;
      times.push({ name: 'time-For1', value: timeFor });
    }
    {
      const arrFor = new Array(size).fill(1);
      let timeFor = +new Date();
      console.time('arrFor2');
      let i = size - 1;
      for (;i > -1;i--) {
        const b = arrFor[i];
        //
      }
      console.timeEnd('arrFor2');
      timeFor = new Date().getTime() - timeFor;
      times.push({ name: 'time-For2', value: timeFor });
    }
    {
      const arrWhile = new Array(size).fill(1);
      let timeWhile = +new Date();
      console.time('timeWhile');
      let i = size - 1;
      while (i > -1) {
        const b = arrWhile[i];
        i--;
      }
      console.timeEnd('timeWhile');
      timeWhile = new Date().getTime() - timeWhile;
      times.push({ name: 'time-While', value: timeWhile });
    }
    times = times.sort((a, b) =>     a.value - b.value);
    console.log(times);

    测试结果:

    { name: 'time-For2', value: 11 },

    { name: 'time-While', value: 11 },

    { name: 'time-For', value: 14 },

    { name: 'time-For1', value: 14 },

    { name: 'time-For0', value: 18 }

    对象测试代码

    const size = 100000;

    let times = [];
    {
      const arrFor = Array.from(new Array(size), (n, index) => [index, index + 1]);
      let timeFor = +new Date();
      const obj = Object.fromEntries(arrFor);
      console.time('forIn');
      for (const key in obj) {
        const item  = obj[key];
      }
      console.timeEnd('forIn');
      timeFor = new Date().getTime() - timeFor;
      times.push({ name: 'forIn', value: timeFor });
    }
    {
      const arrFor = Array.from(new Array(size), (n, index) => [index, index + 1]);
      let timeFor = +new Date();
      const obj = Object.fromEntries(arrFor);
      console.time('Object.keys.map');
      Object.keys(obj).map((key) => {
        const item = obj[key];
      });
      console.timeEnd('Object.keys.map');
      timeFor = new Date().getTime() - timeFor;
      times.push({ name: 'Object.keys.map', value: timeFor });
    }
    times = times.sort((a, b) =>     a.value - b.value);
    console.log(times);

    先这样吧

    后面再来整理一下。

    参考文章:

    Js中for in 和for of的区别 https://juejin.cn/post/6844903601261772808

    for…in和for…of的用法与区别 https://segmentfault.com/a/1190000022348279

    [JavaScript] for、forEach、for...of、for...in 的区别与比较 https://blog.csdn.net/csdn_yudong/article/details/85053698

    for in 和 for of 的区别? https://zhuanlan.zhihu.com/p/282961866

    百度前端面试题:for in 和 for of的区别详解以及为for in的输出顺序 https://zhuanlan.zhihu.com/p/161892289

  • 相关阅读:
    二分练习题4 查找最接近的元素 题解
    二分练习题5 二分法求函数的零点 题解
    二分练习题3 查找小于x的最大元素 题解
    二分练习题2 查找大于等于x的最小元素 题解
    二分练习题1 查找元素 题解
    code forces 1176 D. Recover it!
    code forces 1173 B. Nauuo and Chess
    code forces 1173 C. Nauuo and Cards
    吴恩达深度学习课程笔记-15
    吴恩达深度学习课程笔记-14
  • 原文地址:https://www.cnblogs.com/zhoulujun/p/15182407.html
Copyright © 2011-2022 走看看