欢迎来到JavaScript数组操作的全面教程!数组是JavaScript中最常用的数据结构之一,掌握其操作方法对于编写高效、简洁的代码至关重要。本教程将带您从数组的基础概念出发,深入探讨各种改变原数组、不改变原数组以及迭代的方法,并通过丰富的示例帮助您巩固理解。
在深入了解数组操作方法之前,我们先来回顾一下数组的基本概念和创建方式。
在JavaScript中,数组是一种特殊的有序集合,它可以存储多个值(元素),并且这些值可以是任何数据类型(数字、字符串、布尔值、对象、甚至其他数组)。数组的每个元素都有一个对应的数字索引,从0开始。
这是创建数组最常用、最简洁的方式。
const arr1 = []; // 创建一个空数组
const arr2 = [1, 2, 3, 'hello', true]; // 创建一个包含多个元素的数组
const arr3 = [
{ name: 'Alice', age: 30 },
[10, 20]
]; // 数组元素可以是对象或嵌套数组
使用Array构造函数时需要注意参数的不同含义:
const arr4 = new Array(); // []
const arr5 = new Array(5); // [empty × 5] - 创建一个长度为5的空数组,而不是包含数字5的数组
const arr6 = new Array(1, 2, 3); // [1, 2, 3]
const arr7 = new Array('hello', 'world'); // ['hello', 'world']
Array.of() 方法用于创建新数组实例,无论传入的参数是单个数字还是多个。它解决了Array构造函数在处理单个数字参数时的歧义。
const arr8 = Array.of(7); // [7] - 包含数字7的数组
const arr9 = Array.of(1, 2, 'three'); // [1, 2, 'three']
Array.from() 方法从一个类数组对象或可迭代对象创建一个新的、浅拷贝的数组实例。这对于将NodeList、Set、Map等转换为数组非常有用,也可以用于生成指定范围的数组。
// 从字符串创建数组
const arr10 = Array.from('hello'); // ['h', 'e', 'l', 'l', 'o']
// 从 Set 创建数组
const mySet = new Set([1, 2, 3, 2, 1]);
const arr11 = Array.from(mySet); // [1, 2, 3]
// 将类数组对象(如arguments或DOM集合)转换为数组
function createArrayFromArgs() {
return Array.from(arguments);
}
const arr12 = createArrayFromArgs(1, 'b', true); // [1, 'b', true]
// 使用映射函数生成数组
const arr13 = Array.from({ length: 5 }, (v, i) => i * 2); // [0, 2, 4, 6, 8]
通过方括号和索引来访问数组中的特定元素。索引从 0 开始。
const fruits = ['apple', 'banana', 'orange'];
console.log(fruits[0]); // 'apple'
console.log(fruits[1]); // 'banana'
console.log(fruits[2]); // 'orange'
console.log(fruits[3]); // undefined (索引超出范围)
length 属性表示数组中元素的数量。它是可读可写的。
const numbers = [10, 20, 30, 40];
console.log(numbers.length); // 4
// 修改length属性可以截断或扩展数组
numbers.length = 2; // [10, 20]
console.log(numbers);
numbers.length = 5; // [10, 20, empty × 3]
console.log(numbers);
这些方法会直接修改调用它们的原数组,并返回修改后的数组或新的长度/元素。
在数组的末尾添加一个或多个元素,并返回数组的新长度。
const fruits = ['apple', 'banana'];
const newLength = fruits.push('orange', 'grape');
console.log(fruits); // ['apple', 'banana', 'orange', 'grape']
console.log(newLength); // 4
删除数组的最后一个元素,并返回被删除的元素。如果数组为空,则返回undefined。
const fruits = ['apple', 'banana', 'orange'];
const removedFruit = fruits.pop();
console.log(fruits); // ['apple', 'banana']
console.log(removedFruit); // 'orange'
const emptyArr = [];
console.log(emptyArr.pop()); // undefined
在数组的开头添加一个或多个元素,并返回数组的新长度。这个方法会改变所有现有元素的索引。
const fruits = ['banana', 'orange'];
const newLength = fruits.unshift('apple', 'grape');
console.log(fruits); // ['apple', 'grape', 'banana', 'orange']
console.log(newLength); // 4
删除数组的第一个元素,并返回被删除的元素。如果数组为空,则返回undefined。与unshift()类似,它也会改变剩余元素的索引。
const fruits = ['apple', 'banana', 'orange'];
const removedFruit = fruits.shift();
console.log(fruits); // ['banana', 'orange']
console.log(removedFruit); // 'apple'
splice() 方法通过删除或替换现有元素或者原地添加新的元素来修改数组,并以数组形式返回被修改/删除的元素。这个方法非常灵活,因为它可以在任意位置执行删除、插入或替换操作。
语法:array.splice(start, deleteCount, item1, item2, ...)
start:必需。起始索引。deleteCount:可选。要删除的元素数量。如果为0,则不删除元素。如果省略,则删除从start到末尾的所有元素。item1, item2, ...:可选。要添加到数组中的元素。
const numbers = [1, 2, 3, 4, 5];
// 从索引2开始,删除2个元素
const removed = numbers.splice(2, 2);
console.log(numbers); // [1, 2, 5]
console.log(removed); // [3, 4]
const numbers = [1, 5];
// 从索引1开始,删除0个元素,插入2, 3, 4
numbers.splice(1, 0, 2, 3, 4);
console.log(numbers); // [1, 2, 3, 4, 5]
const fruits = ['apple', 'banana', 'orange'];
// 从索引1开始,删除1个元素('banana'),插入'grape', 'kiwi'
const replaced = fruits.splice(1, 1, 'grape', 'kiwi');
console.log(fruits); // ['apple', 'grape', 'kiwi', 'orange']
console.log(replaced); // ['banana']
sort() 方法用于对数组的元素进行原地排序,并返回数组。默认情况下,它将元素转换为字符串,然后按照它们的UTF-16码元值进行排序。
为了正确排序数字或自定义排序,需要提供一个比较函数:
比较函数 `(a, b) => { ... }`:
< 0,则 a 排在 b 前面。= 0,则 a 和 b 的相对位置不变。> 0,则 b 排在 a 前面。
const numbers = [3, 1, 4, 1, 5, 9];
numbers.sort(); // 默认按字符串排序
console.log(numbers); // [1, 1, 3, 4, 5, 9] (在这里恰好是对的,但数字更大时会出现问题,如 [10, 2])
const numbers2 = [10, 2, 8, 1];
numbers2.sort();
console.log(numbers2); // [1, 10, 2, 8] - 字符串排序的结果!
// 数字升序排序
numbers2.sort((a, b) => a - b);
console.log(numbers2); // [1, 2, 8, 10]
// 数字降序排序
numbers2.sort((a, b) => b - a);
console.log(numbers2); // [10, 8, 2, 1]
// 排序对象数组
const users = [
{ name: 'Alice', age: 30 },
{ name: 'Bob', age: 25 },
{ name: 'Charlie', age: 35 }
];
users.sort((a, b) => a.age - b.age); // 按年龄升序
console.log(users);
// [ { name: 'Bob', age: 25 }, { name: 'Alice', age: 30 }, { name: 'Charlie', age: 35 } ]
reverse() 方法将数组中元素的顺序颠倒,并返回对同一数组的引用。
const numbers = [1, 2, 3, 4, 5];
numbers.reverse();
console.log(numbers); // [5, 4, 3, 2, 1]
fill() 方法用一个固定值填充数组中从起始索引到终止索引内的所有元素(不包含终止索引)。它会修改原数组。
语法:arr.fill(value, start, end)
value:必需。用于填充数组的指定值。start:可选。起始索引(包含),默认为0。end:可选。终止索引(不包含),默认为arr.length。
const numbers = [1, 2, 3, 4, 5];
// 用0填充整个数组
numbers.fill(0);
console.log(numbers); // [0, 0, 0, 0, 0]
const colors = ['red', 'green', 'blue', 'yellow', 'purple'];
// 从索引1到索引3(不含3)填充 'black'
colors.fill('black', 1, 3);
console.log(colors); // ['red', 'black', 'black', 'yellow', 'purple']
这些方法不会修改原数组,而是返回一个新的数组或值。
concat() 方法用于合并两个或多个数组。此方法不会更改现有数组,而是返回一个新数组。
const arr1 = [1, 2];
const arr2 = [3, 4];
const arr3 = [5, 6];
const newArr = arr1.concat(arr2, arr3);
console.log(newArr); // [1, 2, 3, 4, 5, 6]
console.log(arr1); // [1, 2] (原数组未改变)
// 也可以连接非数组值,它们会被添加到新数组的末尾
const mixedArr = arr1.concat('hello', 7, [8, 9]);
console.log(mixedArr); // [1, 2, 'hello', 7, 8, 9]
join() 方法将数组的所有元素连接成一个字符串。元素之间用指定的分隔符分隔。
语法:arr.join(separator)
separator:可选。指定一个字符串来分隔数组的每个元素。如果省略,默认为逗号 (,)。如果为空字符串 (''),则所有元素之间没有分隔符。
const elements = ['Fire', 'Air', 'Water'];
console.log(elements.join()); // "Fire,Air,Water"
console.log(elements.join('')); // "FireAirWater"
console.log(elements.join('-')); // "Fire-Air-Water"
const numbers = [1, 2, undefined, 4];
console.log(numbers.join('_')); // "1_2__4" (undefined和null会转换为空字符串)
slice() 方法返回一个新的数组,包含从 start 到 end(不包括 end)选择的数组元素。它不会修改原数组。
语法:arr.slice(start, end)
start:可选。开始提取的索引。如果为负值,则表示从数组末尾开始的偏移量(-1表示最后一个元素)。默认为0。end:可选。结束提取的索引(不包含)。如果为负值,则表示从数组末尾开始的偏移量。默认为数组的长度。
const animals = ['ant', 'bison', 'camel', 'duck', 'elephant'];
console.log(animals.slice(2)); // ['camel', 'duck', 'elephant'] (从索引2到末尾)
console.log(animals.slice(2, 4)); // ['camel', 'duck'] (从索引2到索引4-1)
console.log(animals.slice(1, 5)); // ['bison', 'camel', 'duck', 'elephant']
console.log(animals.slice(-2)); // ['duck', 'elephant'] (从倒数第二个开始)
console.log(animals.slice(2, -1)); // ['camel', 'duck'] (从索引2到倒数第二个-1)
console.log(animals); // ['ant', 'bison', 'camel', 'duck', 'elephant'] (原数组未变)
indexOf() 方法返回在数组中可以找到一个给定元素的第一个(最小)索引,如果不存在,则返回 -1。
语法:arr.indexOf(searchElement, fromIndex)
const beasts = ['ant', 'bison', 'camel', 'duck', 'bison'];
console.log(beasts.indexOf('bison')); // 1
console.log(beasts.indexOf('bison', 2)); // 4 (从索引2开始查找)
console.log(beasts.indexOf('giraffe')); // -1
lastIndexOf() 方法返回在数组中可以找到一个给定元素的最后一个(最大)索引,如果不存在,则返回 -1。从后往前查找。
const beasts = ['ant', 'bison', 'camel', 'duck', 'bison'];
console.log(beasts.lastIndexOf('bison')); // 4
console.log(beasts.lastIndexOf('bison', 3)); // 1 (从索引3开始向前查找)
console.log(beasts.lastIndexOf('giraffe')); // -1
includes() 方法用来判断一个数组是否包含一个指定的值,根据情况返回 true 或 false。
它比 indexOf() 的优势在于能正确处理 NaN。
const numbers = [1, 2, 3, NaN];
console.log(numbers.includes(2)); // true
console.log(numbers.includes(4)); // false
console.log(numbers.includes(NaN)); // true (indexOf(NaN)会返回-1)
toString() 方法返回一个表示该数组的字符串。它将数组的每个元素转换为字符串,然后用逗号连接它们。
const fruits = ['apple', 'banana', 'orange'];
console.log(fruits.toString()); // "apple,banana,orange"
const mixed = [1, null, 'test', undefined, {a: 1}];
console.log(mixed.toString()); // "1,,test,,[object Object]"
toLocaleString() 方法返回一个表示数组元素的字符串,数组的每个元素都将通过其toLocaleString()方法转换成字符串,再使用一个特定语言环境的字符串(例如逗号)隔开。
const numbers = [1, 15000, 123.45];
console.log(numbers.toLocaleString()); // 例如: "1,15,000,123.45" (根据地区格式化数字)
const dates = [new Date()];
console.log(dates.toLocaleString()); // 例如: "2023/10/26 下午3:30:00" (根据地区格式化日期)
这些方法都接受一个回调函数作为参数,并对数组的每个(或部分)元素执行该函数。它们是处理数组数据的主要方式,通常比传统的for循环更简洁、可读性更好。
大多数迭代方法的回调函数通常接受三个参数:
currentValue:正在处理的当前元素。index:可选。正在处理的当前元素的索引。array:可选。调用该方法的数组本身。
// 示例回调函数结构
array.method((currentValue, index, array) => {
// 逻辑处理
});
forEach() 方法对数组的每个元素执行一次提供的函数。它没有返回值(或者说返回undefined),主要用于遍历数组并执行副作用(如打印、修改外部变量)。
const numbers = [1, 2, 3];
numbers.forEach((num, index) => {
console.log(`Element at index ${index} is: ${num}`);
});
// 输出:
// Element at index 0 is: 1
// Element at index 1 is: 2
// Element at index 2 is: 3
let sum = 0;
const prices = [10, 20, 30];
prices.forEach(price => {
sum += price;
});
console.log(sum); // 60
map() 方法创建一个新数组,其结果是该数组中的每个元素都调用一个提供的函数后返回的结果。它不修改原数组。
const numbers = [1, 2, 3];
const doubled = numbers.map(num => num * 2);
console.log(doubled); // [2, 4, 6]
console.log(numbers); // [1, 2, 3] (原数组未变)
const users = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' }
];
// 从用户对象数组中提取所有用户名
const names = users.map(user => user.name);
console.log(names); // ['Alice', 'Bob']
filter() 方法创建一个新数组,其中包含所有通过所提供函数实现的测试的元素。它不修改原数组。
const numbers = [1, 2, 3, 4, 5, 6];
const evenNumbers = numbers.filter(num => num % 2 === 0);
console.log(evenNumbers); // [2, 4, 6]
console.log(numbers); // [1, 2, 3, 4, 5, 6] (原数组未变)
const words = ['spray', 'limit', 'elite', 'exuberant', 'destruction', 'present'];
const longWords = words.filter(word => word.length > 6);
console.log(longWords); // ['exuberant', 'destruction', 'present']
reduce() 方法对数组中的每个元素执行一个由您提供的reducer函数(从左到右),将其结果汇总为单个返回值。
reduceRight() 功能类似,但从右到左处理。
语法:arr.reduce(callback(accumulator, currentValue, index, array), initialValue)
accumulator:累计器,回调函数上次调用时的返回值,或initialValue。currentValue:当前正在处理的元素。initialValue:可选。作为第一次调用callback函数时的第一个参数的值。如果没有提供initialValue,则将数组的第一个元素作为initialValue。
const numbers = [1, 2, 3, 4];
// 求和
const sum = numbers.reduce((acc, current) => acc + current, 0);
console.log(sum); // 10
// 扁平化数组数组
const nestedArray = [[1, 2], [3, 4], [5, 6]];
const flatArray = nestedArray.reduce((acc, current) => acc.concat(current), []);
console.log(flatArray); // [1, 2, 3, 4, 5, 6]
// 统计每个元素出现的次数
const names = ['Alice', 'Bob', 'Alice', 'Charlie', 'Bob'];
const nameCounts = names.reduce((acc, name) => {
acc[name] = (acc[name] || 0) + 1;
return acc;
}, {});
console.log(nameCounts); // { Alice: 2, Bob: 2, Charlie: 1 }
// reduceRight 示例
const letters = ['a', 'b', 'c'];
const reversedString = letters.reduceRight((acc, char) => acc + char, '');
console.log(reversedString); // "cba"
some() 方法测试数组中是不是至少有一个元素通过了被提供的函数实现的测试。它返回一个布尔值。
const numbers = [1, 3, 5, 7, 9];
const hasEven = numbers.some(num => num % 2 === 0);
console.log(hasEven); // false
const mixedNumbers = [1, 2, 3, 4];
const hasEven2 = mixedNumbers.some(num => num % 2 === 0);
console.log(hasEven2); // true (因为有2和4)
every() 方法测试一个数组内的所有元素是否都能通过某个指定函数的测试。它返回一个布尔值。
const numbers = [2, 4, 6, 8, 10];
const allEven = numbers.every(num => num % 2 === 0);
console.log(allEven); // true
const mixedNumbers = [1, 2, 3, 4];
const allEven2 = mixedNumbers.every(num => num % 2 === 0);
console.log(allEven2); // false (因为有1和3)
find() 方法返回数组中满足提供的测试函数的第一个元素的值。否则返回 undefined。
const users = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
{ id: 3, name: 'Alice' }
];
const foundUser = users.find(user => user.name === 'Alice');
console.log(foundUser); // { id: 1, name: 'Alice' } (返回第一个匹配项)
const nonExistentUser = users.find(user => user.name === 'David');
console.log(nonExistentUser); // undefined
findIndex() 方法返回数组中满足提供的测试函数的第一个元素的索引。否则返回 -1。
const users = [
{ id: 1, name: 'Alice' },
{ id: 2, name: 'Bob' },
{ id: 3, name: 'Alice' }
];
const foundIndex = users.findIndex(user => user.name === 'Alice');
console.log(foundIndex); // 0 (返回第一个匹配项的索引)
const nonExistentIndex = users.findIndex(user => user.name === 'David');
console.log(nonExistentIndex); // -1
flat() 方法会按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组。
语法:arr.flat(depth)
depth:可选。指定要扁平化的层级数量。默认为 1。Infinity 表示完全扁平化。
const arr1 = [1, 2, [3, 4]];
console.log(arr1.flat()); // [1, 2, 3, 4]
const arr2 = [1, 2, [3, 4, [5, 6]]];
console.log(arr2.flat()); // [1, 2, 3, 4, [5, 6]] (默认只扁平化一层)
console.log(arr2.flat(2)); // [1, 2, 3, 4, 5, 6] (扁平化两层)
console.log(arr2.flat(Infinity)); // [1, 2, 3, 4, 5, 6] (完全扁平化)
flatMap() 方法首先使用映射函数对每个元素调用,然后将结果扁平化到一个新数组中。它相当于先调用map(),然后调用flat(),深度为1。
const words = ['hello world', 'how are you'];
const flattenedWords = words.flatMap(sentence => sentence.split(' '));
console.log(flattenedWords); // ['hello', 'world', 'how', 'are', 'you']
const numbers = [1, 2, 3];
const multipliedAndFlattened = numbers.flatMap(num => [num, num * 2]);
console.log(multipliedAndFlattened); // [1, 2, 2, 4, 3, 6]
这些方法是直接在Array构造函数上调用的,而不是在数组实例上调用。
Array.isArray() 方法用于判断给定的值是否是一个数组。这比使用typeof更可靠,因为typeof []返回"object"。
console.log(Array.isArray([1, 2, 3])); // true
console.log(Array.isArray({})); // false
console.log(Array.isArray('hello')); // false
console.log(Array.isArray(new Array())); // true
关于Array.of() 和 Array.from(),它们在“第一部分:数组基础与概念”中已经详细介绍,因为它们是数组创建的常用方法。
许多不改变原数组的迭代方法(如map(), filter(), slice())会返回一个新数组,这使得它们可以被链式调用,从而编写出更简洁、更具声明性的代码。
const products = [
{ name: 'Laptop', price: 1200, category: 'Electronics' },
{ name: 'Book', price: 25, category: 'Books' },
{ name: 'Mouse', price: 30, category: 'Electronics' },
{ name: 'Keyboard', price: 75, category: 'Electronics' },
{ name: 'Pen', price: 5, category: 'Stationery' }
];
// 找出价格超过50元的电子产品,并返回它们的名称(按字母排序)
const expensiveElectronicsNames = products
.filter(product => product.category === 'Electronics' && product.price > 50)
.map(product => product.name)
.sort();
console.log(expensiveElectronicsNames); // ['Keyboard', 'Laptop']
push, pop, splice)通常更高效,因为它避免了创建新数组的开销。但如果需要保留原数组或在函数式编程风格中,应使用不改变原数组的方法(如concat, slice, map, filter)。forEach, map等)通常更具可读性,并且避免了手动管理索引和循环变量的错误。在大多数现代应用中,它们的性能与传统for循环非常接近,甚至在某些JS引擎优化下可能更好。除非有极端性能要求,否则推荐使用迭代方法。forEach无法中断循环。如果需要在满足某个条件时停止遍历,应使用for...of循环、some()或every()。
const arr = [1, 2, 3, 4, 5];
// forEach 无法中断
arr.forEach(num => {
console.log(num);
if (num === 3) {
// return; // 这只会跳过当前迭代,不会中断 forEach 循环
}
});
// 输出: 1, 2, 3, 4, 5 (全部打印)
// for...of 可以中断
for (const num of arr) {
console.log(num);
if (num === 3) {
break; // 可以中断循环
}
}
// 输出: 1, 2, 3 (在3处中断)
// some() 遇到第一个返回 true 的元素时停止
const hasThree = arr.some(num => {
console.log(`Checking with some: ${num}`);
return num === 3;
});
console.log(`Has three: ${hasThree}`);
// 输出:
// Checking with some: 1
// Checking with some: 2
// Checking with some: 3
// Has three: true (在3处停止检查)
empty项),但行为可能略有不同。在使用时需留意。通过组合不同的数组方法,可以高效地解决复杂的数据处理问题。
// 场景:从一堆学生成绩中,找出所有及格(>=60分)的学生的姓名,并按成绩从高到低排序。
const students = [
{ name: 'Alice', score: 85 },
{ name: 'Bob', score: 55 },
{ name: 'Charlie', score: 92 },
{ name: 'David', score: 70 },
{ name: 'Eve', score: 48 }
];
const topStudents = students
.filter(student => student.score >= 60) // 过滤及格学生
.sort((a, b) => b.score - a.score) // 按成绩降序排序
.map(student => student.name); // 提取姓名
console.log(topStudents); // ['Charlie', 'Alice', 'David']
通过本教程的学习,您应该已经对JavaScript数组的各种操作方法有了全面而深入的理解。多加实践是掌握这些知识的关键,祝您在JavaScript的编程旅程中取得更大的进步!