前言
都2025年了,你还在用for循环遍历数组?是时候升级你的数组技能树了! 今天咱们就来深挖一下JavaScript数组的那些"不为人知"的秘密,保证让你大呼过瘾!
重新认识数组:不只是"盒子"那么简单
首先,我们要颠覆一个认知:JavaScript的数组并不是传统意义上的数组,它更像是一个"可遍历的对象"。这话怎么理解呢?
数组的两种初始化方式
js
// 方式一:new Array() - C++/Java程序员的最爱
const arr = new Array(5); // 指定了大小
console.log(arr); // [empty × 5]
// 方式二:数组字面量 - JavaScript原住民的选择
const arr2 = [1, 2, 3, 4, 5];
这里就有意思了,new Array(5)
创建的数组虽然有5个位置,但这些位置是"empty"状态,不是undefined
!
Empty vs Undefined:一个让人头大的区别
js
const arr = new Array(5);
console.log(arr[0]); // undefined(访问时返回undefined)
// 但是用for...in遍历时
for(let key in arr){
console.log(key, arr[key]); // 什么都不会打印!
}
为什么会这样?因为"empty"意味着这个索引键根本不存在!就像你家里有5个房间的钥匙位置,但钥匙还没造出来。
要解决这个问题,我们需要显式填充:
js
const arr2 = new Array(5).fill(undefined);
arr2[8] = undefined; // 动态扩容,JavaScript数组的超能力!
console.log(arr2); // [undefined, undefined, undefined, undefined, undefined, empty × 3, undefined]
// 现在for...in可以遍历有值的索引了
for(let key in arr2){
console.log(key, arr2[key]); // 0,1,2,3,4,8
}
Array的静态方法:造数组的艺术
Array.of() - 优雅的数组创建
js
// 不用再纠结new Array的奇怪行为了
console.log(Array.of(1,2,3)); // [1, 2, 3]
// 对比一下new Array的迷惑行为
console.log(new Array(3)); // [empty × 3]
console.log(new Array(1,2,3)); // [1, 2, 3]
Array.from() - 数组界的变形金刚
这个方法绝对是我的心头好,能把任何"类数组"转换成真正的数组:
js
// 一行代码生成字母表,是不是很酷?
console.log(Array.from(new Array(26), (val, index) => String.fromCharCode(index + 65)));
// ['A', 'B', 'C', ..., 'Z']
第二个参数是个回调函数,类似于map,可以在转换的同时进行计算。这比先创建再map要优雅多了!
🔍 对象原型链与数组的关系
这里要聊一个容易被忽略的点:JavaScript中一切皆对象,数组也不例外。
js
let obj = {
name: '迪迦',
}
let obj2 = {
skill: '迪拉修姆光流',
}
obj.__proto__ = obj2;
console.log(obj.skill); // '迪拉修姆光流'
// 遍历时会包含原型链上的属性
for(let key in obj){
console.log(obj[key]); // '迪迦' 和 '迪拉修姆光流'
}
// 判断是否为自有属性
console.log(obj.hasOwnProperty('name')); // true
console.log(obj.hasOwnProperty('skill')); // false
// 数组也是对象,也有这个问题
const arr = new Array(5);
console.log(arr.hasOwnProperty(0)); // false(因为是empty)
这就是为什么在遍历对象时,我们经常要用hasOwnProperty
来过滤原型链上的属性。
数组遍历:告别无脑for循环
forEach:最常用但有局限
js
const names = Array.of('Alice','Bob','Charlie','David','Eve');
names.forEach(name => {
if (name === 'Charlie'){
console.log('Charlie is here, stop...');
return; // 只能跳过当前迭代,不能中断整个循环
}
console.log('Processing:', name);
});
forEach
的问题是无法中断循环,return
只是跳过当前项,不是真正的break
。
for...of:现代JavaScript的优雅选择
js
const arr = [1,2,3];
// 简单遍历值
for(let item of arr){
console.log(item);
}
// 需要索引?用entries()
for(const item of arr.entries()){
console.log(item); // [0, 1], [1, 2], [2, 3]
}
// 解构赋值,同时拿到索引和值
for(const [index, item] of arr.entries()){
console.log(index, item);
}
for...of
比forEach
的优势在于:
- 可以使用
break
和continue
- 可读性更好
- 性能略优于
forEach
reduce:数组界的瑞士军刀
如果说JavaScript数组方法中只能选一个MVP,我会毫不犹豫选择reduce
:
js
// 基础用法:累加
console.log([1,2,3,4,5,6].reduce((prev, curr) => {
return prev + curr;
}, 0)); // 21
reduce
的精髓在于"消灭数组,留下一个"。它在处理复杂的状态转换时特别有用,新的状态总是基于上一个状态产生。
想象一下用reduce
实现:
- 数组去重
- 分组统计
- 数据格式转换
- 状态机...
简直就是函数式编程的利器!
实战建议
- 性能考虑:普通的计数for循环性能最好,但可读性差。在性能敏感的场景(如大数据处理)可以考虑
- 可读性优先 :大多数情况下,
for...of
、map
、filter
等方法的可读性远超传统for循环 - 避免混用:不要在同一个项目中混用多种遍历方式,保持代码风格一致
总结
JavaScript数组远比我们想象的复杂和强大:
- 它既有类似HashMap的key-value特性,又支持动态扩容
new Array()
和字面量[]
在某些场景下行为不同- 现代JavaScript提供了丰富的数组方法,告别无脑for循环
reduce
是处理复杂数据转换的神器
下次面试官问你数组相关问题时,记得展示这些深度理解,绝对能让你脱颖而出!