一、修改原数组的方法(有副作用)
这类方法会直接修改原始数组的结构或内容,可能导致副作用,一般适合需要动态调整数组的场景(如队列、动态更新),但需谨慎使用以避免意外数据污染。
1. 栈/队列操作
push(...items) / pop()
- 作用:
push向数组末尾添加元素并返回新长度;pop删除最后一个元素并返回该元素。 - 副作用:直接修改原数组。
- 适用场景:实现栈(先进后出)或队列(先进先出)逻辑。
const arr = [1, 2]; arr.push(3); // [1, 2, 3] arr.pop(); // 3, arr 变为 [1, 2]
shift() / unshift(...items)
- 作用:
shift删除第一个元素;unshift向开头添加元素。 - 副作用:修改原数组,但性能较差(需移动元素)。
- 适用场景:需频繁操作数组头部时(慎用)。
const arr = [1, 2]; arr.unshift(0); // [0, 1, 2] arr.shift(); // 0, arr 变为 [1, 2]
2. 灵活的数组操作
splice(start, deleteCount, ...items)
- 作用:删除或替换数组元素,并返回被删除的元素。
- 副作用:直接修改原数组。
- 适用场景:动态调整数组内容(如删除中间元素、插入新数据)。
const arr = [1, 2, 3, 4, 5]; const removed = arr.splice(2, 2); // 删除索引2开始的2个元素 console.log(removed); // [3, 4] console.log(arr); // [1, 2, 5]
3. 排序与反转
sort([compareFunction])
- 作用:对数组元素进行排序。
- 副作用:修改原数组。
- 注意事项:
- 默认按字符串字典序排序(可能不符合预期)。
- 需通过
a - b实现数字升序,b - a降序。
const arr = [3, 1, 4]; arr.sort((a, b) => a - b); // [1, 3, 4]
reverse()
- 作用:反转数组元素顺序。
- 副作用:修改原数组。
const arr = [2, 4, 3, 1, 5]; arr.reverse(); // [5, 1, 3, 4, 2]
4. 填充数组
fill(value, start, end)
- 作用:用指定值填充数组区间。
- 副作用:修改原数组。
- 适用场景:初始化固定值数组或批量更新数据。
const arr = [5, 1, 4, 2, 3]; arr.fill(9, 1, 3); // [5, 9, 9, 2, 3]
二、不修改原数组的方法(纯函数)
相较于上文所述,下面这些方法则不会改变原始数组,而是返回新数组或值,属于无副作用的“纯函数”,一般适合用于数据转换、过滤和聚合,保持数据不可变性,减少调试复杂度。
1. 遍历与转换
forEach(callback)
- 作用:对数组每个元素执行回调,无返回值。
- 特点:不返回新数组,适合副作用操作(如渲染 DOM)。
const arr = [1, 2, 3]; arr.forEach(item => console.log(item)); // 输出 1, 2, 3
map(callback)
- 作用:对数组每个元素执行回调,返回新数组。
- 适用场景:数据格式转换(如将字符串转数字)。
const arr = ["1", "2", "3"]; const nums = arr.map(Number); // [1, 2, 3]
2. 筛选与聚合
filter(callback)
- 作用:筛选满足条件的元素,返回新数组。
- 适用场景:过滤无效数据(如移除空值)。
const arr = [1, 2, 3, 4]; const even = arr.filter(x => x % 2 === 0); // [2, 4]
slice(start, end)
- 作用:截取数组片段,返回新数组。
- 适用场景:提取子数组而不修改原数组。
const arr = [1, 2, 3, 4, 5]; const subArr = arr.slice(0, 2); // [1, 2]
3. 查找与判断
find(callback) / findIndex(callback)
- 作用:查找满足条件的第一个元素或其索引。
- 适用场景:快速定位特定对象(如查找用户信息)。
const users = [ { id: 1, name: "Alice" }, { id: 2, name: "Bob" } ]; const user = users.find(u => u.id === 2); // { id: 2, name: "Bob" }
includes(value)
- 作用:判断数组是否包含某值。
- 适用场景:验证权限或数据存在性。
const arr = [1, 2, 3]; arr.includes(2); // true
三、业务场景中的最佳实践
在实际开发中,还是要根据业务需求选择合适的方法,平衡性能、以此维护性和数据一致性,如:在数据展示前使用纯函数方法预处理数据,在动态更新时使用修改原数组的方法。
1. 避免副作用的场景
- 推荐方法:
map、filter、slice。 - 案例:数据展示前的预处理。
const data = [10, 20, 30]; const processed = data.map(x => x * 2); // [20, 40, 60] // data 仍为 [10, 20, 30]
2. 性能敏感的场景
- 慎用方法:
shift、unshift。 - 替代方案:使用
slice+concat模拟队列。const queue = [1, 2, 3]; const newQueue = [queue[0] + 1].concat(queue.slice(1)); // [2, 2, 3]
3. 数据清洗的场景
- 推荐方法:
filter+map。 - 案例:过滤空值并转换格式。
const raw = [null, "a", undefined, "b"]; const clean = raw.filter(Boolean).map(x => x.toUpperCase()); // ["A", "B"]
四、版本演进与兼容性
1. ES5 vs ES6+
- ES5:提供
indexOf、lastIndexOf等基础查找方法。 - ES6:新增
find、includes等更直观的方法。 - ES2023:引入
findLast、findLastIndex,增强反向查找能力。
2. 兼容性建议
- 生产环境:优先使用
includes替代indexOf判断存在性。 - 旧浏览器:通过 Polyfill 补充缺失方法(如
Array.from)。
五、总结
| 方法类型 | 推荐方法 | 适用场景 | 是否修改原数组 |
|---|---|---|---|
| 修改原数组 | push/pop/splice/sort | 动态调整数组内容(如队列操作) | ✅ |
| 纯函数 | map/filter/slice | 数据转换/过滤(保持数据不可变) | ❌ |
| 查找与判断 | find/includes | 快速定位或验证数据存在性 | ❌ |
建议:
- 在需要保持数据不可变时,优先选择不修改原数组的方法。
- 对性能敏感的场景(如大型数组),避免使用
shift/unshift。 - 结合业务需求选择合适的方法,例如:
map转换数据、filter清洗数据、find定位关键元素。