首頁 >web前端 >js教程 >JavaScript深度複製(deep clone)的實作方法_javascript技巧

JavaScript深度複製(deep clone)的實作方法_javascript技巧

WBOY
WBOY原創
2016-05-16 15:14:441388瀏覽

在程式碼重複使用模式裡面有一種叫做「複製屬性模式」(copying properties pattern)。談到程式碼重複使用的時候,很有可能想到的是程式碼的繼承性(inheritance),但重要的是要記住其最終目標-我們要重複使用程式碼。繼承性只是實作程式碼復用的手段,而不是唯一的方法。複製屬性也是一種複用模式,它跟繼承性是有所不同的。在這種模式中,物件將從另外一個在物件中取得成員,其方法是僅需將其複製即可。用過jQuery的都知道,它有一個$.extend()方法,它的用途除了擴充第三方插件之外,還可以用來複製屬性的。下面我們來看一個extend()函數的實作程式碼(注意這裡的並不是jQuery的源碼,只是一個簡單的範例):

function extend(parent, child) {
var i;
//如果不传入第二参数child
//那么就创建一个新的对象
child = child || {}; 
//遍历parent对象的所有属性
//并且过滤原型上的属性
//然后将自身属性复制到child对象上
for(i in parent) {
if(parent.hasOwnProperty(i)) {
child[i] = parent[i];
}
}
//返回目标对象child
return child;
} 

上面的程式碼是一個簡單的實現,它只遍歷父物件的成員並將其複製到子物件中去。下面我們用上面的extend()方法來測試一下:

var dad = {name: "Adam"};
var kid = extend(dad);
console.log(kid.name); //Adam 

我們發現,extend()方法已經可以正常運作了。但是有一個問題,上面給出的是一種所謂的淺複製(shallow clone)。在使用淺複製的時候,如果改變了子對象的屬性,並且該屬性恰好又是一個對象,那麼這種操作也會修改父對象,單是很多情況這不是我們想要的結果。考慮下列情況:

var dad = {
counts: [1, 2, 3],
reads: {paper: true}
};
var kid = extend(dad) //调用extend()方法将dad的属性复制到kid上面
kid.counts.push(4); //把4追加到kid.counts数组里面
console.log(dad.counts); //[1, 2, 3, 4] 

透過上面的例子,我們會發現,修改了kid.counts屬性以後(把元素4追加進去了),dad.counts也會受到影響。這是因為在使用淺複製的時候,由於物件是透過引用傳遞的,即kid.counts和dad.counts指向的是同一個陣列(或者說在記憶體上他們指向同一個堆的位址)。

下面,讓我們修改extend()函數以實現深度複製。我們需要做的事情就是檢查父物件的每一個屬性,如果該屬性恰好是物件的話,那麼就遞歸複製出該物件的屬性。另外,還需要偵測該物件是否為一個數組,這是因為數組的字面量創建方式和物件的字面量創建方式不一樣,前者是[],後者是{}。檢測陣列可以使用Object.prototype.toString()方法進行檢測,如果是陣列的話,他會回傳"[object Array]"。下面我們來看看深度複製版本的extend()函數:

function extendDeep(parent, child) {
child = child || {};
for(var i in parent) {
if(parent.hasOwnProperty(i)) {
//检测当前属性是否为对象
if(typeof parent[i] === "object") {
//如果当前属性为对象,还要检测它是否为数组
//这是因为数组的字面量表示和对象的字面量表示不同
//前者是[],而后者是{}
child[i] = (Object.prototype.toString.call(parent[i]) === "[object Array]") ? [] : {};
//递归调用extend
extendDeep(parent[i], child[i]);
} else {
child[i] = parent[i];
}
}
}
return child;
} 

好了,深度複製的函數已經寫好了,下面來測試一下看是否能夠預期那樣子工作,即是否可以實現深度複製:

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 

透過上面例子,我們可以發現,即使修改了子物件的kid.counts和kid.reads,父物件的dad.counts和kid.reads並沒有改變,因此我們的目的實現了。

下面來總結實現深複製的基本想法:

1.偵測目前屬性是否為物件

2.因為數組是特殊的對象,所以,在屬性為對象的前提下還需要偵測它是否為數組。

3.如果是數組,則建立一個[]空數組,否則,建立一個{}空對象,並賦值給子對象的目前屬性。然後,遞歸呼叫extendDeep函數。

上面範例使我們自己使用遞歸演算法實現的一種深度複製方法。事實上,ES5新增的JSON物件提供的兩個方法也可以實現深度複製,分別是JSON.stringify()和JSON.parse();前者用來將物件轉成字串,後者則把字串轉換成物件。下面我們使用此方法來實作一個深度複製的函數:

function extendDeep(parent, child) {
var i,
proxy;
proxy = JSON.stringify(parent); //把parent对象转换成字符串
proxy = JSON.parse(proxy) //把字符串转换成对象,这是parent的一个副本
child = child || {};
for(i in proxy) {
if(proxy.hasOwnProperty(i)) {
child[i] = proxy[i];
}
}
proxy = null; //因为proxy是中间对象,可以将它回收掉
return child;
} 

下面是測試範例:

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 

測試發現,它也實現了深度複製。一般建議使用後面這種方法,因為JSON.parse和JSON.stringify是內建函數,處理起來會比較快。另外,前面的那種方法使用了遞歸調用,我們都知道,遞歸是效率比較低的演算法。

關於JavaScript深度複製(deep clone)的實作方法就跟大家介紹這麼多,希望對大家有幫助!

陳述:
本文內容由網友自願投稿,版權歸原作者所有。本站不承擔相應的法律責任。如發現涉嫌抄襲或侵權的內容,請聯絡admin@php.cn