本文由小智根据《Finally, safe array methods in JavaScript》所译。译文带有我自己的理解和思想,如需转载请注明相关信息:
原文地址:Finally, safe array methods in JavaScript
——译者:小智
不难理解为什么许多开发者在使用 .sort()、.reverse() 或者 .splice() 这些 JavaScript 方法前会犹豫:这些方法修改了原数组。这就会导致一些难以察觉、难以追踪的 bug,特别是在一些共享状态和响应式的程序中。好消息是在过去的几年里我们获得了一些新的数组方法,不会影响到原数组,使得数组操作更加安全和干净。
toSorted()toReversed()toSpliced()
这些方法返回数组的拷贝而不是在原数组上做改动。一点点语法变动就能带来不小的影响,尤其是对依赖不可变性(immutability) 的 React 开发者来说。
就地修改型数组方法的问题
在 JavaScript 中,像 .sort()、.reverse() 和 .splice() 这样的传统方法在调用的时候会修改原数组:
const numbers = [3, 1, 2];
numbers.sort(); // Mutates the array
console.log(numbers); // [1, 2, 3]
在像 React 这样的框架中,这会在更新状态的时候导致不可预料的行为,因为直接修改数组并不会触发重新渲染。
新旧对比
| 操作 | 修改型方法 | 非修改型方法 |
|---|---|---|
| 排序 | arr.sort() | arr.toSorted() |
| 倒序 | arr.reversed() | arr.toReversed() |
| 替换 | arr.splice() | arr.toSpliced() |
这些新方法的行为和对应的老方法相似,只是返回了新的数组,而不是在原数组上修改。
注意:这些都是“浅拷贝”,如果你的数组含有对象,这些对象还是维持原来的引用。
解决方案:安全的、非修改型方法
ES2023 引入了这些非修改型的数组方法:
toSorted()
创建一个排序的拷贝,而不修改原来的数组。
const numbers = [3, 1, 2];
const sorted = numbers.toSorted();
console.log(sorted); // [1, 2, 3]
console.log(numbers); // [3, 1, 2] ✅
// ‼️对比:.sort() 会修改原数组
numbers.sort();
console.log(numbers); // [1, 2, 3] ❌
你也可以传入一个用于对比的方法,就像 .sort() 那样:
const users = [
{ name: 'Kristen', age: 36 },
{ name: 'David', age: 34 }
]
const byAge = users.toSorted((a, b) => a.age - b.age);
console.log(byAge); // [{ name: 'David', age: 34 }, { name: 'Kristen', age: 36 }]
console.log(users); // [{ name: 'Kristen', age: 36 }, { name: 'David', age: 34 }]
toReversed()
返回数组的倒序拷贝:
const names = ['Kristen', 'David', 'Ben']
const reversed = names.toReversed();
console.log(reversed); // ['Ben', 'David', 'Kristen']
console.log(names); // ['Kristen', 'David', 'Ben'] ✅
// ‼️对比:.reverse() 会修改原数组
names.reverse();
console.log(names); // ['Ben', 'David', 'Kristen'] ❌
当你想展示一个倒序的列表而不修改原有数据时这就很完美。
toSpliced()
.splice() 方法的一个安全替代。返回包含新增/删除元素的新数组,而不染指旧数据。
const items = ['a', 'b', 'c', 'd'];
// 移除下标 1 的元素
const withoutB = items.toSpliced(1, 1);
console.log(withoutB); // ['a', 'c', 'd']
console.log(items); // ['a', 'b', 'c', 'd'] ✅
// 添加新元素到下标 2
const withX = items.toSpliced(2, 0, 'x');
console.log(withX); // ['a', 'b', 'x', 'c', 'd']
// ‼️对比:`.splice()` 会修改原数组
const items2 = ['a', 'b', 'c', 'd'];
const removed = items2.splice(1, 1);
console.log(removed); // ['b']
console.log(items2); // ['a', 'c', 'd'] ❌
提醒:
.splice()返回被移除的元素,而.toSpliced()返回更新后的数据。
为什么在 React 里这很重要
在 React 里,不可变数据是触发组件更新和保持数据可预测性的关键。
// ❌ 直接修改数据
state.items.sort(); // 不重新渲染
// ✅ 使用 toSorted
const sortedItems = state.items.toSorted();
setState({ items: sortedItems }); // 触发重新渲染
这些方法有助于你以不可变的方式对待数组,而无需使用 structuredClone() 或其他的深拷贝手段。
真实案例:在 React 中给任务排序
这里展示了如何在组件中使用 toSorted() 或 toReversed() 方法来安全地展示动态列表:
function TaskList({ tasks }) {
// 优先展示更临近的任务
const recentFirst = tasks?.toReversed() ?? [];
return (
<ul>
{recentFirst.map((task) => (
<li key={task.id}>{task.title}</li>
))}
</ul>
);
}
这避免了在 tasks 是 props 或来源于 state 的情况下,对其修改而产生的 bug。而可选链修饰符(?.)和空值合并运算符(??)也能在 task 是 undefined 的时候避免产生错误。
小小的语法改动,大大的胜利
这些方法并不要求新的心智模型,它们仅仅是你已经在使用的老方法的升级版。如果你在现代环境(抑或使用 Babel 或 SWC),不妨试着使用它们。
浏览器支持
toSorted()、toReversed() 和 toSpliced() 在所有现代环境中都被支持(Chrome/Edge 110+,Safari 16+,Firefox 115+,Node.js 20+)。在老环境中,你可以考虑使用 core-js 作为补丁。
![[译] 终于,JavaScript 有了安全的数组方法](https://img-1252058122.cos.ap-guangzhou.myqcloud.com/blog/article-cover/cmfkkq9a00001uhcg5k63cmt3.jpg)



