JS数组与对象常用遍历方法详解及性能对比
本文系统梳理了JavaScript中数组与对象的常用遍历方式,涵盖for、forEach、for...of、reduce等数组方法及for...in、Object.keys、Object.entries等对象遍历API。通过对比执行逻辑、终止条件及Chrome实测性能,帮助开发者根据场景选择最合适的遍历方案,避免常见陷阱。
前言
数组和对象作为最基础的数据结构,在各类编程语言中都占据核心地位。若缺少它们,编程体验将难以想象,尤其在灵活多变的弱类型语言 JavaScript 中更是如此。本文将深入解析常用的数组与对象遍历方法,对比其使用场景与注意事项。
数组遍历
随着 JavaScript 标准的演进,截至 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
});
小结
- 这三种方式均遵循从左至右的顺序遍历数组。
- 其中,forEach 不支持跳出循环;而 for 和 for...of 均可通过 break 或 continue 实现跳过或中断。
- for...of 直接获取数组的实际元素值;for 遍历的是数组索引;forEach 的回调函数参数更为丰富,可同时获取元素值、索引及原数组引用。
- 当数组中存在空元素(稀疏数组)时,for...of 与 for 均会执行对应逻辑。
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
小结
- 这两个方法均用于数组条件判断,最终返回布尔值。
- 二者均支持在满足条件时中断循环。
- some 方法只要有一个元素满足条件即返回 true 并中断循环;若所有元素均不满足,则返回 false。
- every 方法与 some 逻辑相反:只要有一个元素不满足条件即返回 false 并中断循环;若所有元素均满足,则返回 true。
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 },
// ]
小结
- 二者均生成新数组,且不修改原数组(注:若遍历的是对象数组且在回调中直接操作元素对象,则原数组会被改变)。
- 二者都会自动跳过空元素,建议读者自行打印验证。
- map 方法将回调函数的返回值组成新数组,新数组长度与原数组保持一致。
- filter 方法将符合回调函数条件的元素筛选出来组成新数组。
- map 生成的新数组元素内容可由回调函数自定义。
- filter 生成的新数组元素与原数组对应元素完全一致,不可自定义。
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 }
result.name = '底部导航';
// list: [
// { name: '头部导航', id: 1 },
// { name: '轮播', id: 2 },
// { name: '底部导航', id: 3 },
// ]
const index = list.findIndex((item) => item.id === 3);
// index: 2
list[index].name // '底部导航';
小结
- 这两个方法均用于查找数组中的特定元素。
- find 方法返回满足 callback 函数的第一个元素的值;若未找到,则返回 undefined。
- findIndex 方法返回满足条件的元素索引值,而非元素本身;若未找到,则返回 -1。
reduce、reduceRight
reduce 方法接收两个参数:第一个是回调函数(callback),第二个是初始值(initialValue)。
reduceRight 方法与 reduce 的执行方向相反(从右向左),其余逻辑完全一致。
回调函数接收四个参数:
- accumulator:MDN 解释为累计器,但更准确的理解是截至当前元素前,所有数组元素经回调函数处理后的累计结果。
- current:当前正在被执行的数组元素。
- currentIndex:当前正在被执行的数组元素的索引。
- sourceArray:调用 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高级用法》。
性能对比
上述遍历方法在性能上是否存在差异?我们在 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 循环最慢。
常用遍历的终止、性能表格对比

最后,不同浏览器内核的表现可能存在差异,有兴趣的读者可自行尝试验证。
对象遍历
在对象遍历中,常需获取键或值。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 章节。
- 首先列出名字为非负整数字符串的属性,按数值从小到大排序。这意味着数组和类数组对象的属性会按顺序枚举。
- 接着列出所有类数组索引之外的属性,包括看似负数或浮点数的字符串名。这些属性按添加到对象的先后顺序列出。对于对象字面量中定义的属性,按其在字面量中出现的顺序列出。
- 最后,名字为 Symbol 对象的属性按添加到对象的先后顺序列出。
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']
小结
用于获取对象自身所有可枚举及不可枚举的属性名,不包括原型中的属性,返回一个由属性名组成的数组。
总结
我们对比了多种常用遍历方法的差异。了解这些区别后,在实际使用时需仔细思考,以选择最合适的方法。欢迎读者纠正与补充。
到此这篇关于 JS 中数组常用的循环遍历的文章就介绍到这了,更多相关 JS 数组循环遍历内容请搜索本站以前的文章或继续浏览下面的相关文章,希望大家以后多多支持本站!
什么时候该用JS构造器:与工厂模式、字面量的选择对比
2026-11-05 16:18:22
推荐几款轻量级JS照片墙开源库及对比
2026-11-05 15:56:19
JavaScript递归写法有哪些常见陷阱及避免方法
2026-11-05 14:53:38
JS数组浅拷贝与深拷贝怎么选?场景与方法总结
2026-11-05 14:41:02
p5.js 中 setup() 和 draw() 函数的执行机制详解
2026-11-05 12:32:38
