深入理解 JavaScript 数组:结构与遍历方式全解析
在前端开发中,数组是最基础也最常用的数据结构之一。然而,很多开发者对其底层原理和不同遍历方式的性能差异并不清楚。本文将结合实践笔记,深入剖析 JavaScript 中数组的内存结构、创建方式以及各种遍历方法的优劣。
一、JavaScript 数组的本质
1.1 数组是特殊的对象
在 JavaScript 中,数组本质上是一种特殊类型的对象。它拥有数字索引作为键(key),并自动维护一个 length 属性。正因为如此,我们甚至可以用 for...in 来遍历数组:
const arr = [1, 2, 3];
for (let key in arr) {
console.log(key, arr[key]); // '0' 1, '1' 2, '2' 3
}
1.2 内存结构:连续 vs 离散
- 数组:在理想情况下,JS 引擎会对密集型(dense)数组使用连续内存布局,使得通过索引访问的时间复杂度为 O(1) 。
- 链表:节点分散在堆内存中,每个节点包含数据和指向下一个节点的指针,访问需从头遍历,时间复杂度 O(n)。
小结:少量数据时,数组性能优于链表;但在频繁插入/删除场景下,链表更灵活。而 JS 的数组其实是“动态数组”,底层会自动扩容。(但是扩容开销较大)
二、如何正确创建数组?
2.1 字面量方式(推荐)
const arr = [1, 2, 3, 4, 5]; // 开箱即用,元素已知
- 优点:简洁、高效、语义清晰。
- 适用:元素内容明确的场景。
2.2 构造函数 + fill 初始化
const arr = new Array(10).fill(0); // 创建长度为10、值全为0的数组
- 注意:
new Array(10)会产生一个稀疏数组(empty slots) ,不能直接遍历!必须用.fill()填充。 - 适用:预先知道长度但元素初始值相同的情况。
三、JavaScript 数组的遍历方式
3.1 传统 for 循环(性能最优)
const arr = new Array(10).fill(0);
const len = arr.length;
for (let i = 0; i < len; i++) {
console.log(arr[i]);
}
- 优点:性能最好,CPU 友好,无函数调用开销。
- 缺点:代码略冗长,可读性一般。
- 适用:对性能敏感的循环(如大数据量处理、游戏循环等)。
3.2 for...of(ES6 推荐写法)
const arr = [1, 2, 3];
for (let item of arr) {
console.log(item);
}
- 优点:语义清晰,只遍历实际值,不会受原型污染影响。
- 支持
break/continue。 - 适用:日常开发首选,兼顾可读性与安全性。
当我们不需要使用到数组的下标索引时,我们应该尽可能的使用for...of来进行遍历数组
3.3 forEach(函数式风格)
arr.forEach((item, index) => {
console.log(item, index);
});
-
优点:函数式编程风格,代码简洁。
-
缺点:
- 无法中途 break 或 return 终止循环;
- 每次迭代都会调用函数,有额外的函数入栈/出栈开销,性能较差。
-
适用:简单遍历且无需中断的场景。
3.4 map(用于转换)
const newArr = arr.map(item => item + 1); // 返回新数组
- 用途:不是为了遍历,而是为了生成新数组。
- 不要用
map代替forEach做纯遍历(浪费内存和性能)。
3.5 for...in(不推荐用于数组)
for (let k in obj) { ... } // 对象可以
for (let i in arr) { ... } // 数组慎用!
-
问题:
- 遍历的是字符串形式的索引("0", "1"...);
- 可能遍历到非数字属性(如手动添加的
arr.foo = 'bar'); - 顺序不保证(尽管现代引擎通常按索引顺序)。
四、闭包陷阱:经典面试题解析
看这段代码:
for (var i = 0; i < 10; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
你可能会以为输出 0,1,2,...9,但实际上——它确会输出 10个10!
为什么?因为 var 声明不具有块级作用域, var是函数作用域,setTimeout 捕获的是全局变量中的 i 值。
- 如果把
var换成let,就会全部输出0~9,因为let支持块级作用域,每个let都会在词法环境中声明一次i,所有回调都具有自己的对应的i。
有关闭包的详细解释我们不在这里陈述,仅仅演示对数组进行循环遍历的各个情况
五、总结:如何选择遍历方式?
- for循环遍历:对性能有追求
- forEach:单次遍历不需要中断
- for..of:不需要索引下标进行操作(日常开发可以代替for循环)
- for..in:遍历对象,但不适用于遍历数组
- map:单纯只是需要遍历数组不要使用
结语
JavaScript 的数组看似简单,实则暗藏玄机。理解其底层结构、初始化方式和遍历机制,不仅能写出更高效的代码,还能避开常见的陷阱。记住:没有“最好”的方法,只有“最合适”的选择。
建议:在项目中统一规范遍历风格,优先使用
for...of,性能关键路径再考虑传统 for 循环。
本文基于个人学习笔记整理,欢迎交流指正。