写点什么

JS 遍历循环方法性能对比:for/while/for in/for of/map/foreach/every

用户头像
zhoulujun
关注
发布于: 3 小时前

这周 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 是最快的。 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('________\n');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 内部,为了有效地提升存储和访问这两种属性的性能,分别使⽤了两个 线性数据结构来分别保存排序 属性和常规属性,具体结构如下图所⽰:



对象中的数字属性称为 「排序属性」,在 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('______________\n');{  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');  let i = 0  for ( ;i < arrFor;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('arrFor');  for (let i = arrFor.length - 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('arrFor2');  let i = arrFor.length - 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 = 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 });}times = times.sort((a, b) =>     a.value - b.value);console.log(times);
复制代码

测试结果:

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

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

{ name: 'time-For', 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


转载本站文章《JS遍历循环方法性能对比:for/while/for in/for of/map/foreach/every》,请注明出处:https://www.zhoulujun.cn/html/webfront/ECMAScript/js/2021_0822_8668.html

发布于: 3 小时前阅读数: 4
用户头像

zhoulujun

关注

还未添加个人签名 2021.06.25 加入

还未添加个人简介

评论

发布
暂无评论
JS遍历循环方法性能对比:for/while/for in/for of/map/foreach/every