array.prototype.with() 是 es2023 引入的原生不可变数组方法,用于安全替换指定索引处的单个元素并返回新数组,不修改原数组、支持负索引、越界抛 rangeerror,性能优于 map 和 slice 拼接。

JavaScript 数组原生没有 with 方法,但你可以用纯函数方式模拟类似语义:返回一个新数组,仅修改指定索引的值,不改变原数组。这符合不可变更新(immutable update)的核心要求——避免副作用、便于状态追踪和 React 等框架优化。
为什么不用直接赋值或 splice?
直接通过索引赋值(如 arr[i] = newVal)会修改原数组;splice() 也是就地修改。两者都违反不可变原则,可能导致意外交互、难以调试或触发不必要的重新渲染。
- 原数组被改写 → 副作用难追踪
- 引用未变 → 浅相等判断失效(如
prevArr === nextArr仍为true) - React.memo 或 useSelector 无法感知变化
手写 with 方法(基于索引更新)
可以给 Array.prototype 添加一个安全的 with 方法,接收索引和新值,返回新数组:
Array.prototype.with = function(index, value) {
if (index = this.length) {
throw new RangeError(`Index ${index} out of bounds for array of length ${this.length}`);
}
const newArr = [...this];
newArr[index] = value;
return newArr;
};
使用示例:
const list = ['a', 'b', 'c']; const newList = list.with(1, 'x'); // ['a', 'x', 'c'] console.log(list); // ['a', 'b', 'c'] ← 原数组不变 console.log(newList); // ['a', 'x', 'c'] ← 新数组
扩展支持对象字段更新(类 withPath)
若数组元素是对象,常需更新嵌套字段(如 list[2].name),可封装一个更通用的 withAt 工具函数:
const withAt = (arr, index, updater) => {
if (index = arr.length) {
throw new RangeError(`Index ${index} out of bounds`);
}
return arr.map((item, i) => i === index ? updater(item) : item);
};
用法:
const users = [{id: 1, name: 'Alice'}, {id: 2, name: 'Bob'}];
const updated = withAt(users, 1, user => ({...user, name: 'Bobby'}));
// → [{id:1,name:'Alice'}, {id:2,name:'Bobby'}]
配合现代 API 更简洁(推荐)
不污染原型、更函数式的方式是直接用展开语法或 toSpliced()(ES2023+):
- 单个元素更新:
[...arr.slice(0, i), newValue, ...arr.slice(i + 1)] - ES2023
toSpliced(真正不可变 splice):arr.toSpliced(i, 1, newValue) - 多个位置更新?组合
map或用withAt多次调用
这些方式无需扩展原型,语义清晰,兼容性可控,更适合生产环境。
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











