JavaScript 数组操作方法从入门到精通

JavaScript 数组操作方法:从入门到精通

欢迎来到JavaScript数组操作的全面教程!数组是JavaScript中最常用的数据结构之一,掌握其操作方法对于编写高效、简洁的代码至关重要。本教程将带您从数组的基础概念出发,深入探讨各种改变原数组、不改变原数组以及迭代的方法,并通过丰富的示例帮助您巩固理解。

graph TD A[JavaScript 数组方法] --> B[改变原数组 Mutator Methods] A --> C[不改变原数组 Accessor Methods] A --> D[迭代方法 Iteration Methods] A --> E[静态方法 Static Methods] B --> B1[添加/删除: push, pop, unshift, shift, splice] B --> B2[排序/反转: sort, reverse] B --> B3[填充: fill] C --> C1[查询/连接: concat, join, slice, indexOf, lastIndexOf, includes] C --> C2[转换为字符串: toString, toLocaleString] D --> D1[遍历: forEach, map, filter, reduce, reduceRight] D --> D2[查找: find, findIndex] D --> D3[判断: some, every] D4[扁平化: flat, flatMap] --> D E --> E1[类型判断: Array.isArray] E --> E2[创建: Array.of, Array.from]

第一部分:数组基础与概念

在深入了解数组操作方法之前,我们先来回顾一下数组的基本概念和创建方式。

1. 什么是数组?

在JavaScript中,数组是一种特殊的有序集合,它可以存储多个值(元素),并且这些值可以是任何数据类型(数字、字符串、布尔值、对象、甚至其他数组)。数组的每个元素都有一个对应的数字索引,从0开始。

2. 数组的创建方式

2.1. 字面量创建 `[]` (推荐)

这是创建数组最常用、最简洁的方式。


const arr1 = []; // 创建一个空数组
const arr2 = [1, 2, 3, 'hello', true]; // 创建一个包含多个元素的数组
const arr3 = [
    { name: 'Alice', age: 30 },
    [10, 20]
]; // 数组元素可以是对象或嵌套数组
        

2.2. `Array` 构造函数创建 `new Array()`

使用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']
        

2.3. `Array.of()`

Array.of() 方法用于创建新数组实例,无论传入的参数是单个数字还是多个。它解决了Array构造函数在处理单个数字参数时的歧义。


const arr8 = Array.of(7); // [7] - 包含数字7的数组
const arr9 = Array.of(1, 2, 'three'); // [1, 2, 'three']
        

2.4. `Array.from()`

Array.from() 方法从一个类数组对象可迭代对象创建一个新的、浅拷贝的数组实例。这对于将NodeListSetMap等转换为数组非常有用,也可以用于生成指定范围的数组。


// 从字符串创建数组
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]
        

3. 数组元素的访问与修改

3.1. 索引访问 `arr[index]`

通过方括号和索引来访问数组中的特定元素。索引从 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 (索引超出范围)
        

3.2. `length` 属性

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);
        

第二部分:改变原数组的方法(Mutator Methods)

这些方法会直接修改调用它们的原数组,并返回修改后的数组或新的长度/元素。

1. 添加/删除元素

1.1. `push()`:末尾添加

在数组的末尾添加一个或多个元素,并返回数组的新长度。


const fruits = ['apple', 'banana'];
const newLength = fruits.push('orange', 'grape');
console.log(fruits);      // ['apple', 'banana', 'orange', 'grape']
console.log(newLength);   // 4
        

1.2. `pop()`:末尾删除

删除数组的最后一个元素,并返回被删除的元素。如果数组为空,则返回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
        

1.3. `unshift()`:开头添加

在数组的开头添加一个或多个元素,并返回数组的新长度。这个方法会改变所有现有元素的索引。


const fruits = ['banana', 'orange'];
const newLength = fruits.unshift('apple', 'grape');
console.log(fruits);      // ['apple', 'grape', 'banana', 'orange']
console.log(newLength);   // 4
        

1.4. `shift()`:开头删除

删除数组的第一个元素,并返回被删除的元素。如果数组为空,则返回undefined。与unshift()类似,它也会改变剩余元素的索引。


const fruits = ['apple', 'banana', 'orange'];
const removedFruit = fruits.shift();
console.log(fruits);        // ['banana', 'orange']
console.log(removedFruit);  // 'apple'
        

1.5. `splice()`:强大的通用方法

splice() 方法通过删除或替换现有元素或者原地添加新的元素来修改数组,并以数组形式返回被修改/删除的元素。这个方法非常灵活,因为它可以在任意位置执行删除、插入或替换操作。

语法:array.splice(start, deleteCount, 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']
        

2. 排序与反转

2.1. `sort()`:排序

sort() 方法用于对数组的元素进行原地排序,并返回数组。默认情况下,它将元素转换为字符串,然后按照它们的UTF-16码元值进行排序。

为了正确排序数字或自定义排序,需要提供一个比较函数

比较函数 `(a, b) => { ... }`:


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 } ]
        

2.2. `reverse()`:反转

reverse() 方法将数组中元素的顺序颠倒,并返回对同一数组的引用。


const numbers = [1, 2, 3, 4, 5];
numbers.reverse();
console.log(numbers); // [5, 4, 3, 2, 1]
        

3. 填充

3.1. `fill()`

fill() 方法用一个固定值填充数组中从起始索引到终止索引内的所有元素(不包含终止索引)。它会修改原数组。

语法:arr.fill(value, start, end)


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']
        

第三部分:不改变原数组的方法(Accessor Methods)

这些方法不会修改原数组,而是返回一个新的数组或值。

1. 查询与连接

1.1. `concat()`:连接数组

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]
        

1.2. `join()`:数组转字符串

join() 方法将数组的所有元素连接成一个字符串。元素之间用指定的分隔符分隔。

语法:arr.join(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会转换为空字符串)
        

1.3. `slice()`:截取子数组

slice() 方法返回一个新的数组,包含从 startend(不包括 end)选择的数组元素。它不会修改原数组。

语法:arr.slice(start, 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'] (原数组未变)
        

1.4. `indexOf()`:查找元素首次出现索引

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
        

1.5. `lastIndexOf()`:查找元素末次出现索引

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
        

1.6. `includes()`:判断是否包含

includes() 方法用来判断一个数组是否包含一个指定的值,根据情况返回 truefalse

它比 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)
        

2. 转换为字符串

2.1. `toString()`

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]"
        

2.2. `toLocaleString()`

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" (根据地区格式化日期)
        

第四部分:迭代方法(Iteration Methods)

这些方法都接受一个回调函数作为参数,并对数组的每个(或部分)元素执行该函数。它们是处理数组数据的主要方式,通常比传统的for循环更简洁、可读性更好。

回调函数参数约定

大多数迭代方法的回调函数通常接受三个参数:


// 示例回调函数结构
array.method((currentValue, index, array) => {
    // 逻辑处理
});
        

1. `forEach()`:遍历

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
        

2. `map()`:映射生成新数组

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']
        

3. `filter()`:过滤生成新数组

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']
        

4. `reduce()` / `reduceRight()`:归纳聚合

reduce() 方法对数组中的每个元素执行一个由您提供的reducer函数(从左到右),将其结果汇总为单个返回值。

reduceRight() 功能类似,但从右到左处理。

语法:arr.reduce(callback(accumulator, currentValue, index, array), 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"
        

5. `some()`:测试至少一个通过

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)
        

6. `every()`:测试所有都通过

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)
        

7. `find()`:查找第一个匹配元素的值

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
        

8. `findIndex()`:查找第一个匹配元素的索引

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
        

9. `flat()` / `flatMap()`:扁平化嵌套数组

flat() 方法会按照一个可指定的深度递归遍历数组,并将所有元素与遍历到的子数组中的元素合并为一个新数组。

语法:arr.flat(depth)


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]
        

第五部分:静态方法(Static Methods of Array Object)

这些方法是直接在Array构造函数上调用的,而不是在数组实例上调用。

1. `Array.isArray()`:判断是否为数组

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(),它们在“第一部分:数组基础与概念”中已经详细介绍,因为它们是数组创建的常用方法。

第六部分:实际应用与最佳实践

1. 链式调用数组方法

许多不改变原数组的迭代方法(如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']
        

2. 性能考量与选择

3. 数组方法的组合使用

通过组合不同的数组方法,可以高效地解决复杂的数据处理问题。


// 场景:从一堆学生成绩中,找出所有及格(>=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的编程旅程中取得更大的进步!

互动区域

登录后可以点赞此内容

参与互动

登录后可以点赞和评论此内容,与作者互动交流