瀏覽器悄悄支持了這個(gè)新 API,讓數(shù)組操作性能翻倍
在前端開(kāi)發(fā)中,尤其是在使用 React 或 Vue 等現(xiàn)代框架時(shí),我們被反復(fù)告知一個(gè)黃金法則:不要直接修改狀態(tài)(Don’t mutate state)。這意味著,當(dāng)我們需要更新一個(gè)數(shù)組中的某個(gè)元素時(shí),我們不能這樣做:
// ? 錯(cuò)誤的做法!這會(huì)直接修改原始數(shù)組
const state = ['a', 'b', 'c', 'd'];
state[2] = 'x'; // 這是一個(gè)“突變” (mutation)為什么?因?yàn)檫@會(huì)破壞狀態(tài)的可預(yù)測(cè)性,讓框架的變更檢測(cè)機(jī)制“失靈”,導(dǎo)致各種難以追蹤的 Bug。
為了遵循“不可變性”(Immutability)原則,我們多年來(lái)一直依賴(lài)一些經(jīng)典的“曲線(xiàn)救國(guó)”方案。但瀏覽器已經(jīng)悄悄地支持了一個(gè)全新的原生 API,它不僅讓代碼更優(yōu)雅,還能在某些場(chǎng)景下讓性能得到顯著提升。
它就是 —— Array.prototype.with(index, value)。

以前是怎么做的?
在 with() 出現(xiàn)之前,要“不可變地”更新數(shù)組中的一個(gè)元素,我們通常有兩種主流方法:
方法一:使用 map()
map() 方法會(huì)返回一個(gè)全新的數(shù)組,是我們的老朋友了。

- 優(yōu)點(diǎn):非常直觀(guān),函數(shù)式編程的典范。
- 缺點(diǎn):性能開(kāi)銷(xiāo)大。即使我們只改變一個(gè)元素,map() 依然會(huì)遍歷整個(gè)數(shù)組,從頭到尾創(chuàng)建一個(gè)新數(shù)組。當(dāng)數(shù)組包含成千上萬(wàn)個(gè)元素時(shí),這種浪費(fèi)是顯而易見(jiàn)的。
方法二:使用展開(kāi)語(yǔ)法 ... 或 slice()
這是更常見(jiàn)、性能也稍好一些的方法。我們先復(fù)制,再修改復(fù)制品。
const oldArray = ['apple', 'banana', 'orange', 'grape'];
// 使用展開(kāi)語(yǔ)法
const newArray = [...oldArray]; // 1. 創(chuàng)建一個(gè)淺拷貝
newArray[2] = 'mango'; // 2. 修改拷貝后的數(shù)組
// 或者使用 slice()
// const newArray = oldArray.slice();
// newArray[2] = 'mango';
console.log(newArray); // ['apple', 'banana', 'mango', 'grape']
console.log(oldArray); // ['apple', 'banana', 'orange', 'grape'] (未被改變)- 優(yōu)點(diǎn):比 map() 更直接,意圖更清晰。
- 缺點(diǎn):代碼有點(diǎn)啰嗦,需要兩步操作(先復(fù)制,再賦值)。而且,它同樣需要完整地遍歷并復(fù)制整個(gè)原始數(shù)組,性能瓶頸依然存在。
Array.prototype.with()
現(xiàn)在,讓我們看看 with() 是如何將上述操作簡(jiǎn)化為一步的。
with(index, value) 方法接收兩個(gè)參數(shù):要替換的元素的索引和新值。它會(huì)返回一個(gè)全新的數(shù)組,其中指定索引處的元素已被替換,而原始數(shù)組保持不變。
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'] (完美!原始數(shù)組安然無(wú)恙)看看這代碼!
- 優(yōu)雅:一行代碼,一個(gè)方法,清晰地表達(dá)了“用一個(gè)新值替換某個(gè)位置的元素并得到一個(gè)新數(shù)組”的意圖。
- 不可變:它天生就是為不可變操作而設(shè)計(jì)的。
- 高性能:這才是它的殺手锏!
性能翻倍的秘密
答案在于,with() 向 JavaScript 引擎?zhèn)鬟f了一個(gè)更明確的信號(hào)。
當(dāng)我們使用 [...oldArray] 時(shí),我們告訴引擎:“我需要一個(gè)這個(gè)數(shù)組的完整克隆品,所有元素都得復(fù)制一遍。” 引擎只能老老實(shí)實(shí)地分配新內(nèi)存,然后遍歷拷貝。
而當(dāng)我們使用 oldArray.with(2, 'mango') 時(shí),我們告訴引擎:“我需要一個(gè)和 oldArray 幾乎一樣的新數(shù)組,只有一個(gè)位置不同。”
這個(gè)明確的信號(hào)使得 JavaScript 引擎(如 V8)可以進(jìn)行底層優(yōu)化。引擎不必真的去完整復(fù)制所有元素。它可以創(chuàng)建一個(gè)新的數(shù)組結(jié)構(gòu),內(nèi)部指向舊數(shù)組的大部分?jǐn)?shù)據(jù),只為那個(gè)被改變的元素分配新的空間。這種“寫(xiě)時(shí)復(fù)制”(Copy-on-Write)的優(yōu)化策略,在處理大型數(shù)組時(shí),可以極大地減少內(nèi)存分配和復(fù)制操作,從而帶來(lái)巨大的性能提升。
對(duì)于一個(gè)包含 100 萬(wàn)個(gè)元素的數(shù)組,map() 和 slice() 需要復(fù)制 100 萬(wàn)個(gè)元素引用,而 with() 的理想開(kāi)銷(xiāo)接近于只處理 1 個(gè)元素。這就是“性能翻倍”說(shuō)法的底氣所在。
Array.prototype.with() 和它的伙伴們,不僅僅是幾個(gè)語(yǔ)法糖。它們代表了 JavaScript 語(yǔ)言本身對(duì)“不可變性”這一重要編程范式的擁抱和認(rèn)可。




























