《前端面试题:数组操作》
JavaScript 数组操作指南:哪些方法会改变原数组?哪些不会?(面试必备详解)
在 JavaScript 开发中,数组操作是最常用的功能之一。区分哪些方法会改变原数组(mutating methods),哪些不会(non-mutating methods) 是面试高频考点,也是避免实际开发中产生 BUG 的关键。本文将深度解析这两类方法,并附详细代码示例。
一、会改变原数组的方法(Mutating Methods)
这些方法直接修改原始数组,使用时需特别谨慎。
-
push()
在数组末尾添加元素,返回 新长度。let arr = [1, 2, 3]; arr.push(4); // 返回长度 4 console.log(arr); // [1, 2, 3, 4] ✅ 原数组改变
-
pop()
删除数组最后一个元素,返回 被删除的元素。let arr = [1, 2, 3]; arr.pop(); // 返回 3 console.log(arr); // [1, 2] ✅ 原数组改变
-
shift()
删除数组第一个元素,返回 被删除的元素。let arr = [1, 2, 3]; arr.shift(); // 返回 1 console.log(arr); // [2, 3] ✅ 原数组改变
-
unshift()
在数组开头添加元素,返回 新长度。let arr = [1, 2, 3]; arr.unshift(0); // 返回长度 4 console.log(arr); // [0, 1, 2, 3] ✅ 原数组改变
-
splice()
添加/删除任意位置元素,返回 被删除元素的数组。let arr = [1, 2, 3, 4]; arr.splice(1, 2, 'a', 'b'); // 从索引1开始删除2个元素,添加'a'、'b' console.log(arr); // [1, 'a', 'b', 4] ✅ 原数组改变
-
sort()
排序数组(默认按 Unicode 排序),返回 排序后的原数组。let arr = [3, 1, 2]; arr.sort(); // 返回 [1, 2, 3] console.log(arr); // [1, 2, 3] ✅ 原数组改变
-
reverse()
反转数组顺序,返回 反转后的原数组。let arr = [1, 2, 3]; arr.reverse(); // 返回 [3, 2, 1] console.log(arr); // [3, 2, 1] ✅ 原数组改变
-
fill()
填充数组(指定范围),返回 填充后的原数组。let arr = [1, 2, 3]; arr.fill(0, 1, 3); // 从索引1到3填充0 console.log(arr); // [1, 0, 0] ✅ 原数组改变
-
copyWithin()
复制数组内元素到指定位置,返回 修改后的原数组。let arr = [1, 2, 3, 4]; arr.copyWithin(0, 2, 4); // 将索引2-4的元素复制到索引0 console.log(arr); // [3, 4, 3, 4] ✅ 原数组改变
二、不会改变原数组的方法(Non-Mutating Methods)
这些方法返回新数组或新值,原始数组保持不变。
-
concat()
合并数组,返回 新数组。let arr = [1, 2]; let newArr = arr.concat([3, 4]); console.log(arr); // [1, 2] ❌ 原数组不变 console.log(newArr); // [1, 2, 3, 4]
-
slice()
截取数组片段,返回 新数组。let arr = [1, 2, 3, 4]; let subArr = arr.slice(1, 3); // 截取索引1-2 console.log(arr); // [1, 2, 3, 4] ❌ 原数组不变 console.log(subArr); // [2, 3]
-
join()
数组转字符串,返回 字符串。let arr = [1, 2, 3]; let str = arr.join('-'); console.log(arr); // [1, 2, 3] ❌ 原数组不变 console.log(str); // "1-2-3"
-
map()
映射新数组,返回 新数组。let arr = [1, 2, 3]; let doubled = arr.map(x => x * 2); console.log(arr); // [1, 2, 3] ❌ 原数组不变 console.log(doubled); // [2, 4, 6]
-
filter()
过滤元素,返回 新数组。let arr = [1, 2, 3, 4]; let evens = arr.filter(x => x % 2 === 0); console.log(arr); // [1, 2, 3, 4] ❌ 原数组不变 console.log(evens); // [2, 4]
-
reduce()
/reduceRight()
累积计算,返回 累积结果。let arr = [1, 2, 3]; let sum = arr.reduce((acc, cur) => acc + cur, 0); console.log(arr); // [1, 2, 3] ❌ 原数组不变 console.log(sum); // 6
-
find()
/findIndex()
查找元素/索引,返回 元素值或索引。let arr = [5, 12, 8]; let found = arr.find(x => x > 10); // 12 console.log(arr); // [5, 12, 8] ❌ 原数组不变
-
every()
/some()
逻辑判断,返回 布尔值。let arr = [1, 2, 3]; let allPositive = arr.every(x => x > 0); // true console.log(arr); // [1, 2, 3] ❌ 原数组不变
-
flat()
/flatMap()
扁平化数组,返回 新数组。let arr = [1, [2, 3]]; let flatArr = arr.flat(); console.log(arr); // [1, [2, 3]] ❌ 原数组不变 console.log(flatArr); // [1, 2, 3]
三、面试高频问题与陷阱解析
-
问题:
sort()
和reverse()
的返回值是什么?
答案: 它们返回修改后的原数组(不是新数组),支持链式调用(但不推荐)。 -
陷阱: 嵌套对象的修改
即使使用map()
、filter()
等非变异方法,若直接修改元素内的对象属性,仍会影响原数组:let users = [{ name: 'Alice' }, { name: 'Bob' }]; let newUsers = users.map(user => {user.name = user.name.toUpperCase(); // ❌ 错误!修改了原对象return user; }); console.log(users[0].name); // 'ALICE'(原数组被污染)
正确做法:
let newUsers = users.map(user => ({...user, // 创建新对象name: user.name.toUpperCase() }));
-
问题: 如何在 React/Vue 中安全更新数组状态?
答案: 使用非变异方法或创建副本:// React 示例:添加元素 setList(prev => [...prev, newItem]);// Vue 示例:删除元素 this.list = this.list.filter(item => item.id !== id);
-
问题: 如何快速判断一个方法是否改变原数组?
技巧:- 返回数组长度、元素或 undefined 的方法通常改变原数组(如
push
,pop
)。 - 返回新数组的方法不改变原数组(如
map
,filter
)。
- 返回数组长度、元素或 undefined 的方法通常改变原数组(如
四、总结与最佳实践
类型 | 方法 |
---|---|
改变原数组 | push , pop , shift , unshift , splice , sort , reverse , fill , copyWithin |
不改变原数组 | concat , slice , map , filter , reduce , find , every , flat , join |
最佳实践:
- 在函数式编程中优先使用非变异方法。
- 修改数组前用
[...arr]
或arr.slice()
创建副本。 - 在框架中更新状态时,永远不要直接修改原数组。
掌握这些细节不仅能轻松应对面试,更能写出健壮可靠的代码。建议收藏本文并动手练习示例代码!