首頁  >  文章  >  web前端  >  在淺談JavaScript中遍歷數組和物件的幾種常用方法

在淺談JavaScript中遍歷數組和物件的幾種常用方法

青灯夜游
青灯夜游轉載
2021-06-25 11:21:422701瀏覽

陣列和物件在各種程式語言中都扮演著至關重要的角色,本篇文章為大家介紹一下JavaScript中常用陣列遍歷、物件遍歷的方法和各方法間的差異,以及使用時的注意事項。

在淺談JavaScript中遍歷數組和物件的幾種常用方法

陣列遍歷

#隨著 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  10 11
});

小結

  • 三者都是基本的從左到右遍歷數組
  • forEach 無法跳出循環;for 和for ..of 可以使用break 或continue 跳過或中斷。
  • for ...of 直接存取的是實際元素。 for 遍歷數組索引,forEach 回呼函數參數更豐富,元素、索引、原始數組都可以取得。
  • for ...of 與 for 如果陣列中存在空元素,同樣會執行。

some、every

const list = [
  { name: &#39;头部导航&#39;, backward: false },
  { name: &#39;轮播&#39;, backward: true },
  { name: &#39;页脚&#39;, backward: false },
];
const someBackward = list.some(item => item.backward);
// someBackward: true
const everyNewest = list.every(item => !item.backward);
// everyNewest: false

小結

    ##二者都是用來做數組條件判斷的,都是返回一個布林值
  • 二者都可以被中斷
  • some 若某一元素滿足條件,返回true,循環中斷;所有元素不滿足條件,回傳false。
  • every 與 some 相反,若有益元素不符合條件,回傳 false,循環中斷;所有元素滿足條件,回傳 true。

filter、map

const list = [
{ name: &#39;头部导航&#39;, type: &#39;nav&#39;, id: 1 },,
{ name: &#39;轮播&#39;, type: &#39;content&#39;, id: 2 },
{ name: &#39;页脚&#39;, type: &#39;nav&#39;, id: 3 },
];
const resultList = list.filter(item => {
  console.log(item);
  return item.type === &#39;nav&#39;;
});
// resultList: [
//   { name: &#39;头部导航&#39;, type: &#39;nav&#39;, id: 1 },
//   { name: &#39;页脚&#39;, type: &#39;nav&#39;, id: 3 },
// ]

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

// list: [
//   { name: &#39;头部导航&#39;, type: &#39;nav&#39;, id: 1 },
//   empty,
//   { name: &#39;轮播&#39;, type: &#39;content&#39;, id: 2 },
//   { name: &#39;页脚&#39;, type: &#39;nav&#39;, id: 3 },
// ]

小結

##二者都是產生一個新數組,都不會改變原始數組(不包括遍歷物件數組是,在回呼函數中操作元素物件)
  • 二者都會跳過空元素。有興趣的同學可以自己列印一下
  • map 會將回呼函數的回傳值組成一個新數組,數組長度與原始數組一致。
  • filter 會將符合回呼函數條件的元素組成一個新數組,數組長度與原始數組不同。
  • map 產生的新陣列元素是可自訂。
  • filter 產生的新數組元素不可自訂,與對應原始數組元素一致。

find、findIndex#

const list = [
{ name: &#39;头部导航&#39;, id: 1 },
{ name: &#39;轮播&#39;, id: 2 },
{ name: &#39;页脚&#39;, id: 3 },
];
const result = list.find((item) => item.id === 3);
// result: { name: &#39;页脚&#39;, id: 3 }
result.name = &#39;底部导航&#39;;
// list: [
//   { name: &#39;头部导航&#39;, id: 1 },
//   { name: &#39;轮播&#39;, id: 2 },
//   { name: &#39;底部导航&#39;, id: 3 },
// ]

const index = list.findIndex((item) => item.id === 3);
// index: 2
list[index].name // &#39;底部导航&#39;;

小結

二者都是用來找出數組元素。
  • find 方法傳回數組中滿足 callback 函數的第一個元素的值。如果不存在回傳 undefined。
  • findIndex 它傳回數組中找到的元素的索引,而不是其值,如果不存在返回 -1。

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

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

回呼函數接收四個參數:

accumulator:MDN 上解釋為累計器,但我覺得不恰當,按我的理解它應該是截至當前元素,之前所有的數組元素被回調函數處理累計的結果。
  • current:目前被執行的陣列元素。
  • currentIndex: 目前被執行的陣列元素索引。
  • sourceArray:原始數組,也就是呼叫 reduce 方法的陣列。
  • 如果不傳入初始值,reduce 方法會從索引 1 開始執行回呼函數,如果傳入初始值,將從索引 0 開始、並從初始值的基礎上累積執行回呼函數。
計算物件陣列某一屬性的總和

const list  = [
  { name: &#39;left&#39;, width: 20 },
  { name: &#39;center&#39;, width: 70 },
  { name: &#39;right&#39;, width: 10 },
];
const total = list.reduce((currentTotal, item) => {
  return currentTotal + item.width;
}, 0);
// total: 100

物件陣列的去重,並統計每一項重複次數

const list  = [
  { name: &#39;left&#39;, width: 20 },
  { name: &#39;right&#39;, width: 10 },
  { name: &#39;center&#39;, width: 70 },
  { name: &#39;right&#39;, width: 10 },
  { name: &#39;left&#39;, width: 20 },
  { name: &#39;right&#39;, 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: &#39;left&#39;, width: 20 },
//   { name: &#39;right&#39;, width: 10 },
//   { name: &#39;center&#39;, width: 70 },
// ]

物件數組最大/最小值獲取

const list  = [
  { name: &#39;left&#39;, width: 20 },
  { name: &#39;right&#39;, width: 30 },
  { name: &#39;center&#39;, width: 70 },
  { name: &#39;top&#39;, width: 40 },
  { name: &#39;bottom&#39;, 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高級用法》

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

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

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

console.time(&#39;every&#39;);
list.every(() => { return true })
console.timeEnd(&#39;every&#39;)
// some: 2.751708984375 ms

console.time(&#39;some&#39;);
list.some(() => { return false })
console.timeEnd(&#39;some&#39;)
// some: 2.786590576171875 ms

console.time(&#39;foreach&#39;);
list.forEach(() => {})
console.timeEnd(&#39;foreach&#39;);
// foreach: 3.126708984375 ms

console.time(&#39;map&#39;);
list.map(() => {})
console.timeEnd(&#39;map&#39;);
// map: 3.743743896484375 ms

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

從列印結果可以看出,for 迴圈的速度最快,for of 迴圈最慢

常用遍歷的終止、效能表格比較


是否可终止


** break continue return 性能(ms)
for 终止 ✅ 跳出本次循环 ✅ 2.42
forEach 3.12
map 3.74
for of 终止 ✅ 跳出本次循环 ✅ 6.33
some return true  ✅ 2.78
every return false ✅ 2.75

最后,不同浏览器内核 也会有些差异,有兴趣的同学也可以尝试一下。

对象遍历

在对象遍历中,经常需要遍历对象的键、值,ES5 提供了 for...in 用来遍历对象,然而其涉及对象属性的“可枚举属性”、原型链属性等,下面将从 Object 对象本质探寻各种遍历对象的方法,并区分常用方法的一些特点。

for in

Object.prototype.fun = () => {};const obj = { 2: &#39;a&#39;, 1: &#39;b&#39; };for (const i in obj) {  console.log(i, &#39;:&#39;, obj[i]);}// 1: b// 2: a// fun : () => {} Object 原型链上扩展的方法也被遍历出来for (const i in obj) {  if (Object.prototype.hasOwnProperty.call(obj, i)) {      console.log(i, &#39;:&#39;, 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 = &#39;ab&#39;;console.log(Object.keys(str));// [&#39;0&#39;, &#39;1&#39;]const arr = [&#39;a&#39;, &#39;b&#39;];console.log(Object.keys(arr));// [&#39;0&#39;, &#39;1&#39;]const obj = { 1: &#39;b&#39;, 0: &#39;a&#39; };console.log(Object.keys(obj));// [&#39;0&#39;, &#39;1&#39;]

小结

用于获取对象自身所有的可枚举的属性值,但不包括原型中的属性,然后返回一个由属性名组成的数组。

Object.values

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

小结

用于获取对象自身所有的可枚举的属性值,但不包括原型中的属性,然后返回一个由属性值组成的数组。

Object.entries

const str = &#39;ab&#39;;for (const [key, value] of Object.entries(str)) {    console.log(`${key}: ${value}`);}// 0: a// 1: bconst arr = [&#39;a&#39;, &#39;b&#39;];for (const [key, value] of Object.entries(arr)) {    console.log(`${key}: ${value}`);}// 0: a// 1: bconst obj = { 1: &#39;b&#39;, 0: &#39;a&#39; };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 = &#39;ab&#39;;console.log(Object.getOwnPropertyNames(str));// [&#39;0&#39;, &#39;1&#39;, &#39;length&#39;]const arr = [&#39;a&#39;, &#39;b&#39;];console.log(Object.getOwnPropertyNames(arr));// [&#39;0&#39;, &#39;1&#39;, &#39;length&#39;]const obj = { 1: &#39;b&#39;, 0: &#39;a&#39; };console.log(Object.getOwnPropertyNames(obj));// [&#39;0&#39;, &#39;1&#39;]

小结

用于获取对象自身所有的可枚举的属性值,但不包括原型中的属性,然后返回一个由属性名组成的数组。

总结

我们对比了多种常用遍历的方法的差异,在了解了这些之后,我们在使用的时候需要好好思考一下,就能知道那个方法是最合适的。欢迎大家纠正补充。

更多编程相关知识,请访问:编程视频!!

以上是在淺談JavaScript中遍歷數組和物件的幾種常用方法的詳細內容。更多資訊請關注PHP中文網其他相關文章!

陳述:
本文轉載於:segmentfault.com。如有侵權,請聯絡admin@php.cn刪除