🔥 JavaScript 数组全攻略:从初始化到遍历,99% 的人都不知道的 let/var 陷阱!
💡 前言:数组是 JS 开发中最常用的数据结构,但你真的会用吗?本文带你彻底搞懂数组初始化、遍历方法,以及那个让无数人踩坑的
letvsvar闭包问题!
📚 一、数据结构基础:为什么要学数组?
在开始之前,先快速了解一下数据结构的世界:
📊 数据结构分类
├── 线性结构
│ ├── 数组 ✅ 连续内存,随机访问
│ ├── 栈 先进后出 FILO
│ ├── 队列 先进先出 FIFO
│ └── 链表 离散内存,指针连接
└── 非线性结构
└── 树(二叉树)层级关系
数组的优势:
- ✅ 开箱即用,无需额外实现
- ✅ 连续内存,访问速度快 O(1)
- ✅ API 丰富,JS 内置大量方法
🛠️ 二、数组的初始化方法(面试高频考点)
2.1 字面量创建(最常用)
// ✅ 推荐:元素确定时使用
const arr = [1, 3, 4];
console.log(arr); // [1, 3, 4]
// 内存结构:
// 栈内存:arr → 引用地址
// 堆内存:[1, 3, 4] 连续空间
2.2 Array 构造函数
// 创建空数组
const arr1 = new Array(); // []
// ⚠️ 坑点:单个数字参数创建的是"空位数组"
const arr3 = new Array(6);
console.log(arr3); // [empty × 6]
console.log(arr3.length); // 6
// ✅ 正确用法:创建并填充
const arr2 = (new Array(6)).fill(1);
console.log(arr2); // [1, 1, 1, 1, 1, 1]
const arr = (new Array(6)).fill(3);
console.log(arr); // [3, 3, 3, 3, 3, 3]
📊 初始化方法对比
| 方法 | 代码 | 结果 | 推荐度 |
|---|---|---|---|
| 字面量 | [1,2,3] | [1,2,3] | ⭐⭐⭐⭐⭐ |
| Array 空参 | new Array() | [] | ⭐⭐⭐ |
| Array 数字 | new Array(6) | [empty×6] | ⭐⭐(有坑) |
| Array+fill | new Array(6).fill(1) | [1,1,1,1,1,1] | ⭐⭐⭐⭐ |
🔄 三、数组遍历方法大比拼(核心考点)
3.1 for 循环(性能王者)
const arr = [1, 3, 4, 5, 6, 7];
// ✅ 优化版:缓存 length,避免重复查询
const len = arr.length; // RHS 查询只做一次
for (let i = 0; i < len; i++) {
console.log(arr[i]);
}
// ❌ 不推荐:每次循环都查询 length
for (let i = 0; i < arr.length; i++) {
console.log(arr[i]);
}
性能分析:
- ✅ CPU 友好:计数循环与 CPU 指令契合
- ✅ 性能最好:无函数调用开销
- ❌ 可读性差:
i=0; i<len; i++过于死板
3.2 forEach(简洁但有限制)
const arr = [1, 3, 4, 5, 6, 7];
arr.forEach((item, index) => {
console.log(item, index);
});
⚠️ 三大限制:
- ❌ 不能 break:无法提前退出循环
- ❌ 不能 return:return 只退出当前回调
- ❌ 性能较差:函数入栈出栈有开销
// ❌ 错误示范:break 无效
arr.forEach((item) => {
if (item === 4) break; // SyntaxError!
});
// ✅ 正确做法:用 for 或 for...of
for (let item of arr) {
if (item === 4) break; // 可以正常退出
}
3.3 map(遍历 + 转换)
const arr = [1, 2, 3, 4, 5, 6];
// forEach 只遍历,不返回
// map 遍历 + 加工,返回新数组
const newArr = arr.map(item => item + 1);
console.log(newArr); // [2, 3, 4, 5, 6, 7]
console.log(arr); // [1, 2, 3, 4, 5, 6] 原数组不变
使用场景:
- ✅ 数据转换(如 API 响应处理)
- ✅ 保持函数式编程的不可变性
- ❌ 不需要新数组时别用(浪费内存)
3.4 for...of(ES6 新宠)
const arr = [1, 2, 3, 4, 5, 6];
for (let item of arr) {
console.log(item);
}
优势:
- ✅ 可读性好:语义清晰
- ✅ 支持 break/continue
- ✅ 支持 async/await
- ⚠️ 性能略低于 for 循环
3.5 for...in(数组慎用!)
const arr = [1, 3, 4, 5, 6, 7, 8, 8];
for (let key in arr) {
// ⚠️ key 是字符串类型的下标!
console.log(key, typeof key); // "0" "string"
console.log(arr[key]);
}
⚠️ 为什么不建议用于数组:
- ❌
key是字符串,不是数字 - ❌ 会遍历原型链上的属性
- ❌ 顺序不保证(虽然现代引擎通常有序)
✅ 正确用途:遍历对象
const obj = {
name: 'llili',
age: 16,
hobbies: ["篮球", "足球", "跑步"]
};
for (let k in obj) {
console.log(k, obj[k]);
}
// 输出:name llili / age 16 / hobbies [...]
📊 遍历方法对比表
| 方法 | 可 break | 可 continue | 返回值 | 性能 | 推荐场景 |
|---|---|---|---|---|---|
for | ✅ | ✅ | 无 | ⭐⭐⭐⭐⭐ | 性能敏感 |
for...of | ✅ | ✅ | 无 | ⭐⭐⭐⭐ | 日常遍历 |
forEach | ❌ | ❌ | 无 | ⭐⭐⭐ | 简单遍历 |
map | ❌ | ❌ | 新数组 | ⭐⭐⭐ | 数据转换 |
for...in | ✅ | ✅ | 无 | ⭐⭐ | 遍历对象 |
💣 四、let vs var 在 for 循环中的区别(闭包经典题)
这是 JavaScript 面试最高频考点,没有之一!
4.1 var 版本(错误示范)
for (var i = 0; i < 10; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
// 输出:10 个 10 ❌
4.2 let 版本(正确示范)
for (let i = 0; i < 10; i++) {
setTimeout(function() {
console.log(i);
}, 1000);
}
// 输出:0 1 2 3 4 5 6 7 8 9 ✅
🔍 为什么结果不同?
┌─────────────────────────────────────────────────────────┐
│ var vs let 本质区别 │
├─────────────────────────────────────────────────────────┤
│ var i │
│ ├── 函数作用域 │
│ ├── 整个循环只有 1 个 i 变量 │
│ └── 所有 setTimeout 共享同一个 i │
│ │
│ let i │
│ ├── 块级作用域 │
│ ├── 每次循环创建新的 i 变量 │
│ └── 每个 setTimeout 捕获独立的 i │
└─────────────────────────────────────────────────────────┘
📖 闭包原理解析
// var 版本:所有回调共享同一个 i
var i; // 全局/函数作用域只有 1 个变量
for (i = 0; i < 10; i++) {
setTimeout(() => console.log(i), 1000);
}
// 循环结束 i = 10,所有回调都输出 10
// let 版本:每次循环创建新的 i
for (let i = 0; i < 10; i++) {
// 相当于每次都有新的块级作用域
setTimeout(() => console.log(i), 1000);
}
// 每个回调都有自己的 i,输出 0~9
🎯 三种解决方案
// 方案 1:使用 let(推荐)⭐⭐⭐⭐⭐
for (let i = 0; i < 10; i++) {
setTimeout(() => console.log(i), 1000);
}
// 方案 2:IIFE 立即执行函数 ⭐⭐⭐
for (var i = 0; i < 10; i++) {
(function(j) {
setTimeout(() => console.log(j), 1000);
})(i);
}
// 方案 3:setTimeout 传参 ⭐⭐⭐⭐
for (var i = 0; i < 10; i++) {
setTimeout(function(j) {
console.log(j);
}, 1000, i);
}
📝 五、最佳实践总结
✅ 数组初始化
// 元素已知 → 字面量
const arr = [1, 2, 3];
// 需要指定长度 → Array + fill
const arr = new Array(10).fill(0);
✅ 数组遍历
// 性能敏感 → for 循环
for (let i = 0, len = arr.length; i < len; i++) {}
// 日常遍历 → for...of
for (let item of arr) {}
// 数据转换 → map
const newArr = arr.map(item => item * 2);
// 遍历对象 → for...in
for (let key in obj) {}
✅ 循环变量声明
// 永远优先使用 let/const
for (let i = 0; i < arr.length; i++) {}
// 避免使用 var(除非有特殊原因)
🎯 六、面试考点速记
| 考点 | 关键知识点 |
|---|---|
| 数组初始化 | new Array(n) 创建空位数组 |
| 遍历性能 | for > for...of > forEach > map |
| forEach 限制 | 不能 break/continue/return |
| let vs var | 块级作用域 vs 函数作用域 |
| 闭包陷阱 | setTimeout + for 循环经典题 |
| for...in | 适合对象,不适合数组 |
💬 结语
数组是 JavaScript 最基础也最重要的数据结构,掌握它的初始化、遍历方法,以及 let/var 的区别,是成为合格前端工程师的必备技能。
记住这句话:
现代 JS 开发,优先使用
let/const,遍历数组首选for...of,性能敏感用for,数据转换用map!
觉得有用请点赞 👍 收藏 ⭐ 关注 📌 详见:《JavaScript 闭包深度解析:从入门到精通》
本文同步发布于掘金、知乎、CSDN 作者:前端技术分享 | 转载请注明出处