JavaScript 数组的核心操作方法,从基础到高级

概述

JavaScript 数组是开发中最常用的数据结构之一,掌握其操作方法对于提高编程效率至关重要。以下是我整理的完整数组操作指南。

一、数组创建与初始化

在 JavaScript 中,有多种方式可以创建和初始化数组。不同的方法适用于不同的场景,理解它们的区别有助于写出更清晰、更安全的代码。

javascript 复制代码
// 1. 字面量创建
const arr1 = [1, 2, 3, 4, 5];
const arr2 = ['a', 'b', 'c'];

// 2. 构造函数创建
const arr3 = new Array(5);        // 创建长度为5的空数组
const arr4 = new Array(1, 2, 3);  // 创建包含元素的数组

// 3. Array.of() - 解决构造函数歧义
Array.of(7);       // [7]
Array.of(1, 2, 3); // [1, 2, 3]

// 4. Array.from() - 从类数组或可迭代对象创建
Array.from('hello');           // ['h', 'e', 'l', 'l', 'o']
Array.from({ length: 5 });     // [undefined, undefined, ...]
Array.from({ length: 5 }, (_, i) => i); // [0, 1, 2, 3, 4]

// 5. 填充数组
const filled1 = new Array(5).fill(0);        // [0, 0, 0, 0, 0]
const filled2 = Array.from({ length: 5 }, () => 1); // [1, 1, 1, 1, 1]

说明

  • 使用字面量 [] 是最常见且推荐的方式,简洁直观。
  • new Array(n) 当传入单个数字时会创建稀疏数组(holes),行为容易出错,应避免。
  • Array.of() 能安全地创建指定元素的数组,解决了 new Array() 的歧义问题。
  • Array.from() 不仅能将类数组(如 arguments、NodeList)转为真实数组,还能结合 length 和映射函数生成序列或初始化数组。
  • fill()Array.from() 配合使用,是初始化固定值数组的常用手段。

二、元素增删操作

数组的增删操作分为在头部、尾部或任意位置进行。不同方法对性能和原数组的影响不同,需根据场景选择合适的方法。

1. 尾部操作

尾部操作是最高效的数组修改方式,因为不会影响其他元素的索引。

javascript 复制代码
const arr = [1, 2, 3];

// push - 尾部添加元素
arr.push(4);        // 返回新长度: 4, arr: [1, 2, 3, 4]
arr.push(5, 6);     // 可添加多个: [1, 2, 3, 4, 5, 6]

// pop - 尾部删除元素
const last = arr.pop(); // last = 6, arr: [1, 2, 3, 4, 5]

说明

  • push() 可接收多个参数,一次性添加多个元素,返回新长度。
  • pop() 删除并返回最后一个元素,数组为空时返回 undefined
  • 这两个方法直接修改原数组,适用于需要累积数据的场景(如栈结构)。

2. 头部操作

头部操作效率较低,因为每次添加或删除都会导致所有后续元素索引前移或后移。

javascript 复制代码
// unshift - 头部添加元素
arr.unshift(0);     // 返回新长度: 6, arr: [0, 1, 2, 3, 4, 5]
arr.unshift(-2, -1); // [-2, -1, 0, 1, 2, 3, 4, 5]

// shift - 头部删除元素
const first = arr.shift(); // first = -2, arr: [-1, 0, 1, 2, 3, 4, 5]

说明

  • unshift() 在数组开头插入一个或多个元素,返回新长度。
  • shift() 删除并返回第一个元素,数组为空返回 undefined
  • 由于性能开销较大,应避免在大型数组中频繁使用。

3. 任意位置操作

splice() 是最灵活的数组修改方法,可以在任意位置添加、删除或替换元素。

javascript 复制代码
const arr = [1, 2, 3, 4, 5];

// splice - 多功能修改
// 删除:从索引2开始删除1个元素
arr.splice(2, 1);   // 返回删除元素: [3], arr: [1, 2, 4, 5]

// 添加:从索引1开始删除0个元素,添加新元素
arr.splice(1, 0, 'a', 'b'); // arr: [1, 'a', 'b', 2, 4, 5]

// 替换:从索引3开始删除2个元素,添加新元素
arr.splice(3, 2, 'c', 'd'); // 返回删除元素: [2, 4], arr: [1, 'a', 'b', 'c', 'd']

说明

  • splice(start, deleteCount, item1, item2, ...)start 开始删除 deleteCount 个元素,并插入新元素。
  • 返回被删除的元素组成的数组。
  • 该方法直接修改原数组,适合精确控制数组结构的场景。

4. 清空数组

清空数组有多种方式,但行为和性能略有差异。

javascript 复制代码
let arr = [1, 2, 3];

// 方法1: 重新赋值 (推荐)
arr = [];

// 方法2: 修改length属性
arr.length = 0;

// 方法3: splice
arr.splice(0, arr.length);

说明

  • 重新赋值 arr = [] 最简洁,但如果其他变量引用原数组,则原数组仍存在。
  • arr.length = 0 会清空所有引用该数组的变量,是彻底清空的可靠方式。
  • splice(0) 同样能清空并保留引用,但语法稍显复杂。
  • 推荐使用 length = 0 或重新赋值,视引用情况而定。

三、数组遍历方法

遍历数组是日常开发中最常见的操作。不同遍历方式在语法、性能和用途上各有优劣。

javascript 复制代码
const numbers = [1, 2, 3, 4, 5];

// 1. for循环 (最基础)
for (let i = 0; i < numbers.length; i++) {
    console.log(numbers[i]);
}

// 2. for...of循环 (推荐)
for (const num of numbers) {
    console.log(num);
}

// 3. forEach方法
numbers.forEach((num, index, array) => {
    console.log(`索引 ${index}: 值 ${num}`);
});

// 4. for...in (不推荐用于数组,会遍历所有可枚举属性)
for (const index in numbers) {
    console.log(numbers[index]);
}

// 5. entries() 获取索引和值
for (const [index, value] of numbers.entries()) {
    console.log(index, value);
}

说明

  • for 循环性能最好,支持 breakcontinue,适合复杂逻辑。
  • for...of 语法简洁,支持 breakyield,推荐用于简单遍历。
  • forEach() 语义清晰,但无法中途跳出(return 仅结束当前回调)。
  • for...in 用于对象,不推荐用于数组,可能遍历到非数字索引或原型属性。
  • entries() 结合 for...of 可同时获取索引和值,是现代 JS 的优雅写法。

四、查找与筛选

查找和筛选是处理数组数据的核心能力,尤其在处理用户列表、表单验证等场景中非常关键。

1. 查找元素

javascript 复制代码
const users = [
    { id: 1, name: 'Alice', age: 25 },
    { id: 2, name: 'Bob', age: 30 },
    { id: 3, name: 'Charlie', age: 25 }
];

// find - 查找第一个符合条件的元素
const user = users.find(u => u.age === 25); // { id: 1, name: 'Alice', age: 25 }

// findIndex - 查找第一个符合条件的元素索引
const index = users.findIndex(u => u.name === 'Bob'); // 1

// findLast / findLastIndex (ES2023)
const last25 = users.findLast(u => u.age === 25); // { id: 3, name: 'Charlie', age: 25 }

// includes - 检查是否包含某元素
[1, 2, 3].includes(2); // true

// indexOf / lastIndexOf - 查找元素位置
['a', 'b', 'c', 'b'].indexOf('b');    // 1
['a', 'b', 'c', 'b'].lastIndexOf('b'); // 3

说明

  • find() 返回第一个匹配元素,未找到返回 undefined
  • findIndex() 返回索引,未找到返回 -1,适合需要索引的场景。
  • findLastfindLastIndex 是 ES2023 新增,从末尾开始查找。
  • includes() 用于基本类型比较,使用 === 判断。
  • indexOf() 对于对象数组不适用(引用不同),应配合 find 使用。

2. 筛选数组

javascript 复制代码
const numbers = [1, 2, 3, 4, 5, 6];

// filter - 筛选符合条件的元素
const even = numbers.filter(n => n % 2 === 0); // [2, 4, 6]
const adults = users.filter(u => u.age >= 18);

// 链式调用
const result = users
    .filter(u => u.age > 20)
    .map(u => u.name); // ['Bob', 'Charlie']

说明

  • filter() 返回一个新数组,包含所有满足条件的元素,不修改原数组。
  • find() 不同,filter() 返回数组,即使只有一个匹配项。
  • 支持链式调用,常与 map()sort() 等组合使用,实现函数式编程风格。

五、数组转换

数组转换是函数式编程的核心,通过映射、扁平化和归约,可以将数据结构灵活变换。

1. 映射转换

javascript 复制代码
const numbers = [1, 2, 3];

// map - 将数组映射为新数组
const doubled = numbers.map(n => n * 2); // [2, 4, 6]
const userNames = users.map(u => u.name); // ['Alice', 'Bob', 'Charlie']

// flatMap - 映射后扁平化 (ES2019)
const phrases = ['hello world', 'good morning'];
const words = phrases.flatMap(phrase => phrase.split(' ')); 
// ['hello', 'world', 'good', 'morning']

说明

  • map() 是最常用的转换方法,将每个元素通过函数映射为新值。
  • 返回新数组,长度与原数组相同。
  • flatMap()mapflat(1),适合将一个元素映射为多个并展平,避免嵌套。

2. 扁平化数组

javascript 复制代码
const nested = [1, [2, [3, [4]]]];

// flat - 扁平化数组 (ES2019)
nested.flat();      // [1, 2, [3, [4]]]
nested.flat(2);     // [1, 2, 3, [4]]
nested.flat(Infinity); // [1, 2, 3, 4]

// 替代方案 (ES6)
const flatten = arr => arr.reduce((acc, val) => 
    acc.concat(Array.isArray(val) ? flatten(val) : val), []);

说明

  • flat(depth) 将嵌套数组按指定深度展平。
  • Infinity 可完全展平任意深度嵌套。
  • 旧版可用 reduce + concat + 递归 实现,但性能较差。

3. 归约操作

javascript 复制代码
const numbers = [1, 2, 3, 4, 5];

// reduce - 从左到右归约
const sum = numbers.reduce((acc, curr) => acc + curr, 0); // 15
const max = numbers.reduce((acc, curr) => Math.max(acc, curr), -Infinity); // 5

// reduceRight - 从右到左归约
const reversed = numbers.reduceRight((acc, curr) => [...acc, curr], []); // [5, 4, 3, 2, 1]

// 复杂示例:统计字符出现次数
const chars = ['a', 'b', 'a', 'c', 'b', 'a'];
const count = chars.reduce((acc, char) => {
    acc[char] = (acc[char] || 0) + 1;
    return acc;
}, {}); // { a: 3, b: 2, c: 1 }

说明

  • reduce() 是函数式编程的"瑞士军刀",可用于求和、拼接、分组、状态累积等。
  • 接收累加器 acc 和当前值 curr,初始值通过第二个参数指定。
  • reduceRight() 从右向左处理,适用于需要逆序操作的场景。

六、排序与反转

排序和反转是改变数组顺序的常用操作,但需注意它们会修改原数组。

javascript 复制代码
const numbers = [3, 1, 4, 1, 5, 9];
const names = ['John', 'Alice', 'Bob'];

// sort - 排序 (会修改原数组)
numbers.sort(); // [1, 1, 3, 4, 5, 9] - 默认按字符串排序
numbers.sort((a, b) => a - b); // 数字升序
numbers.sort((a, b) => b - a); // 数字降序

names.sort(); // ['Alice', 'Bob', 'John'] - 字符串排序

// 对象数组排序
users.sort((a, b) => a.age - b.age); // 按年龄升序
users.sort((a, b) => a.name.localeCompare(b.name)); // 按姓名排序

// reverse - 反转数组
numbers.reverse(); // [9, 5, 4, 3, 1, 1]

// 创建排序副本 (不修改原数组)
const sorted = [...numbers].sort();
const sorted2 = numbers.slice().sort(); // 等效

说明

  • sort() 默认将元素转为字符串比较,数字排序必须提供比较函数 (a, b) => a - b
  • localeCompare() 用于安全的字符串排序,支持多语言。
  • reverse() 直接反转原数组。
  • 如需保留原数组,应使用扩展运算符或 slice() 创建副本后再排序。

七、数组切片与连接

切片和连接用于提取子数组或合并多个数组,是构建新数组的重要手段。

javascript 复制代码
const arr = [1, 2, 3, 4, 5];

// slice - 切片 (不修改原数组)
arr.slice(1, 3);    // [2, 3] - 索引1到3(不含)
arr.slice(2);       // [3, 4, 5] - 从索引2到最后
arr.slice(-2);      // [4, 5] - 最后两个元素
arr.slice(1, -1);   // [2, 3, 4] - 从1到倒数第1个

// concat - 连接数组
const arr1 = [1, 2];
const arr2 = [3, 4];
const combined = arr1.concat(arr2); // [1, 2, 3, 4]
const combined2 = [...arr1, ...arr2]; // ES6扩展运算符 (推荐)

// join - 数组转字符串
['Hello', 'World'].join(' '); // "Hello World"
[1, 2, 3].join('-');          // "1-2-3"

说明

  • slice(start, end) 提取从 startend(不含)的子数组,支持负索引。
  • concat() 可连接多个数组或值,返回新数组。
  • 扩展运算符 ... 语法更简洁,是现代 JS 的首选方式。
  • join(separator) 将数组元素拼接为字符串,常用于生成路径、标签等。

八、高阶函数应用

高阶函数让数组操作更具表达力,支持函数式编程范式,提升代码可维护性。

1. 条件判断

javascript 复制代码
const numbers = [1, 2, 3, 4, 5];

// every - 所有元素都满足条件
numbers.every(n => n > 0); // true

// some - 至少一个元素满足条件
numbers.some(n => n > 4); // true

// 实用示例
const formFields = [{ value: 'abc' }, { value: '' }, { value: 'def' }];
const allFilled = formFields.every(field => field.value.trim() !== ''); // false
const anyFilled = formFields.some(field => field.value.trim() !== ''); // true

说明

  • every() 类似逻辑与(AND),全部为真才返回 true
  • some() 类似逻辑或(OR),任一为真即返回 true
  • 常用于表单验证、权限检查、状态判断等场景。

2. 函数式编程模式

javascript 复制代码
// 管道操作模拟
const pipe = (...fns) => (initialValue) => 
    fns.reduce((acc, fn) => fn(acc), initialValue);

// 组合函数
const processNumbers = pipe(
    arr => arr.filter(n => n % 2 === 0),  // 筛选偶数
    arr => arr.map(n => n * 2),           // 乘以2
    arr => arr.reduce((a, b) => a + b, 0) // 求和
);

processNumbers([1, 2, 3, 4, 5]); // 12 (2*2 + 4*2 = 4 + 8)

说明

  • 函数式编程强调无副作用、数据不可变和函数组合。
  • pipe() 实现了函数的链式调用,每个函数接收上一个的输出。
  • 适合处理数据流、构建 DSL 或复杂转换逻辑。

九、ES6+ 新特性

ES6 及后续版本为数组操作带来了革命性改进,尤其是扩展运算符和解构赋值,极大提升了开发体验。

1. 扩展运算符

javascript 复制代码
// 数组复制
const original = [1, 2, 3];
const copy = [...original];

// 数组合并
const arr1 = [1, 2];
const arr2 = [3, 4];
const merged = [...arr1, ...arr2]; // [1, 2, 3, 4]

// 函数参数
const numbers = [1, 2, 3];
Math.max(...numbers); // 3

// 添加元素
const newArr = [0, ...numbers, 4]; // [0, 1, 2, 3, 4]

说明

  • 扩展运算符 ... 可展开可迭代对象,语法简洁,功能强大。
  • 广泛用于浅拷贝、合并、函数传参、插入元素等场景。
  • 注意:仅支持浅拷贝,嵌套对象仍为引用。

2. 解构赋值

javascript 复制代码
const arr = [1, 2, 3, 4, 5];

// 基本解构
const [first, second] = arr; // first = 1, second = 2

// 跳过元素
const [a, , c] = arr; // a = 1, c = 3

// 剩余元素
const [x, y, ...rest] = arr; // x = 1, y = 2, rest = [3, 4, 5]

// 默认值
const [p = 10, q = 20] = [1]; // p = 1, q = 20

// 交换变量
let m = 1, n = 2;
[m, n] = [n, m]; // m = 2, n = 1

说明

  • 解构赋值让从数组中提取值变得极其简洁。
  • 支持跳过、剩余、默认值等高级语法。
  • 常用于函数返回值、参数解构、变量交换等场景。

3. 新增静态方法

javascript 复制代码
// Array.isArray() - 类型检查
Array.isArray([1, 2, 3]); // true
Array.isArray({});        // false

// Array.from() 的高级用法
const unique = Array.from(new Set([1, 2, 2, 3])); // 去重: [1, 2, 3]

// 创建范围数组
const range = (start, end) => 
    Array.from({ length: end - start + 1 }, (_, i) => start + i);
range(1, 5); // [1, 2, 3, 4, 5]

说明

  • Array.isArray() 是判断数组的唯一可靠方式(typeof 无效)。
  • Array.from() 结合 Set 可实现去重,结合 length 可生成序列。
  • range() 函数是生成数字序列的常用工具。

十、性能优化建议

合理选择数组方法不仅能提升代码可读性,还能显著改善性能,尤其是在处理大数据集时。

1. 方法选择指南

javascript 复制代码
// 1. 遍历时不需要返回新数组:forEach > map
// 正确
numbers.forEach(n => console.log(n));

// 2. 需要返回新数组:map > forEach + push
// 推荐
const doubled = numbers.map(n => n * 2);
// 不推荐
const doubled2 = [];
numbers.forEach(n => doubled2.push(n * 2));

// 3. 查找元素:find > filter[0]
// 推荐
users.find(u => u.id === 1);
// 不推荐
users.filter(u => u.id === 1)[0];

// 4. 检查存在性:some > find + Boolean
// 推荐
users.some(u => u.age > 30);
// 不推荐
Boolean(users.find(u => u.age > 30));

说明

  • map() 会创建新数组,若不需要应使用 forEach()
  • filter()[0] 会遍历整个数组,而 find() 找到即停,性能更优。
  • some() 语义更明确且短路求值,优于 find 后转布尔。

2. 避免常见陷阱

javascript 复制代码
// 1. 稀疏数组
const sparse = new Array(5); // [empty × 5]
sparse.map(() => 1);         // 仍然 [empty × 5]

// 解决方案
const dense = Array.from({ length: 5 }, () => 1); // [1, 1, 1, 1, 1]

// 2. 修改原数组的方法
const arr = [1, 2, 3];
const sorted = arr.sort(); // arr也被修改了!

// 解决方案
const sortedSafe = [...arr].sort(); // 或 arr.slice().sort()

// 3. 浮点数精度
[0.1, 0.2].reduce((a, b) => a + b); // 0.30000000000000004

// 解决方案
[0.1, 0.2].reduce((a, b) => a + b).toFixed(1); // "0.3"

说明

  • 稀疏数组的 mapfilter 等方法会跳过空位,导致意外行为。
  • sort()reverse()splice() 等方法会修改原数组,需注意副作用。
  • 浮点数计算应使用 toFixed()Math.round() 处理精度问题。

3. 实用工具函数

javascript 复制代码
// 数组去重
const unique = arr => [...new Set(arr)];
unique([1, 2, 2, 3, 1]); // [1, 2, 3]

// 数组分组
const groupBy = (arr, key) => 
    arr.reduce((groups, item) => {
        const group = groups[item[key]] || [];
        return { ...groups, [item[key]]: [...group, item] };
    }, {});

// 数组分块
const chunk = (arr, size) => 
    Array.from({ length: Math.ceil(arr.length / size) }, (_, i) =>
        arr.slice(i * size, i * size + size)
    );

// 数组随机排序
const shuffle = arr => 
    [...arr].sort(() => Math.random() - 0.5);

说明

  • Set 去重简洁高效,适用于基本类型。
  • groupBy() 利用 reduce 实现对象分组,常用于数据聚合。
  • chunk() 将数组分页或分批处理。
  • shuffle() 实现洗牌,但 sort() 方式不够随机,生产环境建议用 Fisher-Yates 算法。

总结

这份总结涵盖了 JavaScript 数组的核心操作方法,从基础到高级,从传统到现代。掌握这些方法将极大提升你的开发效率和代码质量。建议在实际项目中多加练习,形成自己的使用习惯和最佳实践。

相关推荐
木易士心6 小时前
CSS 中 `data-status` 的使用详解
前端
TimelessHaze6 小时前
🧱 一文搞懂盒模型box-sizing:从标准盒到怪异盒的本质区别
前端·css·面试
VOLUN6 小时前
Vue3 中 watch 第三个参数怎么用?6 大配置属性 + 场景指南
前端·javascript·vue.js
Larcher6 小时前
100 行代码搞定 AI Logo 生成网站!新手也能吃透的 AIGC 前端实战
前端·javascript
Data_Adventure7 小时前
Java 与 TypeScript 的核心对比
前端·后端
天蓝色的鱼鱼7 小时前
零代码Mock神器:json-server 快速上手
前端
鱼鱼块7 小时前
《从零开始掌握CSS盒模型:结构、计算与最佳实践》
前端
子醉7 小时前
html5 input[type=date]如何让日期中的年/月/日改成英文
前端·html·html5
Data_Adventure7 小时前
从前端到 Java 后端:一份详细转型路线指南
前端·后端