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. 尾部操作
尾部操作是最高效的数组修改方式,因为不会影响其他元素的索引。
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. 头部操作
头部操作效率较低,因为每次添加或删除都会导致所有后续元素索引前移或后移。
// 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() 是最灵活的数组修改方法,可以在任意位置添加、删除或替换元素。
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. 清空数组
清空数组有多种方式,但行为和性能略有差异。
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或重新赋值,视引用情况而定。
三、数组遍历方法
遍历数组是日常开发中最常见的操作。不同遍历方式在语法、性能和用途上各有优劣。
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循环性能最好,支持break、continue,适合复杂逻辑。for...of语法简洁,支持break和yield,推荐用于简单遍历。forEach()语义清晰,但无法中途跳出(return仅结束当前回调)。for...in用于对象,不推荐用于数组,可能遍历到非数字索引或原型属性。entries()结合for...of可同时获取索引和值,是现代 JS 的优雅写法。
四、查找与筛选
查找和筛选是处理数组数据的核心能力,尤其在处理用户列表、表单验证等场景中非常关键。
1. 查找元素
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,适合需要索引的场景。findLast和findLastIndex是 ES2023 新增,从末尾开始查找。includes()用于基本类型比较,使用===判断。indexOf()对于对象数组不适用(引用不同),应配合find使用。
2. 筛选数组
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. 映射转换
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()先map再flat(1),适合将一个元素映射为多个并展平,避免嵌套。
2. 扁平化数组
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. 归约操作
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()从右向左处理,适用于需要逆序操作的场景。
六、排序与反转
排序和反转是改变数组顺序的常用操作,但需注意它们会修改原数组。
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()创建副本后再排序。
七、数组切片与连接
切片和连接用于提取子数组或合并多个数组,是构建新数组的重要手段。
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)提取从start到end(不含)的子数组,支持负索引。concat()可连接多个数组或值,返回新数组。- 扩展运算符
...语法更简洁,是现代 JS 的首选方式。join(separator)将数组元素拼接为字符串,常用于生成路径、标签等。
八、高阶函数应用
高阶函数让数组操作更具表达力,支持函数式编程范式,提升代码可维护性。
1. 条件判断
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. 函数式编程模式
// 管道操作模拟
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. 扩展运算符
// 数组复制 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. 解构赋值
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. 新增静态方法
// 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. 方法选择指南
// 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. 避免常见陷阱
// 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"说明:
- 稀疏数组的
map、filter等方法会跳过空位,导致意外行为。sort()、reverse()、splice()等方法会修改原数组,需注意副作用。- 浮点数计算应使用
toFixed()或Math.round()处理精度问题。
3. 实用工具函数
// 数组去重
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 数组的核心操作方法,从基础到高级,从传统到现代。掌握这些方法将极大提升你的开发效率和代码质量。建议在实际项目中多加练习,形成自己的使用习惯和最佳实践。
到此这篇关于JavaScript 数组的核心操作方法,从基础到高级的文章就介绍到这了,更多相关js数组基础内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!
