深入理解 JavaScript 数组:结构与遍历方式全解析

64 阅读4分钟

深入理解 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!

image.png

为什么?因为 var 声明不具有块级作用域, var是函数作用域,setTimeout 捕获的是全局变量中的 i 值。

  • 如果把 var 换成 let,就会全部输出 0~9,因为 let 支持块级作用域,每个let都会在词法环境中声明一次i,所有回调都具有自己的对应的i。

image.png


有关闭包的详细解释我们不在这里陈述,仅仅演示对数组进行循环遍历的各个情况

五、总结:如何选择遍历方式?

  • for循环遍历:对性能有追求
  • forEach:单次遍历不需要中断
  • for..of:不需要索引下标进行操作(日常开发可以代替for循环)
  • for..in:遍历对象,但不适用于遍历数组
  • map:单纯只是需要遍历数组不要使用

结语

JavaScript 的数组看似简单,实则暗藏玄机。理解其底层结构、初始化方式和遍历机制,不仅能写出更高效的代码,还能避开常见的陷阱。记住:没有“最好”的方法,只有“最合适”的选择

建议:在项目中统一规范遍历风格,优先使用 for...of,性能关键路径再考虑传统 for 循环。


本文基于个人学习笔记整理,欢迎交流指正。