浏览器悄悄支持了这个新API,让数组操作性能翻倍


在前端开发中,尤其是在使用 React 或 Vue 等现代框架时,我们被反复告知一个黄金法则: ** 不要直接修改状态(Don’t mutate
state) ** 。这意味着,当我们需要更新一个数组中的某个元素时,我们不能这样做:

// ❌ 错误的做法!这会直接修改原始数组  
const state = ['a', 'b', 'c', 'd'];  
state[2] = 'x'; // 这是一个“突变” (mutation)  

为什么?因为这会破坏状态的可预测性,让框架的变更检测机制“失灵”,导致各种难以追踪的 Bug。

为了遵循“不可变性”(Immutability)原则,我们多年来一直依赖一些经典的“曲线救国”方案。但浏览器已经悄悄地支持了一个全新的原生
API,它不仅让代码更优雅,还能在某些场景下让性能得到显著提升。

它就是 —— Array.prototype.with(index, value)

以前是怎么做的?

with() 出现之前,要“不可变地”更新数组中的一个元素,我们通常有两种主流方法:

** 方法一:使用 map() **

map() 方法会返回一个全新的数组,是我们的老朋友了。

  • ** 优点 ** :非常直观,函数式编程的典范。
  • ** 缺点 ** : ** 性能开销大 ** 。即使我们只改变一个元素, map() 依然会遍历整个数组,从头到尾创建一个新数组。当数组包含成千上万个元素时,这种浪费是显而易见的。

** 方法二:使用展开语法 ... slice() **

这是更常见、性能也稍好一些的方法。我们先复制,再修改复制品。

const oldArray = ['apple', 'banana', 'orange', 'grape'];  
  
// 使用展开语法  
const newArray = [...oldArray]; // 1. 创建一个浅拷贝  
newArray[2] = 'mango';          // 2. 修改拷贝后的数组  
  
// 或者使用 slice()  
// const newArray = oldArray.slice();  
// newArray[2] = 'mango';  
  
console.log(newArray); // ['apple', 'banana', 'mango', 'grape']  
console.log(oldArray); // ['apple', 'banana', 'orange', 'grape'] (未被改变)  
  • ** 优点 ** :比 map() 更直接,意图更清晰。
  • ** 缺点 ** :代码有点啰嗦,需要两步操作(先复制,再赋值)。而且,它同样需要 ** 完整地遍历并复制 ** 整个原始数组,性能瓶颈依然存在。

Array.prototype.with()

现在,让我们看看 with() 是如何将上述操作简化为一步的。

with(index, value) 方法接收两个参数:要替换的元素的 ** 索引 ** 和 ** 新值 ** 。它会返回一个 **
全新的数组 ** ,其中指定索引处的元素已被替换,而原始数组保持不变。

const oldArray = ['apple', 'banana', 'orange', 'grape'];  
  
const newArray = oldArray.with(2, 'mango');  
  
console.log(newArray); // ['apple', 'banana', 'mango', 'grape']  
console.log(oldArray); // ['apple', 'banana', 'orange', 'grape'] (完美!原始数组安然无恙)  

** 看看这代码! **

  • ** 优雅 ** :一行代码,一个方法,清晰地表达了“用一个新值替换某个位置的元素并得到一个新数组”的意图。
  • ** 不可变 ** :它天生就是为不可变操作而设计的。
  • ** 高性能 ** :这才是它的杀手锏!

性能翻倍的秘密

答案在于, with() 向 JavaScript 引擎传递了一个 ** 更明确的信号 ** 。

当我们使用 [...oldArray] 时,我们告诉引擎:“我需要一个这个数组的完整克隆品,所有元素都得复制一遍。”
引擎只能老老实实地分配新内存,然后遍历拷贝。

而当我们使用 oldArray.with(2, 'mango') 时,我们告诉引擎:“我需要一个和 oldArray ** 几乎一样
** 的新数组, ** 只有一个位置不同 ** 。”

这个明确的信号使得 JavaScript 引擎(如
V8)可以进行底层优化。引擎不必真的去完整复制所有元素。它可以创建一个新的数组结构,内部指向旧数组的大部分数据,只为那个被改变的元素分配新的空间。这种“写时复制”(Copy-
on-Write)的优化策略,在处理大型数组时,可以极大地减少内存分配和复制操作,从而带来巨大的性能提升。

对于一个包含 100 万个元素的数组, map() slice() 需要复制 100 万个元素引用,而 with()
的理想开销接近于只处理 1 个元素。这就是“性能翻倍”说法的底气所在。

Array.prototype.with() 和它的伙伴们,不仅仅是几个语法糖。它们代表了 JavaScript
语言本身对“不可变性”这一重要编程范式的拥抱和认可。