JavaScript深度復(fù)制(deep clone)的實(shí)現(xiàn)方法
在代碼復(fù)用模式里面有一種叫做“復(fù)制屬性模式”(copying properties pattern)。談到代碼復(fù)用的時(shí)候,很有可能想到的是代碼的繼承性(inheritance),但重要的是要記住其最終目標(biāo)——我們要復(fù)用代碼。繼承性只是實(shí)現(xiàn)代碼復(fù)用的一種手段,而不是唯一的方法。復(fù)制屬性也是一種復(fù)用模式,它跟繼承性是有所不同的。這種模式中,對(duì)象將從另外一個(gè)在對(duì)象中獲取成員,其方法是僅需將其復(fù)制即可。用過jQuery的都知道,它有一個(gè)$.extend()方法,它的用途除了擴(kuò)展第三方插件之外,還可以用來復(fù)制屬性的。下面我們來看一個(gè)extend()函數(shù)的實(shí)現(xiàn)代碼(注意這里的并不是jQuery的源碼,只是一個(gè)簡(jiǎn)單的示例):
function extend(parent, child) { var i; //如果不傳入第二參數(shù)child //那么就創(chuàng)建一個(gè)新的對(duì)象 child = child || {}; //遍歷parent對(duì)象的所有屬性 //并且過濾原型上的屬性 //然后將自身屬性復(fù)制到child對(duì)象上 for(i in parent) { if(parent.hasOwnProperty(i)) { child[i] = parent[i]; } } //返回目標(biāo)對(duì)象child return child; }
上面的代碼是一個(gè)簡(jiǎn)單的實(shí)現(xiàn),它僅遍歷父對(duì)象的成員并將其復(fù)制到子對(duì)象中去。下面我們用上面的extend()方法來測(cè)試一下:
var dad = {name: "Adam"}; var kid = extend(dad); console.log(kid.name); //Adam
我們發(fā)現(xiàn),extend()方法已經(jīng)可以正常工作了。但是有一個(gè)問題,上面給出的是一種所謂的淺復(fù)制(shallow clone)。在使用淺復(fù)制的時(shí)候,如果改變了子對(duì)象的屬性,并且該屬性恰好又是一個(gè)對(duì)象,那么這種操作也會(huì)修改父對(duì)象,單是很多情況這不是我們想要的結(jié)果??紤]下列情況:
var dad = { counts: [1, 2, 3], reads: {paper: true} }; var kid = extend(dad) //調(diào)用extend()方法將dad的屬性復(fù)制到kid上面 kid.counts.push(4); //把4追加到kid.counts數(shù)組里面 console.log(dad.counts); //[1, 2, 3, 4]
通過上面的例子,我們會(huì)發(fā)現(xiàn),修改了kid.counts屬性以后(把元素4追加進(jìn)去了),dad.counts也會(huì)受到影響。這是因?yàn)樵谑褂脺\復(fù)制的時(shí)候,由于對(duì)象是通過引用傳遞的,即kid.counts和dad.counts指向的是同一個(gè)數(shù)組(或者說在內(nèi)存上他們指向同一個(gè)堆的地址)。
下面,讓我們修改extend()函數(shù)以實(shí)現(xiàn)深度復(fù)制。我們需要做的事情就是檢查父對(duì)象的每一個(gè)屬性,如果該屬性恰好是對(duì)象的話,那么就遞歸復(fù)制出該對(duì)象的屬性。另外,還需要檢測(cè)該對(duì)象是否為一個(gè)數(shù)組,這是因?yàn)閿?shù)組的字面量創(chuàng)建方式和對(duì)象的字面量創(chuàng)建方式不一樣,前者是[],后者是{}。檢測(cè)數(shù)組可以使用Object.prototype.toString()方法進(jìn)行檢測(cè),如果是數(shù)組的話,他會(huì)返回"[object Array]"。下面我們來看一下深度復(fù)制版本的extend()函數(shù):
function extendDeep(parent, child) { child = child || {}; for(var i in parent) { if(parent.hasOwnProperty(i)) { //檢測(cè)當(dāng)前屬性是否為對(duì)象 if(typeof parent[i] === "object") { //如果當(dāng)前屬性為對(duì)象,還要檢測(cè)它是否為數(shù)組 //這是因?yàn)閿?shù)組的字面量表示和對(duì)象的字面量表示不同 //前者是[],而后者是{} child[i] = (Object.prototype.toString.call(parent[i]) === "[object Array]") ? [] : {}; //遞歸調(diào)用extend extendDeep(parent[i], child[i]); } else { child[i] = parent[i]; } } } return child; }
好了,深度復(fù)制的函數(shù)已經(jīng)寫好了,下面來測(cè)試一下看是否能夠預(yù)期那樣子工作,即是否可以實(shí)現(xiàn)深度復(fù)制:
var dad = { counts: [1, 2, 3], reads: {paper: true} }; var kid = extendDeep(dad); //修改kid的counts屬性和reads屬性 kid.counts.push(4); kid.reads.paper = false; console.log(kid.counts); //[1, 2, 3, 4] console.log(kid.reads.paper); //false console.log(dad.counts); //[1, 2, 3] console.log(dad.reads.paper); //true
通過上面例子,我們可以發(fā)現(xiàn),即使修改了子對(duì)象的kid.counts和kid.reads,父對(duì)象的dad.counts和kid.reads并沒有改變,因此我們的目的實(shí)現(xiàn)了。
下面來總結(jié)一下實(shí)現(xiàn)深復(fù)制的的基本思路:
1.檢測(cè)當(dāng)前屬性是否為對(duì)象
2.因?yàn)閿?shù)組是特殊的對(duì)象,所以,在屬性為對(duì)象的前提下還需要檢測(cè)它是否為數(shù)組。
3.如果是數(shù)組,則創(chuàng)建一個(gè)[]空數(shù)組,否則,創(chuàng)建一個(gè){}空對(duì)象,并賦值給子對(duì)象的當(dāng)前屬性。然后,遞歸調(diào)用extendDeep函數(shù)。
上面例子使我們自己使用遞歸算法實(shí)現(xiàn)的一種深度復(fù)制方法。事實(shí)上,ES5新增的JSON對(duì)象提供的兩個(gè)方法也可以實(shí)現(xiàn)深度復(fù)制,分別是JSON.stringify()和JSON.parse();前者用來將對(duì)象轉(zhuǎn)成字符串,后者則把字符串轉(zhuǎn)換成對(duì)象。下面我們使用該方法來實(shí)現(xiàn)一個(gè)深度復(fù)制的函數(shù):
function extendDeep(parent, child) { var i, proxy; proxy = JSON.stringify(parent); //把parent對(duì)象轉(zhuǎn)換成字符串 proxy = JSON.parse(proxy) //把字符串轉(zhuǎn)換成對(duì)象,這是parent的一個(gè)副本 child = child || {}; for(i in proxy) { if(proxy.hasOwnProperty(i)) { child[i] = proxy[i]; } } proxy = null; //因?yàn)閜roxy是中間對(duì)象,可以將它回收掉 return child; }
下面是測(cè)試?yán)樱?br />
var dad = { counts: [1, 2, 3], reads: {paper: true} }; var kid = extendDeep(dad); //修改kid的counts屬性和reads屬性 kid.counts.push(4); kid.reads.paper = false; console.log(kid.counts); //[1, 2, 3, 4] console.log(kid.reads.paper); //false console.log(dad.counts); //[1, 2, 3] console.log(dad.reads.paper); //true
測(cè)試發(fā)現(xiàn),它也實(shí)現(xiàn)了深度復(fù)制。一般推薦使用后面這種方法,因?yàn)镴SON.parse和JSON.stringify是內(nèi)置函數(shù),處理起來會(huì)比較快。另外,前面的那種方法使用了遞歸調(diào)用,我們都知道,遞歸是效率比較低的一種算法。
關(guān)于JavaScript深度復(fù)制(deep clone)的實(shí)現(xiàn)方法就給大家介紹這么多,希望對(duì)大家有所幫助!
- Javascript 實(shí)現(xiàn)復(fù)制(Copy)動(dòng)作方法大全
- js實(shí)現(xiàn)各種復(fù)制到剪貼板的方法(分享)
- JavaScript復(fù)制內(nèi)容到剪貼板的兩種常用方法
- Zero Clipboard js+swf實(shí)現(xiàn)的復(fù)制功能使用方法
- js實(shí)現(xiàn)點(diǎn)擊按鈕復(fù)制文本功能
- 網(wǎng)站內(nèi)容禁止復(fù)制和粘貼、另存為的js代碼
- javascript 三種數(shù)組復(fù)制方法的性能對(duì)比
- js實(shí)現(xiàn)點(diǎn)擊復(fù)制當(dāng)前文本到剪貼板功能(兼容所有瀏覽器)
- js 復(fù)制或插入Html的實(shí)現(xiàn)方法小結(jié)
- 使用js實(shí)現(xiàn)復(fù)制功能
相關(guān)文章
javaScript之split與join的區(qū)別(詳解)
下面小編就為大家?guī)硪黄猨avaScript之split與join的區(qū)別(詳解)。小編覺得挺不錯(cuò)的,現(xiàn)在就分享給大家,也給大家做個(gè)參考。一起跟隨小編過來看看吧2017-11-11基于mouseout和mouseover等類似事件的冒泡問題解決方法
這篇文章主要介紹了關(guān)于mouseout和mouseover等類似事件的冒泡問題解決方法。需要的朋友可以過來參考下,希望對(duì)大家有所幫助2013-11-11JavaScript實(shí)現(xiàn)獲取img的原始尺寸的方法詳解
在微信小程序開發(fā)時(shí),它的image標(biāo)簽有一個(gè)默認(rèn)高度,這樣你的圖片很可能出現(xiàn)被壓縮變形的情況,所以就需要獲取到圖片的原始尺寸對(duì)image的寬高設(shè)置,本文就來分享一下JavaScript實(shí)現(xiàn)獲取img的原始尺寸的方法吧2023-03-03基于javascript實(shí)現(xiàn)右下角浮動(dòng)廣告效果
這篇文章主要介紹了基于javascript實(shí)現(xiàn)右下角浮動(dòng)廣告效果,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-01-01js中的值類型和引用類型小結(jié) 文字說明與實(shí)例
下面就舉例講一下這兩種類型在JavaScript中的體現(xiàn)、用法及注意事項(xiàng)。2010-12-12