这周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('________\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');
  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

JS遍历循环方法性能对比:for/while/for in/for of/map/foreach/every的更多相关文章

  1. JS数组循环的性能和效率分析(for、while、forEach、map、for of)

    从最简单的for循环说起 for( 初始化:条件; ){} 条件为Trusy 值时候,可以继续执行for 循环,当条件变为Falsy 时跳出for循环.for循环常见的四种写法const person ...

  2. ArrayList和LinkedList的几种循环遍历方式及性能对比分析(转)

    主要介绍ArrayList和LinkedList这两种list的五种循环遍历方式,各种方式的性能测试对比,根据ArrayList和LinkedList的源码实现分析性能结果,总结结论. 通过本文你可以 ...

  3. ArrayList和LinkedList的几种循环遍历方式及性能对比分析

    最新最准确内容建议直接访问原文:ArrayList和LinkedList的几种循环遍历方式及性能对比分析 主要介绍ArrayList和LinkedList这两种list的五种循环遍历方式,各种方式的性 ...

  4. ArrayList和LinkedList的几种循环遍历方式及性能对比分析(转载)

    原文地址: http://www.trinea.cn/android/arraylist-linkedlist-loop-performance/ 原文地址: http://www.trinea.cn ...

  5. 【转】ArrayList和LinkedList的几种循环遍历方式及性能对比分析

    原文网址:http://www.trinea.cn/android/arraylist-linkedlist-loop-performance/ 主要介绍ArrayList和LinkedList这两种 ...

  6. Java 集合 ArrayList和LinkedList的几种循环遍历方式及性能对比分析 [ 转载 ]

    Java 集合 ArrayList和LinkedList的几种循环遍历方式及性能对比分析 @author Trinea 原文链接:http://www.trinea.cn/android/arrayl ...

  7. (转)ArrayList和LinkedList的几种循环遍历方式及性能对比分析

    主要介绍ArrayList和LinkedList这两种list的五种循环遍历方式,各种方式的性能测试对比,根据ArrayList和LinkedList的源码实现分析性能结果,总结结论. 通过本文你可以 ...

  8. HashMap循环遍历方式及其性能对比(zhuan)

    http://www.trinea.cn/android/hashmap-loop-performance/ ********************************************* ...

  9. HashMap循环遍历方式及其性能对比

    主要介绍HashMap的四种循环遍历方式,各种方式的性能测试对比,根据HashMap的源码实现分析性能结果,总结结论.   1. Map的四种遍历方式 下面只是简单介绍各种遍历示例(以HashMap为 ...

  10. ArrayList和LinkedList遍历方式及性能对比分析

    ArrayList和LinkedList的几种循环遍历方式及性能对比分析 主要介绍ArrayList和LinkedList这两种list的五种循环遍历方式,各种方式的性能测试对比,根据ArrayLis ...

随机推荐

  1. html-0

    选择器 (一):first-child和:first-of-type :first-child第一个元素 <!DOCTYPE html> <html> <head> ...

  2. Session概述(选自WebX)

    http://openwebx.org/docs/Webx3_Guide_Book.html#d0e9084 8.1. Session概述 8.1.1. 什么是Session HTTP协议是无状态的, ...

  3. String 的 indexOf 与 search 方便的区别

    String 这个对象里面包含许多方法 今天只要讲 indexOf 与 search 1.indexOf stringObject.indexOf(searchvalue,fromindex) 2.s ...

  4. 《最新出炉》系列初窥篇-Python+Playwright自动化测试-29-处理日历时间控件-中篇

    1.简介 上一篇的日历时间控件宏哥介绍的比较传统也是很常见的.宏哥不能说它很low,但是相比较一些高端.大气.上档次的日历时间控件,结果就一目了然了:确实很low. 2.被测网站 2.1高大上日历时间 ...

  5. 你知道C++如何在一个函数内返回不同类型吗?

    C++ 中要在一个函数内返回不同类型的值,你可以使用 C++17 引入的 std::variant 或 std::any,或者使用模板和多态.下面将分别介绍这些方法. 方法一:使用 std::vari ...

  6. JPA动态注册多数据源

    背景 目前已经是微服务的天下,但是随着业务需求的日益增长,部分应用还是出现了需要同时连接多个数据源操作数据的技术诉求. 需要对现有的技术架构进行优化升级,查阅了下网上的文章,基本都是照搬的同一篇文章, ...

  7. MVC控制器传值到JS

    1.传递整形数字 1 <script> 2 var data=@ViewBag.ID; 3 </script> 2.传递字符串 1 <script> 2 var d ...

  8. [ABC261A] Intersection

    Problem Statement We have a number line. Takahashi painted some parts of this line, as follows: Firs ...

  9. C++学习笔记七:输出格式<ios><iomanip>

    这一篇主要总结一下C++标准库里输出格式相关的库函数. https://en.cppreference.com/w/cpp/io/manip 1.库: <ostream> <ios& ...

  10. svn、git服务器配置进程方式网关不生效常见处理方法

    过华企盾DSC防泄密系统配置svn或者git服务器的时候,网关不生效如何解决? 1.先在cmd中运行一下命令netstat -ano|findstr "8080" 找到svn.gi ...