JavaScript Array:深入解析强大的有序集合
在 JavaScript 的世界里,数组(Array)无疑是最常用的数据结构之一。它们是构建几乎所有复杂应用的基础,无论你是处理用户列表、存储配置数据,还是构建复杂算法,数组都扮演着核心角色。理解并熟练掌握 JavaScript 数组是成为一名合格 JavaScript 开发者的必经之路。
本文将带你深入探索 JavaScript 数组的各个方面,从最基本的定义和创建,到各种强大的内置方法,再到现代的迭代方式和高级技巧。我们将力求详尽,辅以丰富的代码示例,帮助你透彻理解这一重要概念。
什么是 JavaScript 数组?
简单来说,JavaScript 数组是一种有序的集合,用于存储一系列元素。这些元素可以是任何类型的数据,包括数字、字符串、布尔值、对象、甚至是其他的数组。
与一些其他编程语言不同,JavaScript 数组具有以下关键特性:
- 动态大小: 数组的大小是动态的,你可以随时添加或删除元素,数组会自动调整其长度。你不需要在创建时指定固定大小。
- 异构性: 同一个数组可以包含不同类型的元素。例如,一个数组可以同时包含数字、字符串和一个对象。
- 零索引: 数组的第一个元素的索引是
0
,第二个元素的索引是1
,以此类推。最后一个元素的索引是length - 1
。 - 是对象: 在 JavaScript 中,数组实际上是一种特殊类型的对象。它们继承了
Array.prototype
上的方法,并且拥有一些对象没有的特殊行为(比如通过数字索引访问元素,以及自动维护length
属性)。
创建数组
创建 JavaScript 数组有几种常见的方式,其中数组字面量是最常用和推荐的方式。
1. 数组字面量 (Array Literal)
这是创建数组最简洁和直观的方法,使用方括号 []
包裹元素,元素之间用逗号 ,
分隔。
“`javascript
// 创建一个空数组
const emptyArray = [];
console.log(emptyArray); // 输出: []
// 创建一个包含元素的数组
const fruits = [“Apple”, “Banana”, “Cherry”];
console.log(fruits); // 输出: [“Apple”, “Banana”, “Cherry”]
// 包含不同类型元素的数组
const mixedArray = [1, “hello”, true, { name: “Alice” }, [1, 2]];
console.log(mixedArray); // 输出: [ 1, ‘hello’, true, { name: ‘Alice’ }, [ 1, 2 ] ]
“`
2. new Array()
构造函数
虽然可以使用 new Array()
构造函数创建数组,但这种方式不如数组字面量常用,并且在使用时需要注意一些细节。
- 无参数: 创建一个空数组,等同于
[]
。
javascript
const array1 = new Array();
console.log(array1); // 输出: [] - 一个数字参数: 创建一个指定长度的空槽数组(sparse array)。注意,这里的数字 不是 数组的第一个元素,而是数组的长度。这些槽没有实际的值(不是
undefined
),在某些方法中表现不同。
javascript
const array2 = new Array(5);
console.log(array2); // 输出: [ <5 empty items> ]
console.log(array2[0]); // 输出: undefined (访问空槽时返回 undefined)
console.log(array2.length); // 输出: 5 - 多个参数: 创建一个包含这些参数作为元素的数组,等同于使用数组字面量。
javascript
const array3 = new Array("Red", "Green", "Blue");
console.log(array3); // 输出: ["Red", "Green", "Blue"]
最佳实践: 除非你有特定的理由(例如需要创建一个指定长度的空槽数组),否则总是优先使用数组字面量 []
来创建数组。它更简洁、更安全(避免了单数字参数的歧义)且性能通常更好。
访问和修改数组元素
通过元素的索引(下标)来访问和修改数组中的元素。索引从 0
开始。
“`javascript
const colors = [“Red”, “Green”, “Blue”, “Yellow”];
// 访问元素
console.log(colors[0]); // 输出: “Red” (第一个元素)
console.log(colors[2]); // 输出: “Blue” (第三个元素)
console.log(colors[colors.length – 1]); // 输出: “Yellow” (最后一个元素)
console.log(colors[10]); // 输出: undefined (访问不存在的索引)
// 修改元素
colors[1] = “Forest Green”;
console.log(colors); // 输出: [“Red”, “Forest Green”, “Blue”, “Yellow”]
// 添加新元素 (如果索引超出当前长度,会创建空槽)
colors[4] = “Purple”;
console.log(colors); // 输出: [“Red”, “Forest Green”, “Blue”, “Yellow”, “Purple”]
console.log(colors.length); // 输出: 5
colors[10] = “Orange”; // 会在索引 5-9 之间创建空槽
console.log(colors); // 输出: [ ‘Red’, ‘Forest Green’, ‘Blue’, ‘Yellow’, ‘Purple’, <5 empty items>, ‘Orange’ ]
console.log(colors.length); // 输出: 11
“`
通过索引直接修改或添加元素非常灵活,但也可能意外地创建空槽数组,这在处理时需要注意。
length
属性
每个数组都有一个 length
属性,它表示数组中元素的数量(或者说是数组中最大索引加一)。length
是一个可读可写的属性。
“`javascript
const numbers = [1, 2, 3, 4, 5];
console.log(numbers.length); // 输出: 5
// 修改 length 可以截断数组
numbers.length = 3;
console.log(numbers); // 输出: [1, 2, 3]
console.log(numbers.length); // 输出: 3
// 修改 length 可以扩展数组 (创建空槽)
numbers.length = 7;
console.log(numbers); // 输出: [ 1, 2, 3, <4 empty items> ]
console.log(numbers.length); // 输出: 7
“`
修改 length
属性是一种快速截断或扩展数组的方式,但扩展时会创建空槽,使用时需谨慎。
常用数组方法:添加和删除元素
JavaScript 数组提供了丰富的内置方法来方便地操作数组。这些方法可以分为几类:修改原数组的方法(Mutator Methods)和返回新数组/值而不修改原数组的方法(Accessor Methods)。
首先看一些最基础的修改原数组的方法:
1. push()
在数组末尾添加一个或多个元素,并返回新数组的长度。
javascript
const fruits = ["Apple", "Banana"];
const newLength = fruits.push("Cherry", "Date");
console.log(fruits); // 输出: ["Apple", "Banana", "Cherry", "Date"]
console.log(newLength); // 输出: 4
2. pop()
删除数组末尾的最后一个元素,并返回被删除的元素。如果数组为空,则返回 undefined
。
“`javascript
const fruits = [“Apple”, “Banana”, “Cherry”];
const lastFruit = fruits.pop();
console.log(fruits); // 输出: [“Apple”, “Banana”]
console.log(lastFruit); // 输出: “Cherry”
const emptyArr = [];
console.log(emptyArr.pop()); // 输出: undefined
console.log(emptyArr); // 输出: []
“`
3. unshift()
在数组开头添加一个或多个元素,并返回新数组的长度。
javascript
const fruits = ["Banana", "Cherry"];
const newLength = fruits.unshift("Apple", "Date");
console.log(fruits); // 输出: ["Apple", "Date", "Banana", "Cherry"]
console.log(newLength); // 输出: 4
注意: unshift()
操作可能比 push()
慢,因为它需要重新索引所有现有元素。对于非常大的数组,频繁使用 unshift()
可能会影响性能。
4. shift()
删除数组开头的第一个元素,并返回被删除的元素。如果数组为空,则返回 undefined
。
“`javascript
const fruits = [“Apple”, “Banana”, “Cherry”];
const firstFruit = fruits.shift();
console.log(fruits); // 输出: [“Banana”, “Cherry”]
console.log(firstFruit); // 输出: “Apple”
const emptyArr = [];
console.log(emptyArr.shift()); // 输出: undefined
console.log(emptyArr); // 输出: []
“`
注意: shift()
操作可能比 pop()
慢,因为它需要重新索引所有剩余元素。对于非常大的数组,频繁使用 shift()
可能会影响性能。
常用数组方法:更灵活的修改 (splice
)
splice()
是一个非常强大的方法,可以实现在任意位置添加、删除或替换数组元素。它会修改原数组,并返回一个包含被删除元素的数组(如果没有删除元素,则返回空数组)。
它的语法是:array.splice(start, deleteCount, item1, item2, ...)
start
:必需。开始修改的索引位置。如果为负值,则从数组末尾开始计算(例如 -1 表示最后一个元素)。deleteCount
:可选。要删除的元素数量。如果为0
,则不删除元素。如果省略或大于从start
到末尾的元素数量,则删除从start
到末尾的所有元素。item1, item2, ...
:可选。要添加到数组中的元素,从start
位置开始插入。
“`javascript
const months = [“Jan”, “Feb”, “Mar”, “Apr”, “May”];
// 1. 删除元素 (从索引 2 开始,删除 2 个元素)
const removedMonths = months.splice(2, 2);
console.log(months); // 输出: [“Jan”, “Feb”, “May”]
console.log(removedMonths); // 输出: [“Mar”, “Apr”]
// 2. 添加元素 (从索引 1 开始,不删除,添加两个元素)
months.splice(1, 0, “Feb”, “Mar”);
console.log(months); // 输出: [“Jan”, “Feb”, “Mar”, “May”] (恢复了原样,巧合)
// 3. 替换元素 (从索引 2 开始,删除 1 个,添加两个新的)
const replacedElement = months.splice(2, 1, “April”, “June”);
console.log(months); // 输出: [“Jan”, “Feb”, “April”, “June”, “May”]
console.log(replacedElement); // 输出: [“Mar”]
// 4. 从末尾开始删除 (从倒数第 2 个开始,删除 2 个)
const fruits = [“Apple”, “Banana”, “Cherry”, “Date”, “Elderberry”];
const lastTwo = fruits.splice(-2, 2);
console.log(fruits); // 输出: [“Apple”, “Banana”, “Cherry”]
console.log(lastTwo); // 输出: [“Date”, “Elderberry”]
“`
splice
的灵活性使其成为修改数组内容的利器。
常用数组方法:创建新数组 (slice
, concat
)
这些方法不会修改原数组,而是返回一个新的数组或值。
1. slice()
从现有数组中截取一部分元素创建一个新数组。它接受两个可选参数:start
和 end
(不包含 end
索引处的元素)。
语法:array.slice(start, end)
start
:可选。开始截取的索引位置。默认是 0。如果为负值,则从数组末尾开始计算。end
:可选。结束截取的索引位置(不包含该位置的元素)。默认是数组的长度。如果为负值,则从数组末尾开始计算。
“`javascript
const colors = [“Red”, “Green”, “Blue”, “Yellow”, “Purple”];
// 截取从索引 1 到索引 3 (不包含 3)
const slicedColors1 = colors.slice(1, 3);
console.log(slicedColors1); // 输出: [“Green”, “Blue”]
console.log(colors); // 输出: [“Red”, “Green”, “Blue”, “Yellow”, “Purple”] (原数组未改变)
// 截取从索引 2 到末尾
const slicedColors2 = colors.slice(2);
console.log(slicedColors2); // 输出: [“Blue”, “Yellow”, “Purple”]
// 截取最后两个元素 (从倒数第 2 个到末尾)
const slicedColors3 = colors.slice(-2);
console.log(slicedColors3); // 输出: [“Yellow”, “Purple”]
// 截取整个数组 (浅拷贝)
const colorsCopy = colors.slice();
console.log(colorsCopy); // 输出: [“Red”, “Green”, “Blue”, “Yellow”, “Purple”]
console.log(colorsCopy === colors); // 输出: false (是新数组)
“`
slice()
常用于创建数组的浅拷贝或获取数组的子集。
2. concat()
用于合并两个或多个数组,返回一个新数组,而不修改现有数组。
语法:array1.concat(array2, array3, ...)
“`javascript
const arr1 = [1, 2];
const arr2 = [3, 4];
const arr3 = [5, 6];
const combinedArray = arr1.concat(arr2, arr3);
console.log(combinedArray); // 输出: [1, 2, 3, 4, 5, 6]
console.log(arr1); // 输出: [1, 2] (原数组未改变)
“`
concat()
也可以用于将非数组值添加到数组末尾,虽然不如 push
直接。
javascript
const arr = [1, 2];
const newArr = arr.concat(3, 4, [5, 6]);
console.log(newArr); // 输出: [1, 2, 3, 4, 5, 6]
常用数组方法:查找和搜索
1. indexOf()
返回指定元素在数组中第一次出现的索引。如果元素不存在,则返回 -1
。可以指定一个可选的开始搜索的索引。
语法:array.indexOf(searchElement, fromIndex)
“`javascript
const colors = [“Red”, “Green”, “Blue”, “Green”, “Yellow”];
console.log(colors.indexOf(“Green”)); // 输出: 1
console.log(colors.indexOf(“Green”, 2)); // 输出: 3 (从索引 2 开始搜索)
console.log(colors.indexOf(“Purple”)); // 输出: -1
“`
2. lastIndexOf()
返回指定元素在数组中最后一次出现的索引。如果元素不存在,则返回 -1
。可以指定一个可选的开始搜索的索引(从后向前搜索)。
语法:array.lastIndexOf(searchElement, fromIndex)
“`javascript
const colors = [“Red”, “Green”, “Blue”, “Green”, “Yellow”];
console.log(colors.lastIndexOf(“Green”)); // 输出: 3
console.log(colors.lastIndexOf(“Green”, 2)); // 输出: 1 (从索引 2 开始从后向前搜索)
console.log(colors.lastIndexOf(“Purple”)); // 输出: -1
“`
3. includes()
检查数组是否包含指定的元素,返回一个布尔值 (true
或 false
)。它是 ES6 中新增的方法,比 indexOf !== -1
更直观。
语法:array.includes(searchElement, fromIndex)
“`javascript
const fruits = [“Apple”, “Banana”, “Cherry”];
console.log(fruits.includes(“Banana”)); // 输出: true
console.log(fruits.includes(“Date”)); // 输出: false
console.log(fruits.includes(“Apple”, 1)); // 输出: false (从索引 1 开始搜索)
“`
常用数组方法:排序和反转 (sort
, reverse
)
1. sort()
对数组的元素进行原地(in-place)排序,并返回对原数组的引用。默认的排序顺序是按照字符串的 Unicode 码位升序。
语法:array.sort(compareFunction)
compareFunction
:可选。一个函数,用于指定按某种顺序进行排序。如果省略,元素按照字符串 Unicode 码位比较。
如果不提供 compareFunction
,对于数字排序会出问题:
javascript
const numbers = [1, 10, 2, 20, 5];
numbers.sort();
console.log(numbers); // 输出: [1, 10, 2, 20, 5] -> ["1", "10", "2", "20", "5"] -> ["1", "10", "2", "20", "5"] // 字符串排序
// 期望: [1, 2, 5, 10, 20]
因此,对于数字或自定义对象的排序,必须提供一个比较函数。比较函数接受两个参数 a
和 b
,表示正在比较的两个元素:
- 如果返回值 小于 0,表示
a
排在b
前面。 - 如果返回值 等于 0,表示
a
和b
的相对位置不变。 - 如果返回值 大于 0,表示
b
排在a
前面。
数字升序排序: (a, b) => a - b
数字降序排序: (a, b) => b - a
“`javascript
const numbers = [1, 10, 2, 20, 5];
// 数字升序排序
numbers.sort((a, b) => a – b);
console.log(numbers); // 输出: [1, 2, 5, 10, 20]
// 数字降序排序
numbers.sort((a, b) => b – a);
console.log(numbers); // 输出: [20, 10, 5, 2, 1]
// 字符串按字母序升序 (默认行为)
const fruits = [“Cherry”, “Apple”, “Banana”];
fruits.sort();
console.log(fruits); // 输出: [“Apple”, “Banana”, “Cherry”]
“`
2. reverse()
颠倒数组中元素的顺序,并返回对原数组的引用。它会修改原数组。
javascript
const numbers = [1, 2, 3, 4, 5];
numbers.reverse();
console.log(numbers); // 输出: [5, 4, 3, 2, 1]
遍历数组:多种方式
遍历数组是常见的操作。JavaScript 提供了多种遍历数组的方式,各有特点。
1. for
循环
最传统的遍历方式,通过索引访问元素。
javascript
const fruits = ["Apple", "Banana", "Cherry"];
for (let i = 0; i < fruits.length; i++) {
console.log(`Index ${i}: ${fruits[i]}`);
}
// 输出:
// Index 0: Apple
// Index 1: Banana
// Index 2: Cherry
优点:控制灵活,可以随时 break
或 continue
。
缺点:相对啰嗦,需要手动管理索引。
2. for...of
循环 (ES6)
直接遍历数组的元素值,无需关心索引。
javascript
const fruits = ["Apple", "Banana", "Cherry"];
for (const fruit of fruits) {
console.log(fruit);
}
// 输出:
// Apple
// Banana
// Cherry
优点:简洁易读,直接获取元素值。
缺点:不能直接获取索引,无法使用 continue
或 break
(尽管可以通过 try...catch
模拟跳出)。
3. forEach()
方法
对数组的每个元素执行一次提供的函数。它不返回值,常用于执行副作用操作(如打印)。
语法:array.forEach(callback(currentValue, index, array), thisArg)
callback
:为数组中每个元素执行的函数。currentValue
:当前正在处理的元素。index
:当前元素的索引。array
:调用forEach
的数组本身。
thisArg
:可选。执行callback
时使用的this
值。
javascript
const fruits = ["Apple", "Banana", "Cherry"];
fruits.forEach((fruit, index) => {
console.log(`Element at index ${index} is ${fruit}`);
});
// 输出:
// Element at index 0 is Apple
// Element at index 1 is Banana
// Element at index 2 is Cherry
优点:语法简洁,意图清晰,可以直接访问元素和索引。
缺点:不能通过 break
或 continue
中断或跳过循环。如果需要提前终止,考虑使用 for
循环、for...of
循环或 some
/every
方法。
4. for...in
循环 (不推荐用于数组)
遍历数组的可枚举属性。对于数组,这会遍历索引(字符串类型)。
javascript
const fruits = ["Apple", "Banana", "Cherry"];
for (const index in fruits) {
console.log(`Index ${index} has value ${fruits[index]}`);
}
// 输出:
// Index 0 has value Apple
// Index 1 has value Banana
// Index 2 has value Cherry
看起来好像可以,但强烈不推荐使用 for...in
遍历数组。原因如下:
* 它遍历的是属性名(索引是字符串 “0”, “1”, “2”),而不是数值索引。
* 它还会遍历原型链上的属性(如果有人修改了 Array.prototype
),这可能导致意外的结果。
* 遍历顺序不能保证是按照索引的数字顺序。
总结: 对于遍历数组,推荐使用 for
循环(需要控制流程或关心索引)或 for...of
/ forEach
(更现代、简洁)。避免使用 for...in
。
高阶函数:强大的迭代方法
JavaScript 数组原型上提供了许多高阶函数,它们接受回调函数作为参数,用于对数组进行转换、过滤、聚合等操作。这些方法是函数式编程风格的体现,非常强大和常用。它们通常不修改原数组,而是返回新数组或值。
1. map()
创建一个新数组,其结果是该数组中的每个元素都调用一次提供的回调函数后返回的结果。用于元素的转换。
语法:array.map(callback(currentValue, index, array), thisArg)
javascript
const numbers = [1, 2, 3, 4];
const squaredNumbers = numbers.map(number => number * number);
console.log(squaredNumbers); // 输出: [1, 4, 9, 16]
console.log(numbers); // 输出: [1, 2, 3, 4] (原数组未改变)
常见用途:从对象数组中提取特定属性,转换数据格式。
2. filter()
创建一个新数组,其包含通过测试的所有元素(回调函数返回 true
的元素)。用于元素的筛选。
语法:array.filter(callback(currentValue, index, array), thisArg)
javascript
const numbers = [1, 2, 3, 4, 5, 6];
const evenNumbers = numbers.filter(number => number % 2 === 0);
console.log(evenNumbers); // 输出: [2, 4, 6]
console.log(numbers); // 输出: [1, 2, 3, 4, 5, 6] (原数组未改变)
常见用途:从列表中移除不符合条件的项,根据条件筛选数据。
3. reduce()
对数组中的所有元素执行一个由你提供的 reducer
函数,将其结果汇总为单个返回值。用于元素的聚合或累加。
语法:array.reduce(callback(accumulator, currentValue, index, array), initialValue)
callback
:对数组中每个元素执行的函数。accumulator
:累加器,它累积回调的返回值。它是上一次调用回调时返回的累积值,或initialValue
。currentValue
:数组中当前正在处理的元素。index
:当前元素的索引。array
:调用reduce
的数组本身。
initialValue
:可选。作为第一次调用callback
函数时的accumulator
值。
“`javascript
const numbers = [1, 2, 3, 4];
// 计算总和
const sum = numbers.reduce((accumulator, currentValue) => accumulator + currentValue, 0);
console.log(sum); // 输出: 10 (0 + 1 + 2 + 3 + 4)
// 如果没有 initialValue,累加器初始值为数组第一个元素,currentValue 从第二个开始
const sumWithoutInitial = numbers.reduce((accumulator, currentValue) => accumulator + currentValue);
console.log(sumWithoutInitial); // 输出: 10 (1 + 2 + 3 + 4)
// 展平数组 (将二维数组变成一维数组)
const二维数组 = [[1, 2], [3, 4], [5, 6]];
const flattenedArray = 二维数组.reduce((accumulator, currentValue) => accumulator.concat(currentValue), []);
console.log(flattenedArray); // 输出: [1, 2, 3, 4, 5, 6]
// 统计元素出现的次数
const names = [‘Alice’, ‘Bob’, ‘Tiff’, ‘Alice’, ‘Bob’];
const nameCounts = names.reduce((allNames, name) => {
const currentCount = allNames[name] ?? 0;
return {
…allNames,
[name]: currentCount + 1,
};
}, {});
console.log(nameCounts); // 输出: { Alice: 2, Bob: 2, Tiff: 1 }
``
reduce` 是一个非常灵活的方法,可以实现很多不同的聚合操作,包括求和、求平均、统计、构建对象等。
4. some()
测试数组中是否至少有一个元素通过了由提供的函数实现的测试。返回一个布尔值。一旦找到满足条件的元素,立即停止遍历。
语法:array.some(callback(currentValue, index, array), thisArg)
“`javascript
const numbers = [1, 3, 5, 7, 9];
const hasEven = numbers.some(number => number % 2 === 0);
console.log(hasEven); // 输出: false
const moreNumbers = [1, 3, 5, 8, 9];
const hasEvenNow = moreNumbers.some(number => number % 2 === 0);
console.log(hasEvenNow); // 输出: true (在检查到 8 时就返回 true 并停止)
“`
常见用途:检查数组中是否存在某个特定条件的元素。
5. every()
测试数组的所有元素是否都通过了由提供的函数实现的测试。返回一个布尔值。一旦找到不满足条件的元素,立即停止遍历。
语法:array.every(callback(currentValue, index, array), thisArg)
“`javascript
const numbers = [2, 4, 6, 8, 10];
const allEven = numbers.every(number => number % 2 === 0);
console.log(allEven); // 输出: true
const mixedNumbers = [2, 4, 5, 8, 10];
const allEvenNow = mixedNumbers.every(number => number % 2 === 0);
console.log(allEvenNow); // 输出: false (在检查到 5 时就返回 false 并停止)
“`
常见用途:检查数组中的所有元素是否都符合某个条件。
6. find()
(ES6)
返回数组中满足提供的测试函数的第一个元素的值。否则返回 undefined
。
语法:array.find(callback(currentValue, index, array), thisArg)
“`javascript
const users = [
{ id: 1, name: ‘Alice’ },
{ id: 2, name: ‘Bob’ },
{ id: 3, name: ‘Charlie’ },
{ id: 2, name: ‘Bob Jr.’ } // 查找只会返回第一个
];
const userBob = users.find(user => user.id === 2);
console.log(userBob); // 输出: { id: 2, name: ‘Bob’ }
const userDavid = users.find(user => user.id === 4);
console.log(userDavid); // 输出: undefined
“`
常见用途:查找具有特定属性值的对象或符合特定条件的第一个元素。
7. findIndex()
(ES6)
返回数组中满足提供的测试函数的第一个元素的索引。否则返回 -1
。
语法:array.findIndex(callback(currentValue, index, array), thisArg)
“`javascript
const users = [
{ id: 1, name: ‘Alice’ },
{ id: 2, name: ‘Bob’ },
{ id: 3, name: ‘Charlie’ },
{ id: 2, name: ‘Bob Jr.’ }
];
const bobIndex = users.findIndex(user => user.id === 2);
console.log(bobIndex); // 输出: 1
const davidIndex = users.findIndex(user => user.id === 4);
console.log(davidIndex); // 输出: -1
“`
常见用途:查找符合特定条件的元素的索引。
8. flatMap()
(ES2019)
结合了 map
和 flat
的功能。首先使用映射函数对每个元素调用 map
,然后将结果展平一层。
语法:array.flatMap(callback(currentValue, index, array), thisArg)
“`javascript
const sentences = [“This is a sentence”, “Another one”];
// 使用 map 后再 flat
const wordsMapFlat = sentences.map(s => s.split(” “)).flat();
console.log(wordsMapFlat); // 输出: [“This”, “is”, “a”, “sentence”, “Another”, “one”]
// 使用 flatMap
const wordsFlatMap = sentences.flatMap(s => s.split(” “));
console.log(wordsFlatMap); // 输出: [“This”, “is”, “a”, “sentence”, “Another”, “one”]
// 过滤和映射结合
const numbers = [1, 2, 3, 4, 5];
// 偶数保留,奇数生成空数组 (相当于过滤掉)
const processed = numbers.flatMap(n => (n % 2 === 0 ? [n * 2] : []));
console.log(processed); // 输出: [4, 8]
“`
常见用途:将每个元素映射到一个数组,然后将所有结果数组合并成一个新数组,常用于处理嵌套结构或结合映射和过滤。
其他有用的数组方法
join()
: 将数组的所有元素连接成一个字符串。
javascript
const fruits = ["Apple", "Banana", "Cherry"];
const fruitString = fruits.join(" - ");
console.log(fruitString); // 输出: "Apple - Banana - Cherry"toString()
: 将数组转换为字符串,元素之间用逗号分隔。
javascript
const numbers = [1, 2, 3];
console.log(numbers.toString()); // 输出: "1,2,3"fill()
(ES6): 用静态值填充数组中从开始索引到结束索引(不包含)的部分。
javascript
const arr = [1, 2, 3, 4, 5];
arr.fill(0, 2, 4); // 从索引 2 到 4 (不包含) 用 0 填充
console.log(arr); // 输出: [1, 2, 0, 0, 5]copyWithin()
(ES6): 在数组内部,将部分元素浅复制到数组中的另一个位置,并返回修改后的数组。
javascript
const arr = [1, 2, 3, 4, 5];
// 将从索引 3 开始的元素复制到索引 0 开始的位置
arr.copyWithin(0, 3); // 从索引 3 (即 4) 开始复制到索引 0
console.log(arr); // 输出: [4, 5, 3, 4, 5]Array.from()
(ES6): 从一个类数组对象或可迭代对象创建一个新的,浅拷贝的 Array 实例。
javascript
// 从字符串创建数组
console.log(Array.from("hello")); // 输出: ["h", "e", "l", "l", "o"]
// 从 NodeList (类数组对象) 创建数组
// const divs = document.querySelectorAll('div');
// const divArray = Array.from(divs);
// 使用映射函数
console.log(Array.from([1, 2, 3], x => x * 2)); // 输出: [2, 4, 6]Array.of()
(ES6): 创建一个具有可变数量参数的新Array
实例,无论参数的数量或类型如何。解决了new Array(number)
的单参数歧义问题。
javascript
console.log(Array.of(1, 2, 3)); // 输出: [1, 2, 3]
console.log(Array.of(7)); // 输出: [7]
console.log(Array.of(undefined)); // 输出: [undefined]
检查是否为数组 (Array.isArray()
)
typeof
操作符对于数组会返回 "object"
,因为数组在 JavaScript 中是一种特殊的对象。为了准确判断一个值是否为数组,应该使用静态方法 Array.isArray()
。
“`javascript
const myArr = [1, 2, 3];
const myObj = { a: 1, b: 2 };
const myStr = “hello”;
console.log(typeof myArr); // 输出: “object”
console.log(Array.isArray(myArr)); // 输出: true
console.log(typeof myObj); // 输出: “object”
console.log(Array.isArray(myObj)); // 输出: false
console.log(Array.isArray(myStr)); // 输出: false
“`
类数组对象 (Array-like Objects)
有些对象看起来像数组,它们有 length
属性并通过数字索引访问元素,但它们不是真正的数组实例,不具备 Array.prototype
上的方法(如 forEach
, map
, push
等)。常见的类数组对象有函数内部的 arguments
对象和 DOM 集合(如 document.querySelectorAll
返回的 NodeList
)。
“`javascript
function printArgs() {
console.log(typeof arguments); // 输出: “object”
console.log(arguments.length); // 可以访问 length
console.log(arguments[0]); // 可以通过索引访问
// arguments.forEach(arg => console.log(arg)); // 错误!arguments 没有 forEach 方法
}
printArgs(1, 2, 3);
“`
将类数组对象转换为真正的数组,以便使用数组方法:
- 使用
Array.from()
(推荐):
javascript
function printArgsArray() {
const argsArray = Array.from(arguments);
argsArray.forEach(arg => console.log(arg)); // 现在可以使用 forEach
}
printArgsArray(1, 2, 3); -
使用 Spread syntax (
...
) (ES6):
“`javascript
function printArgsSpread() {
const argsArray = […arguments];
argsArray.forEach(arg => console.log(arg));
}
printArgsSpread(1, 2, 3);// 对于 NodeList
// const divs = document.querySelectorAll(‘div’);
// const divArray = […divs];
// divArray.forEach(div => console.log(div));
3. 使用 `slice()` 方法 (较旧方法):
javascript
function printArgsSlice() {
const argsArray = Array.prototype.slice.call(arguments);
argsArray.forEach(arg => console.log(arg));
}
printArgsSlice(1, 2, 3);
“`
Spread Syntax (...
) 与数组
Spread syntax (展开语法) 可以在数组字面量或函数调用中展开数组元素,非常方便。
- 复制数组 (浅拷贝):
javascript
const original = [1, 2, 3];
const copy = [...original];
console.log(copy); // 输出: [1, 2, 3]
console.log(copy === original); // 输出: false - 合并数组:
javascript
const arr1 = [1, 2];
const arr2 = [3, 4];
const merged = [...arr1, ...arr2];
console.log(merged); // 输出: [1, 2, 3, 4] - 在数组中插入元素:
javascript
const fruits = ["Banana", "Cherry"];
const newFruits = ["Apple", ...fruits, "Date"];
console.log(newFruits); // 输出: ["Apple", "Banana", "Cherry", "Date"] - 将数组元素作为函数参数:
javascript
const numbers = [1, 2, 3];
console.log(Math.max(...numbers)); // 输出: 3
数组解构 (Array Destructuring)
数组解构赋值允许你从数组中提取值,然后将这些值赋给单独的变量。
“`javascript
const rgb = [“Red”, “Green”, “Blue”];
// 基本解构
const [r, g, b] = rgb;
console.log(r); // 输出: “Red”
console.log(g); // 输出: “Green”
console.log(b); // 输出: “Blue”
// 跳过元素
const [first, , third] = rgb;
console.log(first); // 输出: “Red”
console.log(third); // 输出: “Blue”
// 使用 rest 参数 (…rest) 收集剩余元素
const [header, …restOfColors] = rgb;
console.log(header); // 输出: “Red”
console.log(restOfColors); // 输出: [“Green”, “Blue”]
// 默认值
const [c1, c2, c3, c4 = “Black”] = rgb;
console.log(c1, c2, c3, c4); // 输出: Red Green Blue Black
// 交换变量的值 (经典用法)
let x = 1, y = 2;
[x, y] = [y, x];
console.log(x, y); // 输出: 2 1
“`
数组解构极大地提高了代码的可读性和简洁性。
总结与最佳实践
- 优先使用数组字面量
[]
创建数组。 - 使用索引
[]
访问和修改元素。 注意零索引和超出范围的访问。 - 理解
length
属性 及其对数组的影响。 - 区分修改原数组的方法 (
push
,pop
,shift
,unshift
,splice
,sort
,reverse
,fill
,copyWithin
) 和返回新数组/值的方法 (slice
,concat
,map
,filter
,reduce
等)。在需要保留原数组时,优先使用后者。 - 对于遍历,推荐使用
for
循环 (需要控制流) 或for...of
/forEach
(更现代、简洁)。避免使用for...in
遍历数组。 - 熟练掌握高阶函数 (
map
,filter
,reduce
),它们是处理数组的强大工具,体现了函数式编程的思想。 - 使用
Array.isArray()
准确判断一个值是否为数组。 - 了解类数组对象及其与真正数组的区别,知道如何使用
Array.from()
或 Spread syntax 转换为数组。 - 利用 Spread syntax (
...
) 和数组解构来简化代码。
JavaScript 数组是动态的、灵活的,并且拥有一套极其丰富和实用的内置方法。通过深入理解和实践这些概念和方法,你将能够更高效、更优雅地处理各种数据集合,为你的 JavaScript 开发之路打下坚实的基础。不断尝试和练习这些方法,直到它们成为你编程工具箱中的自然部分!