太卷了!瀏覽器也支持原生的深拷貝API了?
大家好,我是 ConardLi,今天來聊一個前端老生常談的話題,深拷貝。
在以前,由于瀏覽器并未對這個能力提供原生支持,所以它經(jīng)常出現(xiàn)在 手寫XXX 這樣的面試題中,我之前也為它專門寫過一篇文章:
不過,我們成功的把瀏覽器給卷了,現(xiàn)在它給我們提供了一個原生的深拷貝 API:structuredClone。
淺拷貝
再看深拷貝之前,我們還是先來簡單回顧一下淺拷貝和深拷貝的區(qū)別(不要嫌我啰嗦,主要為了照顧新手同學,如果有基礎的同學直接跳過)。
淺拷貝:創(chuàng)建一個新對象,這個對象有著原始對象屬性值的一份精確拷貝。如果屬性是基本類型,拷貝的就是基本類型的值,如果屬性是引用類型,拷貝的就是內(nèi)存地址 ,所以如果其中一個對象改變了這個地址,就會影響到另一個對象。

將一個對象從內(nèi)存中完整的拷貝一份出來,從堆內(nèi)存中開辟一個新的區(qū)域存放新對象,且修改新對象不會影響原對象。

在代碼里我們復制一個對象可能用的最多的就是擴展運算符 ...,這就是一種淺拷貝。
const?myOriginal?=?{
??someProp:?"code秘密花園",
??anotherProp:?{
????withAnotherProp:?1,
????andAnotherProp:?true
??}
};
const?myShallowCopy?=?{...myOriginal};
我們直接在淺拷貝對象上添加或更改屬性只會影響拷貝副本,而不會影響原始值:
myShallowCopy.aNewProp?=?"ConardLi";
console.log(myOriginal.aNewProp)
//?^?logs?`undefined`
如果我們更改之前的引用對象屬性,副本和原始值雙方都會影響:
myShallowCopy.anotherProp.aNewProp?=?"ConardLi";
console.log(myOriginal.anotherProp.aNewProp)?
//?^?logs?`ConardLi`
本質(zhì)上,就是拷貝對原始類型(string、number、bigint、boolean、undefined、symbol、null)和引用類型(object、array 等)的處理不一樣,這倆的區(qū)別,可以看看這篇文章,這里就不過多展開了。
深拷貝
與淺拷貝相對的就是深拷貝,深拷貝算法也會一個一個地拷貝一個對象的屬性,但是當它拷貝對另一個對象的引用時會遞歸調(diào)用,同時創(chuàng)建該引用類型的一個副本,這可以有效避免我們在代碼里共享一個意想不到的對象引用。
在以前,我們需要依賴一些第三方庫來實現(xiàn)深拷貝,比如 Lodash 的 cloneDeep() 函數(shù),或者可能大多數(shù)人用的基于是 JSON 的 hack :
const?myDeepCopy?=?JSON.parse(JSON.stringify(myOriginal));
但是這種方法缺點很多:
循環(huán)引用: JSON.stringify()的對象中如果有循環(huán)引用會拋出異常Converting circular structure to JSON。其他數(shù)據(jù)類型: JSON.stringify()無法拷貝Map、Set、RegExp這些特殊數(shù)據(jù)類型。函數(shù): JSON.stringify()會默認移除函數(shù)。
structuredClone
現(xiàn)在,structuredClone API 已經(jīng)成為了一個 HTML 規(guī)范中的標準提案,用它可以輕松實現(xiàn)一個深拷貝,并且也默認解決了循環(huán)引用等問題、支持了很多默認的數(shù)據(jù)類型。
//?Create?an?object?with?a?value?and?a?circular?reference?to?itself.
const?original?=?{?name:?"MDN"?};
original.itself?=?original;
//?Clone?it
const?clone?=?structuredClone(original);
console.assert(clone?!==?original);?//?the?objects?are?not?the?same?(not?same?identity)
console.assert(clone.name?===?"MDN");?//?they?do?have?the?same?values
console.assert(clone.itself?===?clone);?//?and?the?circular?reference?is?preserved
并且,相比 JSON.parse() ,structuredClone API 的性能更好,特別是在處理一些更大復雜的對象的時候,所以我們可以用它來作為代碼里深拷貝的默認方法啦,為了兼容性考慮,可以用 JSON.stringify 或者其他工具函數(shù)作為備用。
不過,這個 API 也并不完美,它也有些缺點:
原型:無法拷貝對象的原型鏈。 函數(shù):無法拷貝函數(shù)。 不可克隆:并沒有支持所有類型的拷貝,比如 Error。
當然,大部分實際的需求場景中,我們沒必要拷貝這些東西,估計這些也就只能出現(xiàn)在面試題里面了。。。先看怎么做的話,還是可以回去看我這篇文章:如何寫出一個驚艷面試官的深拷貝
兼容性
目前,主流瀏覽器(Chrome、Firefox、Safari)都已經(jīng)在 release 版本支持了這個 API,Firefox 也已經(jīng)在其 94 穩(wěn)定版本支持了。
另外,Node 17 和 Deno 1.14 也已經(jīng)實現(xiàn)了這個 API,未來一定會成為一個被廣泛使用的 API 的,你可以放心大膽的用了。

參考
https://caniuse.com/?search=structuredClone https://web.dev/structured-clone/
往期推薦

解密初、中、高級程序員的進化之路(前端)

程序員一定會有35歲危機嗎?

近 20k Star的項目說不做就不做了,但總結的內(nèi)容值得借鑒

但凡早知道這28個網(wǎng)站,都不至于學得那么不扎實
如果你覺得這篇內(nèi)容對你挺有啟發(fā),我想邀請你幫我三個小忙:
點個「在看」,讓更多的人也能看到這篇內(nèi)容(喜歡不點在看,都是耍流氓 -_-)
歡迎加我微信「huab119」拉你進技術群,長期交流學習...
關注公眾號「前端勸退師」,持續(xù)為你推送精選好文,也可以加我為好友,隨時聊騷。

如果覺得這篇文章還不錯,來個【轉發(fā)、收藏、在看】三連吧,讓更多的人也看到~

