JS 常用的循環遍歷你會幾種?

本文首發於政採雲前端團隊博客:JS 常用的循環遍歷你會幾種

https://www.zoo.team/article/cycle-in-js

前言

數組和對象作爲一個最基礎數據結構,在各種編程語言中都充當着至關重要的角色,你很難想象沒有數組和對象的編程語言會是什麼模樣。特別是 JS,弱類型語言,非常靈活。本文帶你瞭解常用數組遍歷、對象遍歷的使用,對比以及注意事項。

數組遍歷

隨着 JS 的不斷髮展,截至 ES7 規範已經有十多種遍歷方法。下面按照功能類似的方法爲一組,來介紹數組的常用遍歷方法。

for、forEach、for ...of

const list = [1, 2, 3, 4, 5, 6, 7, 8,, 10, 11];

for (let i = 0, len = list.length; i < len; i++) {
  if (list[i] === 5) {
    break; // 1 2 3 4
    // continue; // 1 2 3 4 6 7 8 undefined 10 11
  }
  console.log(list[i]);
}

for (const item of list) {
  if (item === 5) {
    break; // 1 2 3 4
    // continue; // 1 2 3 4 6 7 8 undefined 10 11
  }
  console.log(item);
}

list.forEach((item, index, arr) ={
  if (item === 5) return;
  console.log(index); // 0 1 2 3 5 6 7 9 10
  console.log(item); // 1 2 3 4 6 7 8 9 10
});

小結

some、every

const list = [
  { name: '頭部導航', backward: false },
  { name: '輪播', backward: true },
  { name: '頁腳', backward: false },
];
const someBackward = list.some(item => item.backward);
// someBackward: true
const everyNewest = list.every(item => !item.backward);
// everyNewest: false

小結

filter、map

const list = [
{ name: '頭部導航', type: 'nav', id: 1 },,
{ name: '輪播', type: 'content', id: 2 },
{ name: '頁腳', type: 'nav', id: 3 },
];
const resultList = list.filter(item ={
  console.log(item);
  return item.type === 'nav';
});
// resultList: [
//   { name: '頭部導航', type: 'nav', id: 1 },
//   { name: '頁腳', type: 'nav', id: 3 },
// ]

const newList = list.map(item ={
  console.log(item);
  return item.id;
});
// newList: [1, empty, 2, 3]

// list: [
//   { name: '頭部導航', type: 'nav', id: 1 },
//   empty,
//   { name: '輪播', type: 'content', id: 2 },
//   { name: '頁腳', type: 'nav', id: 3 },
// ]

小結

find、findIndex

const list = [
    { name: '頭部導航', id: 1 },
    { name: '輪播', id: 2 },
    { name: '頁腳', id: 3 },
];
const result = list.find(item => item.id === 3);
// result: { name: '頁腳', id: 3 }
const index = list.findIndex(item => item.id === 3);
// index: 2

小結

reduce、reduceRight

reduce 方法接收兩個參數,第一個參數是回調函數(callback) ,第二個參數是初始值(initialValue)。

reduceRight 方法除了與 reduce 執行方向相反外 (從右往左),其他完全與其一致。

回調函數接收四個參數:

 

如果不傳入初始值,reduce 方法會從索引 1 開始執行回調函數,如果傳入初始值,將從索引 0 開始、並從初始值的基礎上累計執行回調。

計算對象數組某一屬性的總和
const list  = [
  { name: 'left', width: 20 },
  { name: 'center', width: 70 },
  { name: 'right', width: 10 },
];
const total = list.reduce((currentTotal, item) ={
  return currentTotal + item.width;
}, 0);
// total: 100
對象數組的去重,並統計每一項重複次數
const list  = [
  { name: 'left', width: 20 },
  { name: 'right', width: 10 },
  { name: 'center', width: 70 },
  { name: 'right', width: 10 },
  { name: 'left', width: 20 },
  { name: 'right', width: 10 },
];
const repeatTime = {};
const result = list.reduce((array, item) ={
  if (repeatTime[item.name]) {
    repeatTime[item.name]++;
    return array;
  }
  repeatTime[item.name] = 1;
  return [...array, item];
}[]);
// repeatTime: { left: 2, right: 3, center: 1 }
// result: [
//   { name: 'left', width: 20 },
//   { name: 'right', width: 10 },
//   { name: 'center', width: 70 },
// ]
對象數組最大 / 最小值獲取
const list  = [
  { name: 'left', width: 20 },
  { name: 'right', width: 30 },
  { name: 'center', width: 70 },
  { name: 'top', width: 40 },
  { name: 'bottom', width: 20 },
];
const max = list.reduce((curItem, item) ={
  return curItem.width >= item.width ? curItem : item;
});
const min = list.reduce((curItem, item) ={
  return curItem.width <= item.width ? curItem : item;
});
// max: { name: "center", width: 70 }
// min: { name: "left", width: 20 }

reduce 很強大,更多奇技淫巧推薦查看這篇《25 個你不得不知道的數組 reduce 高級用法》(https://juejin.im/post/6844904063729926152)

性能對比

說了這麼多,那這些遍歷方法, 在性能上有什麼差異呢?我們在 Chrome 瀏覽器中嘗試。我採用每個循環執行 10 次,去除最大、最小值 取平均數,降低誤差。

var list = Array(100000).fill(1)

console.time('for');
for (let index = 0, len = list.length; index < len; index++) {
}
console.timeEnd('for');
// for: 2.427642822265625 ms

console.time('every');
list.every(() ={ return true })
console.timeEnd('every')
// some: 2.751708984375 ms

console.time('some');
list.some(() ={ return false })
console.timeEnd('some')
// some: 2.786590576171875 ms

console.time('foreach');
list.forEach(() ={})
console.timeEnd('foreach');
// foreach: 3.126708984375 ms

console.time('map');
list.map(() ={})
console.timeEnd('map');
// map: 3.743743896484375 ms

console.time('forof');
for (let index of list) {
}
console.timeEnd('forof')
// forof: 6.33380126953125 ms

從打印結果可以看出,for 循環的速度最快,for ...of 循環最慢。

常用遍歷的終止、性能表格對比

mpzSbe

最後,不同瀏覽器內核 也會有些差異, 有興趣的同學也可以嘗試一下。

對象遍歷

在對象遍歷中,經常需要遍歷對象的鍵、值,ES5 提供了 for...in 用來遍歷對象,然而其涉及對象屬性的 “可枚舉屬性”、原型鏈屬性等,下面將從 Object 對象本質探尋各種遍歷對象的方法,並區分常用方法的一些特點。

for in

Object.prototype.fun = () ={};
const obj = { 2: 'a', 1: 'b' };
for (const i in obj) {
  console.log(i, ':', obj[i]);
}
// 1: b
// 2: a
// fun : () ={} Object 原型鏈上擴展的方法也被遍歷出來
for (const i in obj) {
  if (Object.prototype.hasOwnProperty.call(obj, i)) {
      console.log(i, ':', obj[i]);
    }
}
// name : a 不屬於自身的屬性將被 hasOwnProperty 過濾

小結

使用 for in 循環時,返回的是所有能夠通過對象訪問的、可枚舉的屬性,既包括存在於實例中的屬性,也包括存在於原型中的實例。如果只需要獲取對象的實例屬性,可以使用 hasOwnProperty 進行過濾。

使用時,要使用 (const x in a) 而不是 (x in a) 後者將會創建一個全局變量。

for in 的循環順序,參考【 JavaScript 權威指南】(第七版)6.6.1。

Object.keys

Object.prototype.fun = () ={};
const str = 'ab';
console.log(Object.keys(str));
// ['0''1']
const arr = ['a''b'];
console.log(Object.keys(arr));
// ['0''1']
const obj = { 1: 'b', 0: 'a' };
console.log(Object.keys(obj));
// ['0''1']

小結

用於獲取對象自身所有的可枚舉的屬性值,但不包括原型中的屬性,然後返回一個由屬性名組成的數組。

Object.values

Object.prototype.fun = () ={};
const str = 'ab';
console.log(Object.values(str));
// ['a''b']
const arr = ['a''b'];
console.log(Object.values(arr));
// ['a''b']
const obj = { 1: 'b', 0: 'a' };
console.log(Object.values(obj));
// ['a''b']

小結

用於獲取對象自身所有的可枚舉的屬性值,但不包括原型中的屬性,然後返回一個由屬性值組成的數組。

Object.entries

const str = 'ab';
for (const [key, value] of Object.entries(str)) {
    console.log(`${key}${value}`);
}
// 0: a
// 1: b
const arr = ['a''b'];
for (const [key, value] of Object.entries(arr)) {
    console.log(`${key}${value}`);
}
// 0: a
// 1: b
const obj = { 1: 'b', 0: 'a' };
for (const [key, value] of Object.entries(obj)) {
    console.log(`${key}${value}`);
}
// 0: a
// 1: b

小結

用於獲取對象自身所有的可枚舉的屬性值,但不包括原型中的屬性,然後返回二維數組。每一個子數組由對象的屬性名、屬性值組成。可以同時拿到屬性名與屬性值的方法。

Object.getOwnPropertyNames

Object.prototype.fun = () ={};
Array.prototype.fun = () ={};
const str = 'ab';
console.log(Object.getOwnPropertyNames(str));
// ['0''1''length']
const arr = ['a''b'];
console.log(Object.getOwnPropertyNames(arr));
// ['0''1''length']
const obj = { 1: 'b', 0: 'a' };
console.log(Object.getOwnPropertyNames(obj));
// ['0''1']

小結

用於獲取對象自身所有的可枚舉的屬性值,但不包括原型中的屬性,然後返回一個由屬性名組成的數組。

總結

我們對比了多種常用遍歷的方法的差異,在瞭解了這些之後,我們在使用的時候需要好好思考一下,就能知道那個方法是最合適的。歡迎大家糾正補充。

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/a6hdoxbzuF7xd7krNQ5OwQ