大家好,我是你们的老朋友FogLetter,今天我们来聊聊JavaScript中最基础却又最强大的数据结构之一------数组(Array)。很多同学觉得数组很简单,但实际上它藏着不少有趣的秘密和高级玩法。让我们一起来探索吧!
一、数组的本质:不只是简单的列表
1.1 数组是什么?
在JavaScript中,数组是一种特殊的对象,它是有序的数据集合。与普通对象不同,数组的元素是通过数字索引(从0开始)来访问的。
javascript
const fruits = ['苹果', '香蕉', '橙子'];
console.log(fruits[0]); // "苹果"
但有趣的是,JavaScript数组远比这复杂得多。它不像C++或Java那样是严格的内存连续分配结构,而是更灵活的动态结构。
1.2 数组的创建方式
创建数组有三种主要方式:
-
数组字面量(最常用):
javascriptconst arr = [1, 2, 3];
-
Array上的静态方法:
javascriptconst arr = new Array(5); // 创建长度为5的空数组 const arr2 = new Array(1, 2, 3); // [1,2,3]
-
Array上的静态方法:
javascriptconst arr = Array.of(1, 2, 3); // [1, 2, 3] const arr2 = Array.from(new Array(3),(val,index) => String.fromCharCode(index+65)); // ['A','B','C']
这里有个坑需要注意:当你使用new Array(5)
时,创建的并不是包含5个undefined
的数组,而是5个"空槽"(empty slots)。
javascript
const arr = new Array(5);
console.log(arr); // [empty × 5]
这种数组在使用for...in
循环时会有问题,因为空槽实际上是没有属性的:
javascript
for(let key in arr) {
console.log(key, arr[key]); // 什么都不会输出
}
解决办法是用fill
方法填充:
javascript
const arr = new Array(5).fill(undefined);
for(let key in arr) {
console.log(key, arr[key]); // 现在可以正常输出了
}
二、数组的高级创建方式
2.1 Array.of() 方法
Array.of()
方法解决了Array
构造函数参数行为不一致的问题:
javascript
Array.of(7); // [7]
Array.of(1, 2, 3); // [1, 2, 3]
new Array(7); // [empty × 7]
new Array(1, 2, 3); // [1, 2, 3]
2.2 Array.from() 方法
Array.from()
是更强大的数组创建方法,它可以将类数组对象或可迭代对象转换为真正的数组:
javascript
// 从字符串创建数组
Array.from('foo'); // ["f", "o", "o"]
// 从Set创建数组
Array.from(new Set(['a', 'b'])); // ['a', 'b']
// 从Map创建数组
Array.from(new Map([[1, 2], [3, 4]])); // [[1, 2], [3, 4]]
// 创建字母表
const alphabet = Array.from(new Array(26),
(val,index) => String.fromCharCode(index+65)
);
console.log(alphabet); // ["A", "B", "C", ..., "Z"]
三、数组遍历的多种方式
遍历数组是日常开发中最常见的操作之一,JavaScript提供了多种遍历方式:
3.1 传统的for循环(计数循环)
javascript
const arr = [1, 2, 3];
for(let i = 0; i < arr.length; i++) {
console.log(arr[i]);
}
优点:性能最好,可以随时中断循环(使用break
或return
)
缺点:代码略显冗长
类似的还有while循环
3.2 forEach方法
javascript
arr.forEach(item => {
console.log(item);
});
优点:简洁明了
缺点:无法中途跳出循环(即使使用return
也只是跳出当前回调)
3.3 for...of循环
ES6引入的for...of
循环让数组遍历更加优雅:
javascript
for(const item of arr) {
console.log(item);
}
如果需要同时获取索引和值,可以结合entries()
方法:
javascript
for(const [index, value] of arr.entries()) {
console.log(index, value);
}
3.4 其他迭代方法
JavaScript数组还提供了丰富的迭代方法:
map()
- 映射新数组filter()
- 过滤数组find()
- 查找元素some()
- 是否有元素满足条件every()
- 是否所有元素都满足条件
四、数组的reduce方法:函数式编程的利器
reduce()
方法是数组最强大的方法之一,它可以将数组"缩减"为单个值:
javascript
const sum = [1, 2, 3, 4].reduce((prev, curr) => prev + curr, 0);
console.log(sum); // 10
但reduce
的能力远不止于此,它可以实现很多复杂的功能:
4.1 数组转对象
javascript
const fruits = ['apple', 'banana', 'orange'];
const fruitMap = fruits.reduce((obj, fruit) => {
obj[fruit] = true;
return obj;
}, {});
console.log(fruitMap); // {apple: true, banana: true, orange: true}
4.2 实现分组
javascript
const people = [
{name: 'Alice', age: 21},
{name: 'Bob', age: 20},
{name: 'Charlie', age: 21}
];
const groupedByAge = people.reduce((acc, person) => {
const age = person.age;
if(!acc[age]) acc[age] = [];
acc[age].push(person);
return acc;
}, {});
console.log(groupedByAge);
// {
// 20: [{name: 'Bob', age: 20}],
// 21: [{name: 'Alice', age: 21}, {name: 'Charlie', age: 21}]
// }
五、数组的扩展运算符:简洁而强大
ES6的扩展运算符...
让数组操作变得更加简洁:
5.1 复制数组
javascript
const original = [1, 2, 3];
const copy = [...original];
5.2 合并数组
javascript
const arr1 = [1, 2];
const arr2 = [3, 4];
const combined = [...arr1, ...arr2]; // [1, 2, 3, 4]
5.3 函数参数传递
javascript
const numbers = [1, 2, 3];
console.log(Math.max(...numbers)); // 3
5.4 替代apply方法
javascript
// 旧方式
function foo(x, y, z) {}
const args = [1, 2, 3];
foo.apply(null, args);
// 新方式
foo(...args);
六、数组的性能优化技巧
虽然JavaScript数组非常灵活,但在处理大数据量时,性能问题不容忽视:
6.1 预分配数组大小
javascript
// 不推荐:动态扩容
const arr = [];
for(let i = 0; i < 1000000; i++) {
arr[i] = i;
}
// 推荐:预分配大小
const arr = new Array(1000000);
for(let i = 0; i < 1000000; i++) {
arr[i] = i;
}
6.2 避免在循环中修改数组长度
javascript
// 不推荐
for(let i = 0; i < arr.length; i++) {
if(someCondition) {
arr.splice(i, 1); // 修改了数组长度
i--; // 需要手动调整索引
}
}
// 推荐:从后往前遍历
for(let i = arr.length - 1; i >= 0; i--) {
if(someCondition) {
arr.splice(i, 1); // 不影响前面的索引
}
}
七、数组的"怪异"行为
JavaScript数组有一些看似奇怪的行为,了解它们可以避免踩坑:
7.1 稀疏数组
javascript
const arr = [1, , 3]; // 注意中间的空位
console.log(arr.length); // 3
console.log(arr[1]); // undefined
console.log(1 in arr); // false - 说明这个位置没有元素
7.2 length属性的特殊性
javascript
const arr = [1, 2, 3];
arr.length = 5;
console.log(arr); // [1, 2, 3, empty × 2]
arr.length = 2;
console.log(arr); // [1, 2] - 后面的元素被删除了
7.3 数组也是对象
javascript
const arr = [1, 2, 3];
arr.foo = 'bar';
console.log(arr.foo); // 'bar'
console.log(arr.length); // 3 - 非数字属性不影响length
八、现代JavaScript中的数组新特性
8.1 flat()和flatMap()
javascript
// 扁平化数组
const arr = [1, [2, [3]]];
console.log(arr.flat(2)); // [1, 2, 3]
// flatMap = map + flat(1)
const sentences = ["Hello world", "Good morning"];
const words = sentences.flatMap(s => s.split(' '));
console.log(words); // ["Hello", "world", "Good", "morning"]
8.2 Array.prototype.at()
javascript
const arr = [1, 2, 3];
console.log(arr.at(-1)); // 3 - 支持负索引
8.3 findLast()和findLastIndex()
javascript
const arr = [1, 2, 3, 2, 1];
console.log(arr.findLast(x => x === 2)); // 2 (最后一个2)
console.log(arr.findLastIndex(x => x === 2)); // 3
结语
JavaScript数组看似简单,实则内涵丰富。从基本的创建和遍历,到高级的reduce操作和性能优化,数组在JavaScript中扮演着至关重要的角色。希望这篇文章能帮助你更深入地理解和掌握JavaScript数组的各种特性和技巧。
记住,数组是JavaScript中最常用的数据结构之一,熟练掌握它的各种用法,能让你的代码更加简洁高效。如果你有任何问题或想法,欢迎在评论区留言讨论!