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。 |
评论
发表评论