JS遍历循环⽅法性能对⽐:forwhileforinforofmapforeachevery
这周codeReview例会,⼜遇到map与foreach到底谁问题。单独图⽅便,我会选择⽤map⼀个函数搞定⼀切。但是从语义的⾓度来讲,如果只是单纯遍历,还是推荐选择foreach。其实formap 与foreach,性能相差不⼤(个⼈测试数据在10000000,最后有测试案例)。如果⽤foreach 去实现map的效果,性能上就会⽐map差(因为需要操作另外⼀个数组).
使⽤for,变量提前声明,性能会有⼀丢丢提升。如果循环变量i挂在全局变量上,也会造成性能损耗
如果i是挂在全局上的,因为他每次loop完都要从全局中回i值,i++ 和判断
⽽封装在 function⾥⾯的,对⽐与在全局⾥i,单单在function ⾥起来⽐较快
——《》
从性能上考量,我从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属性
解释器遇到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更详细的,参看
for ..in 与 f区别
⼀句话概括: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 遍历范围。
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的最初⽬的,就是让某些属性可以规避掉in操作。⽐如,对象原型的toString⽅法,以及数组的length属性,就通过这种⼿段,不会被in遍历到。
for of 是es6引新引⼊的特性,修复了es5引⼊的for in 的不⾜。
什么数据可以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');
js获取json的key和valuearrForEvery.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 });

版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。