這周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()方法。循環将周遊對象本身的所有可枚舉屬性,以及對象從其構造函數原型中繼承的屬性(更接近原型鍊中對象的屬性覆寫原型屬性)。
枚舉性屬性的影響
- for in (周遊所有可枚舉屬性,不僅是 own properties 也包括原型鍊上的所有屬性)
- Object.keys(隻傳回對象本身具有的可枚舉的屬性)
- JSON.stringify() (隻讀取對象本身可枚舉屬性,并序列化為JSON字元串)
- Object.assign() (複制自身可枚舉的屬性,進行淺拷貝)
引入enumerable的最初目的,就是讓某些屬性可以規避掉for...in操作。比如,對象原型的toString方法,以及數組的length屬性,就通過這種手段,不會被for...in周遊到。
for...of
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