🔥 JavaScript 数组全攻略:从初始化到遍历,99% 的人都不知道的 let/var 陷阱!

4 阅读6分钟

🔥 JavaScript 数组全攻略:从初始化到遍历,99% 的人都不知道的 let/var 陷阱!

💡 前言:数组是 JS 开发中最常用的数据结构,但你真的会用吗?本文带你彻底搞懂数组初始化、遍历方法,以及那个让无数人踩坑的 let vs var 闭包问题!


📚 一、数据结构基础:为什么要学数组?

在开始之前,先快速了解一下数据结构的世界:

📊 数据结构分类
├── 线性结构
│   ├── 数组 ✅ 连续内存,随机访问
│   ├── 栈   先进后出 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+fillnew 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);
});

⚠️ 三大限制

  1. 不能 break:无法提前退出循环
  2. 不能 return:return 只退出当前回调
  3. 性能较差:函数入栈出栈有开销
// ❌ 错误示范: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]);
}

⚠️ 为什么不建议用于数组

  1. key 是字符串,不是数字
  2. ❌ 会遍历原型链上的属性
  3. ❌ 顺序不保证(虽然现代引擎通常有序)

✅ 正确用途:遍历对象

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                                                  │
│  ├── 函数作用域                                          │
│  ├── 整个循环只有 1i 变量                             │
│  └── 所有 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 作者:前端技术分享 | 转载请注明出处