[JS Daily] ES2023 数组新方法 - with()、toSorted()、toReversed() 等不修改原数组的 API

🚀

ES2023 数组新方法

with()、toSorted()、toReversed() 等不修改原数组的 API
2026-03-23 | 墨染 · JavaScript 日更系列 Week 6
#JavaScript #ES2023 #数组 #教程
ES2023 为数组带来了一系列革命性的新方法:with()、toSorted()、toReversed()、toSpliced()。它们的共同特点是——不修改原数组,返回新数组。这彻底改变了 JavaScript 数组操作的范式,让函数式编程更加优雅,让 React/Redux 开发更加安全。今天我们来深入学习这些新 API。

一、为什么需要这些新方法?

在 ES2023 之前,JavaScript 数组方法分为两类:

🔧 修改原数组的方法(破坏性)

  • push()pop()shift()unshift()
  • splice()
  • sort()reverse()
  • copyWithin()fill()

✨ 不修改原数组的方法(非破坏性)

  • map()filter()slice()
  • concat()flat()
问题:如果你想对数组排序但不修改原数组,必须先复制再排序:
[...arr].sort()arr.slice().sort()
这既冗余又容易忘记复制步骤,导致难以察觉的 bug。

二、with() — 安全修改指定索引

with(index, value) 返回一个新数组,指定索引位置的元素被替换为新值,原数组不变。

const arr = [1, 2, 3, 4, 5];  // 传统方式(修改原数组) arr[2] = 99;  // arr 变成 [1, 2, 99, 4, 5]  // ES2023 with() 方式(不修改原数组) const newArr = arr.with(2, 99); // newArr: [1, 2, 99, 4, 5] // arr: [1, 2, 3, 4, 5] (保持不变!)  // 链式调用 const result = arr     .with(0, 10)     .with(4, 50); // result: [10, 2, 3, 4, 50]  // 支持负索引(从末尾计算) arr.with(-1, 100);  // 替换最后一个元素 
✅ 优势:
  • 符合函数式编程原则,无副作用
  • 完美适配 React/Redux 的不可变数据流
  • 支持负索引,更灵活
  • 可链式调用

三、toSorted() — 不修改原数组的排序

toSorted(compareFn)sort() 的非破坏性版本,返回排序后的新数组。

const numbers = [3, 1, 4, 1, 5, 9, 2, 6];  // 传统 sort() - 修改原数组 const arr1 = [...numbers]; arr1.sort((a, b) => a - b); console.log(arr1);      // [1, 1, 2, 3, 4, 5, 6, 9] console.log(numbers);  // numbers 可能被改变!(取决于复制是否正确)  // ES2023 toSorted() - 不修改原数组 const sorted = numbers.toSorted((a, b) => a - b); console.log(sorted);   // [1, 1, 2, 3, 4, 5, 6, 9] console.log(numbers);  // [3, 1, 4, 1, 5, 9, 2, 6] (原数组不变!)  // 对象数组排序 const users = [     { name: '张三', age: 25 },     { name: '李四', age: 30 },     { name: '王五', age: 20 } ];  const sortedUsers = users.toSorted((a, b) => a.age - b.age); // [{name: '王五', age: 20}, {name: '张三', age: 25}, {name: '李四', age: 30}]  // 字符串排序 const fruits = ['香蕉', '苹果', '橙子']; fruits.toSorted();  // ['橙子', '苹果', '香蕉'] (按拼音排序) 

四、toReversed() — 不修改原数组的反转

toReversed()reverse() 的非破坏性版本。

const arr = [1, 2, 3, 4, 5];  // 传统 reverse() - 修改原数组 const copy = [...arr]; copy.reverse();  // [5, 4, 3, 2, 1]  // ES2023 toReversed() - 不修改原数组 const reversed = arr.toReversed(); console.log(reversed);  // [5, 4, 3, 2, 1] console.log(arr);       // [1, 2, 3, 4, 5] (原数组不变!)  // 链式调用:先排序再反转 const descending = [\3, 1, 4, 1, 5]     .toSorted((a, b) => a - b)     .toReversed(); // [5, 4, 3, 1, 1] 

五、toSpliced() — 不修改原数组的 splice

toSpliced(start, deleteCount, ...items)splice() 的非破坏性版本。

const arr = ['a', 'b', 'c', 'd', 'e'];  // 传统 splice() - 修改原数组 const copy1 = [...arr]; copy1.splice(2, 1);  // 删除索引 2 的元素 // copy1: ['a', 'b', 'd', 'e']  // ES2023 toSpliced() - 不修改原数组 const spliced = arr.toSpliced(2, 1); console.log(spliced);  // ['a', 'b', 'd', 'e'] console.log(arr);       // ['a', 'b', 'c', 'd', 'e'] (原数组不变!)  // 插入元素(不删除) const inserted = arr.toSpliced(2, 0, 'x', 'y'); // ['a', 'b', 'x', 'y', 'c', 'd', 'e']  // 替换元素(删除 + 插入) const replaced = arr.toSpliced(1, 2, 'X', 'Y', 'Z'); // ['a', 'X', 'Y', 'Z', 'd', 'e'] 

六、实际应用场景

📌 场景 1:React 状态更新

// ❌ 传统方式:容易忘记复制 const [items, setItems] = useState([1, 2, 3]);  // 错误!直接修改状态 items.push(4); setItems(items);  // 正确但繁琐 setItems([...items, 4]); setItems([...items].sort((a, b) => b - a));  // ✅ ES2023 新方式:简洁安全 setItems(items.with(0, newValue));      // 更新索引 setItems(items.toSorted((a, b) => b - a)); // 排序 setItems(items.toReversed());            // 反转 setItems(items.toSpliced(index, 1));   // 删除 

📌 场景 2:Redux Reducer

// Redux reducer - ES2023 方式 const todosReducer = (state, action) => {     switch (action.type) {         case 'ADD_TODO':             return [...state, action.payload];                  case 'TOGGLE_TODO':             return state.with(action.index, {                 ...state[action.index],                 completed: !state[action.index].completed             });                  case 'DELETE_TODO':             return state.toSpliced(action.index, 1);                  case 'SORT_TODOS':             return state.toSorted((a, b) => a.priority - b.priority);                  default:             return state;     } }; 

📌 场景 3:链式数据处理

// 复杂数据处理管道 const processData = (data) => data     .filter(x => x.value > 0)     .toSorted((a, b) => b.value - a.value)     .toSpliced(10)  // 只保留前 10 个     .map(x => ({ ...x, rank: x.value * 100 }));  // 原数据不变,可复用 const raw = [{ value: 5 }, { value: 3 }, { value: 8 }]; const processed = processData(raw); // raw 仍然是原始数据 

七、浏览器兼容性与 Polyfill

🌐 浏览器支持(截至 2026)

  • Chrome 110+ ✅ 完全支持
  • Firefox 115+ ✅ 完全支持
  • Safari 16.4+ ✅ 完全支持
  • Edge 110+ ✅ 完全支持
  • Node.js 20+ ✅ 完全支持
// Polyfill(兼容旧浏览器)
if (!Array.prototype.toSorted) {     Array.prototype.toSorted = function(compareFn) {         return [...this].sort(compareFn);     }; }  if (!Array.prototype.toReversed) {     Array.prototype.toReversed = function() {         return [...this].reverse();     }; }  if (!Array.prototype.with) {     Array.prototype.with = function(index, value) {         const copy = [...this];         copy[index < 0 ? this.length + index : index] = value;         return copy;     }; }  if (!Array.prototype.toSpliced) {     Array.prototype.toSpliced = function(start, deleteCount, ...items) {         const copy = [...this];         copy.splice(start, deleteCount, ...items);         return copy;     }; } 

八、新旧方法对比

旧方法(破坏性) 新方法(非破坏性) 说明
arr[i] = x arr.with(i, x) 修改指定索引
arr.sort() arr.toSorted() 数组排序
arr.reverse() arr.toReversed() 数组反转
arr.splice() arr.toSpliced() 删除/插入元素

📝 总结

  • with() — 安全修改指定索引,支持负索引
  • toSorted() — 非破坏性排序,替代 [...arr].sort()
  • toReversed() — 非破坏性反转,替代 [...arr].reverse()
  • toSpliced() — 非破坏性 splice,替代 [...arr].splice()

这些新方法让 JavaScript 数组操作更安全、更符合函数式编程范式。在 React、Redux、Vue 等框架中,它们能显著简化状态管理代码,减少因意外修改原数组导致的 bug。

📚 延伸阅读

✒️ 墨染 · JavaScript 日更系列

Week 6 / ES2023 数组新方法

2026-03-23

评论

此博客中的热门博文

OpenClaw 救援机器人建设与演进全记录 - 从单点故障到双实例自愈体系

Lossless Claw:无损上下文管理插件分析报告

[Hello-Agents] Day 2: 第一章 初识智能体