(譯)最全的javaScript中對象深度拷貝指南

原文地址

我在JavaScript中如何拷貝一個(gè)對象?這是一個(gè)簡單的問題,但是答案確不是很簡單。

Did you ever wanted to create a deep copy of an object in JavaScript? There is a way, but you are not gonna like it...
I feel like we need something better ?? pic.twitter.com/IDazhB8BKJ
— Surma (@dassurma) 2018年1月22日

引用調(diào)用

JavaScript通過引用來傳遞所有的值。如果你不知道這是什么意思,下面有個(gè)例子??:

function mutate(obj) {
  obj.a = true;
}

const obj = {a: false};
mutate(obj)
console.log(obj.a); // prints true

mutate方法改變了作為參數(shù)傳遞進(jìn)來的這個(gè)對象。在值調(diào)用環(huán)境中,這個(gè)函數(shù)式傳遞的這個(gè)值,所以相關(guān)于這個(gè)函數(shù)是執(zhí)行了一個(gè)拷貝。這個(gè)函數(shù)使這個(gè)對象對外是不可見的。但是在像js的這種引用調(diào)用的環(huán)境,將會(huì)得到這個(gè)真實(shí)的對象。所以最后控制臺(tái)輸出的為true。

不過,你想要保持你的原始的對象,其他函數(shù)只是創(chuàng)建了這個(gè)對象的拷貝。

在下面就介紹幾種深度拷貝的方式

JSON.parse

第一種最古老的方式就是通過將對象轉(zhuǎn)換為JSON字符串格式,然后將其轉(zhuǎn)換為對象。

let obj = { name : "huyue" };
let copy = JSON.parse(JSON.stringify(obj));
obj.name = 'hy';
console.log(copy);//'huyue'

但是這種方式有些問題

問題一:當(dāng)對象中出現(xiàn)循環(huán)引用的時(shí)候會(huì)報(bào)錯(cuò)。盡管你可能認(rèn)為你不會(huì)如此使用,但是那些還是會(huì)很容易發(fā)生。比如當(dāng)你構(gòu)建了樹狀類型的數(shù)據(jù)機(jī)構(gòu)的時(shí)候,其中一個(gè)節(jié)點(diǎn)引用了父級的某個(gè)節(jié)點(diǎn),這樣就出現(xiàn)了這種場景。

const x = {};
const y = {x};
x.y = y; // Cycle: x.y.x.y.x.y.x.y.x...
const copy = JSON.parse(JSON.stringify(x)); // throws!

問題二:這種方式只支持基礎(chǔ)類型,像Map,SetRegExp,DateArrayBuffer,函數(shù)對象等都會(huì)在序列化的時(shí)候弄丟

var source = { name:function(){console.log(1);}, child:{ name:"child" } } 
var target = JSON.parse(JSON.stringify(source));
console.log(target.name); //undefined

注:JSON對象是ES5中引入的新的類型(支持的瀏覽器為IE8+),瀏覽器支持情況

結(jié)構(gòu)化克隆

結(jié)構(gòu)化克隆是一個(gè)現(xiàn)有算法,它是被用來把一個(gè)領(lǐng)域的值傳遞到另一個(gè)。比如,你調(diào)用postMessage去發(fā)送一個(gè)消息給另一個(gè)窗口或WebWorker。結(jié)構(gòu)化很好的地方就是他能處理循環(huán)對象,并且支持多種內(nèi)置類型。

MessageChannel

我們通過MessageChannel創(chuàng)建一個(gè)新的消息通道,并通過它的兩個(gè)MessagePort屬性來發(fā)送數(shù)據(jù)和獲取數(shù)據(jù)。我們接受到的這條信息就是會(huì)包含原始數(shù)據(jù)的結(jié)構(gòu)化克隆對象。但是這種方式是異步情況,所以下面例子使用的async awit實(shí)現(xiàn)了的,也可參見在線地址

function structuralClone(obj) {
  return new Promise(resolve => {
    const {port1, port2} = new MessageChannel();
    port2.onmessage = ev => resolve(ev.data);
    port1.postMessage(obj);
  });
}

const obj = /* ... */;
const clone = await structuralClone(obj);

注:瀏覽器支持IE10+,瀏覽器支持力度情況

History API

如果你曾經(jīng)使用過history.pushState()去構(gòu)建一個(gè)SPA(單頁應(yīng)用),你應(yīng)該會(huì)知道能提供一個(gè)狀態(tài)對象去保存這URL。這個(gè)狀態(tài)對象就是結(jié)構(gòu)化克隆,并且還是同步的。我們一定要小心,避免在使用這個(gè)狀態(tài)對象的時(shí)候去混淆任何程序邏輯,所以我們需要在我們克隆了之后去恢復(fù)這個(gè)原始的狀態(tài)對象。為了防止發(fā)生任何事件,請使用history.replaceState()而不是history.pushState()。replaceState和pushState區(qū)別詳情

function structuralClone(obj) {
  const oldState = history.state;
  history.replaceState(obj, document.title);
  const copy = history.state;
  history.replaceState(oldState, document.title);//就是為了恢復(fù)原始狀態(tài)對象,避免干擾
  return copy;
}

const obj = /* ... */;
const clone = structuralClone(obj);

為了復(fù)制一個(gè)對象,使用瀏覽器的引擎感覺有些笨拙。不過你還是可以這么做,有些事情還是得注意,因?yàn)镾afari瀏覽器會(huì)限制30秒內(nèi)調(diào)用relaceState的次數(shù)上限為100次

注:瀏覽器支持IE10+,瀏覽器支持力度情況

Notification API

這種方式由Jeremy Banks建議,通知接口用于向用戶配置和顯示桌面通知,這個(gè)消息通知的api有一個(gè)與它們相關(guān)的數(shù)據(jù)對象被克隆??吹竭@,可能有的人表示有點(diǎn)不是很明白,那么可以點(diǎn)擊在線示例

function structuralClone(obj) {
  return new Notification('', {data: obj, silent: true}).data;
}

const obj = /* ... */;
const clone = structuralClone(obj);

它基本觸犯了瀏覽器內(nèi)的權(quán)限機(jī)制,所以懷疑這個(gè)可能會(huì)非常慢。出于某種原因,Safari瀏覽器總是返回undefined。可以使用在線示例

注:瀏覽器不支持IE,瀏覽器支持力度情況

性能測試

對上面幾種方式進(jìn)行性能測試看哪種方式性能最高。剛開始嘗試時(shí),我拿一個(gè)小JSON對象,并通過這些克隆對象一千次的不同方式來進(jìn)行測試。幸運(yùn)的是, Mathias Bynens告訴我在給一個(gè)對象增加屬性的時(shí)候V8是有緩存。為了確保不走緩存,所以我寫了一個(gè)[函數(shù)](a function that generates objects of given depth and width using random key names),使用隨機(jī)鍵名稱生成給定深度和寬度的對象,并重新運(yùn)行測試示例

圖表統(tǒng)計(jì)

image

image

image

總結(jié)

  • 如果你不會(huì)使用循環(huán)對象并且不會(huì)使用內(nèi)置類型,那么還是推薦使用JSON.parse。并且瀏覽器兼容性還更好(ie8+)
  • 如果在考慮性能和瀏覽器兼容,MessageChannel是最好的選擇。(ie10+)
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡書系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

  • 函數(shù)和對象 1、函數(shù) 1.1 函數(shù)概述 函數(shù)對于任何一門語言來說都是核心的概念。通過函數(shù)可以封裝任意多條語句,而且...
    道無虛閱讀 4,926評論 0 5
  • Spring Cloud為開發(fā)人員提供了快速構(gòu)建分布式系統(tǒng)中一些常見模式的工具(例如配置管理,服務(wù)發(fā)現(xiàn),斷路器,智...
    卡卡羅2017閱讀 136,502評論 19 139
  • 本博客轉(zhuǎn)自:「作者:若愚鏈接:https://zhuanlan.zhihu.com/p/22361337來源:知乎...
    韓寶億閱讀 2,929評論 0 3
  • 《輕斷食》:別一味相信、但確實(shí)有效,跟硬派健身配合著做,相當(dāng)有效果! 《硬派健身》 《京都奈良一本就足夠》 《暢游...
    cloverblue閱讀 176評論 0 0
  • 我沒有低頭看著手機(jī), 我只是無意間看到了窗外一個(gè)真實(shí)的世界。
    安小邁閱讀 343評論 0 0

友情鏈接更多精彩內(nèi)容