深入理解 JavaScript 数组:从创建到遍历的完整指南

在 JavaScript 开发中,数组是我们最常用、最基础的数据结构之一。它作为一种线性数据结构 ,以其连续内存 存储和开箱即用的特性,成为我们处理有序数据集合的首选工具。今天,就让我们一起来深入探索 JavaScript 数组的奥秘。

数组的创建:不只是字面量那么简单

基础创建方式

大多数情况下,当我们已经知道数组的所有元素时,会使用字面量方式创建数组:

javascript 复制代码
const arr = [1, 2, 3, 4, 5, 6]

这种方式的代码简洁明了,arr 在栈内存中存储引用地址,而实际的数据 [1,2,3,4,5,6] 则存储在堆内存中,同时申请了 6 个单位的连续空间。

动态数组的创建

但在实际开发中,我们经常遇到不知道具体元素的情况,特别是在动态规划等算法场景中。这时就需要使用构造函数来创建数组:

javascript 复制代码
// 创建一个长度确定且每个元素都有初始值的数组
const arr = (new Array(6)).fill(0)
console.log(arr) // [0, 0, 0, 0, 0, 0]

// 对比:如果不使用 fill
const arr2 = new Array(6)
console.log(arr2) // [empty × 6]

这里有一个重要区别:new Array(6).fill(0) 创建的是包含 6 个 0 的数组,而 new Array(6) 创建的则是包含 6 个空位的数组。在实际使用中,前者更为安全可靠。

数组的动态扩容

JavaScript 数组支持动态扩容,这既是优点也是需要注意的性能点。当我们创建一个长度为 10 的数组:

javascript 复制代码
const arr = (new Array(10)).fill(0)

如果后续需要存储第 11 个数据,JavaScript 引擎会执行以下操作:

  1. 系统并不会再给你1个单位 这样问一个给一个比较繁琐 系统会再给你10个单位 (此时你的数组空间为20个单位)
  2. 将原数组的 10 个元素"搬家"到新空间
  3. 将新元素放入第 11 个位置

这种动态扩容机制虽然方便,但在性能敏感的场景下需要特别注意。相比之下,链表在动态性方面表现更好,但对于数据量不大的情况,数组仍然是更优秀的选择。

数组遍历:多种方式及其适用场景

遍历数组是数组操作中最常见的需求,JavaScript 提供了多种遍历方式,各有特点和适用场景。

1. 传统的 for 循环

javascript 复制代码
const arr = (new Array(10)).fill(0)
const len = arr.length

for(let i = 0; i < len; i++) {
    console.log(arr[i])
}

为什么这里要使用 len 变量而不是直接使用 arr.length

这是因为在循环执行的每一次迭代中,都会执行条件判断。如果直接使用 arr.length,每次都会读取对象的 length 属性,发起一次 RHS 查询,这个开销比使用简单数据类型的 len 变量要大得多。

for 循环也被称为计数循环,它的执行方式与 CPU 的工作模式很契合,因此在所有遍历方法中性能最好。

2. forEach 方法

javascript 复制代码
const arr = [1, 2, 3, 4, 5, 6]
arr.forEach((item, index) => {
    console.log(item, index)
})

与 for 循环的主要区别:

  • 不能提前退出:forEach 无法使用 break 语句中断循环
  • 性能稍差:涉及函数的入栈出栈操作,性能比 for 循环差
javascript 复制代码
// 这样的代码会报错
arr.forEach((item, index) => {
    if(item === 3) {
        break // SyntaxError: Illegal break statement
    }
})

3. map 方法

javascript 复制代码
const arr = [1, 2, 3, 4, 5, 6]
const newArr = arr.map(item => item + 1)
console.log(newArr) // [2, 3, 4, 5, 6, 7]

map 方法不仅可以遍历数组,还可以对每个元素进行"加工",返回一个新的数组。这在函数式编程中非常有用。

4. for...of 循环

javascript 复制代码
const arr = [1, 2, 3, 4, 5, 6]
for(let item of arr) {
    console.log(item)
}

优点:

  • 可读性好:语法简洁,意图明确
  • 相比计数循环更直观 :传统的 for 循环 i = 0; i < ...; i++ 模式相对死板

当你不需要下标,只想单纯遍历数组时,for...of 是最佳选择。

5. for...in 循环

for...in 原本是设计用来迭代对象属性的:

javascript 复制代码
const obj = {
    name: '黄国文',
    age: 18,
    hobbies: ['篮球', '足球', '跑步']
}

for(let k in obj) {
    console.log(k, obj[k])
}

但它也可以用来遍历数组:

javascript 复制代码
const arr = [1, 2, 3, 4, 5, 6]
for(let key in arr) {
    console.log(key, arr[key]) // key 是索引下标
}

为什么 for...in 能遍历数组?

因为在 JavaScript 中,数组本质上也是对象,我们可以把数组看作下标为 key 的可迭代对象。但需要注意的是,for...in 会遍历所有可枚举属性,包括原型链上的属性,因此在数组遍历中并不常用。

闭包与循环:一个经典的面试题

让我们来看一个经典的 JavaScript 面试题,它很好地展示了 var 和 let 在循环中的不同表现:

使用 var 的情况

javascript 复制代码
for (var i = 0; i < 10; i++) {
    setTimeout(function() {
        console.log(i) // 打印 10 个 10
    }, 1000)
}

为什么会这样?

  1. for 循环是同步的,setTimeout 是异步的
  2. var 声明不支持块级作用域,它声明的变量在全局作用域的变量环境中
  3. 在整个全局作用域中共享同一个变量 i
  4. for 循环同步执行完成后,i 已经自增为 10
  5. setTimeout 的回调函数被放入任务队列,等待主线程空闲后执行
  6. 当异步任务执行时,它们都引用同一个 i,此时 i 的值为 10

使用 let 的情况

javascript 复制代码
for (let i = 0; i < 10; i++) {
    setTimeout(function() {
        console.log(i) // 打印 0 到 9
    }, 1000)
}

为什么 let 能解决问题?

  1. let 支持块级作用域
  2. 每次循环都会创建一个新的词法环境,每个 i 都是独立的
  3. 从 0 到 9,每个 i 都在各自的词法环境中
  4. 10 个定时器回调函数会分别对应词法环境中的 i
  5. 当异步任务执行时,每个函数都能找到对应的 i 值

实践建议

何时选择数组?

  • 数据量不大需要快速随机访问时,数组是首选
  • 需要缓存友好的连续内存存储时
  • 进行数值计算算法实现

遍历方法选择指南

  • 性能优先:传统的 for 循环
  • 代码简洁:for...of 循环
  • 需要转换数组:map 方法
  • 单纯遍历不需要中断:forEach 方法

性能优化技巧

  1. 避免在循环中重复计算长度:提前存储 array.length
  2. 根据需求选择合适的遍历方法

总结

JavaScript 数组作为一个强大而灵活的数据结构,在我们的日常开发中扮演着重要角色。从简单的创建和遍历,到理解其底层的内存管理和性能特性,掌握数组的方方面面对于写出高效、可靠的代码至关重要。

希望通过本文的讲解,你能对 JavaScript 数组有更深入的理解,在实际开发中能够根据具体场景选择最合适的数组操作方法。记住,好的开发者不仅要知道怎么用,更要知道为什么这样用。


注意:本文所有代码示例均基于现代 JavaScript 标准,在主流浏览器和 Node.js 环境中均可正常运行。

相关推荐
FogLetter2 小时前
设计模式奇幻漂流:从单例孤岛到工厂流水线
前端·设计模式
逛逛GitHub2 小时前
GitHub 开源 AI 好玩神器,自动记录你的一天。
前端·github
hollyhuang2 小时前
正则校验:校验只能输入数字且首位不能是0
前端
一室易安2 小时前
模仿elementUI 中Carousel 走马灯卡片模式 type=“card“ 的自定义轮播组件 图片之间有宽度
前端·javascript·elementui
在下胡三汉2 小时前
创建轻量级 3D 资产 - Three.js 中的 GLTF 案例
开发语言·javascript·3d
脸大是真的好~2 小时前
黑马JAVAWeb -Vue工程化 - Element Plus- 表格-分页条-中文语言包-对话框-Form表单
前端·javascript·vue.js
程序猿_极客2 小时前
【期末网页设计作业】HTML+CSS+JS 香港旅游网站设计与实现 (附源码)
javascript·css·html
一个小潘桃鸭2 小时前
记录:echarts tooltip内容过多时,会导致部分内容遮挡
前端
小满zs2 小时前
Next.js第四章(路由导航)
前端