在JavaScript开发中,数组是最基础却最容易被误解的数据结构之一。本文将通过实际代码示例,深入剖析数组的创建、遍历方法以及开发中常见的陷阱。
一、数组创建的两种方式
1. 数组字面量 vs new Array()
// 数组字面量
const arr = [1, 2, 3];
// new Array创建稀疏数组
const sparseArr = new Array(5);
console.log(sparseArr); // [empty × 5]
使用new Array(length)创建数组时:
- 创建的是稀疏数组(sparse array)
- 数组元素未被初始化(值为empty而非undefined)
- 无法通过
for...in遍历(因为数组没有实际键值)
初始化处理:
// 正确初始化固定长度数组
const arr = new Array(5).fill(undefined);
console.log(arr); // [undefined, undefined, ...]
二、数组遍历的六大方法
1. 传统计数循环
const names = ['张三','李四','王五'];
for(let i = 0; i < names.length; i++) {
console.log(`索引 ${i}: ${names[i]}`);
}
优点:性能最优(对计算机好),可随时break终止
缺点:代码冗余,需手动处理索引边界,可读性差
2. forEach方法
names.forEach(name => {
if (name === '张三') return; // 注意return不能终止循环!
console.log(`处理 ${name}`);
});
循环陷阱:
- 无法使用break/return终止循环
- 异步回调中可能产生意外执行顺序
3. for of循环
遍历对象的可迭代值(value)
for(const name of names) {
if (name === '李四') break; // 支持break终止
console.log(name);
}
只遍历值怎么获取索引呢?那就需要另一个api配合——entries()
for(const [index, name] of names.entries()) {
console.log(`第${index}位: ${name}`);
}
4. for in循环
遍历对象的可枚举属性名(key)
const arr = new Array(5);
arr[2] = '测试';
// 问题1:无法遍历稀疏数组的空位
for(let key in arr) {
console.log(key); // 只输出"2"
}
// 问题2:会遍历原型链属性
Array.prototype.customMethod = () => {};
for(let key in arr) {
console.log(key); // 可能输出"customMethod"!
}
会遍历原型链属性!要慎用
可以配合hasOwnProperty,有才打印:
for(let key in arr) {
if(arr.hasOwnProperty(key)) {
console.log(arr[key]);
}
}
5. map/filter
// 创建新数组时使用
const upperNames = names.map(name => name.toUpperCase());
console.log(names); // 原数组保持不变
new Array(3).map(() => 'x'); // [ <3 empty items> ]
map不会处理空槽,结果仍然是一个空数组。
6. reduce
// 多维数组扁平化
const flatArr = [[1,2], [3,4]].reduce((acc, curr) =>
acc.concat(curr), []
);
// 替代filter+map组合
const result = data.reduce((arr, item) => {
if(item.valid) arr.push(item.value * 2);
return arr;
}, []);
新的状态基于上一个状态,负责在复杂情况下只产生唯一的值,是个纯函数。
三、静态方法解析
1. Array.of():解决构造函数歧义
Array.of(3); // [3]
new Array(3); // [empty × 3]
2. Array.from():类数组转换
// 生成字母表
const alphabet = Array.from({length: 26}, (_, i) =>
String.fromCharCode(65 + i)
);
// DOM节点转换
const divs = Array.from(document.querySelectorAll('div'));
四、稀疏数组的真相
通过原型链分析揭示稀疏数组本质:
const sparse = new Array(3);
console.log(sparse.hasOwnProperty(0)); // false
// 对比真实数组
const dense = [undefined, undefined];
console.log(dense.hasOwnProperty(0)); // true
核心结论:稀疏数组的"空位"实际上是未被分配的索引,V8引擎会优化存储方式。
总结
- 初始化:优先使用字面量
[]或Array.of() - 固定长度:必须使用
new Array(length).fill() - 遍历选择:
- 需要索引:
for...of+entries() - 仅需值:
for...of - 只读操作:
forEach - 复杂转换:
reduce
- 需要索引:
- 性能关键:避免在循环中创建新数组,谨慎使用链式操作
数组操作的核心在于理解其"类对象"的本质和V8引擎的优化机制。