JavaScript深度複製(deep clone)的實現方法,javascriptclone
在代碼複用模式裡面有一種叫做“複製屬性模式”(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];}}//返回目標對象childreturn 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]") ? [] : {};//遞迴調用extendextendDeep(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); //falseconsole.log(dad.counts); //[1, 2, 3]console.log(dad.reads.paper); //true
通過上面例子,我們可以發現,即使修改了子物件的kid.counts和kid.reads,父物件的dad.counts和kid.reads並沒有改變,因此我們的目的實現了。
下面來總結一下實現深複製的的基本思路:
1.檢測當前屬性是否為對象
2.因為數組是特殊的對象,所以,在屬性為對象的前提下還需要檢測它是否為數組。
3.如果是數組,則建立一個[]空數組,否則,建立一個{}Null 物件,並賦值給子物件的當前屬性。然後,遞迴調用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); //falseconsole.log(dad.counts); //[1, 2, 3]console.log(dad.reads.paper); //true
測試發現,它也實現了深度複製。一般推薦使用後面這種方法,因為JSON.parse和JSON.stringify是內建函數,處理起來會比較快。另外,前面的那種方法使用了遞迴調用,我們都知道,遞迴是效率比較低的一種演算法。
關於JavaScript深度複製(deep clone)的實現方法就給大家介紹這麼多,希望對大家有所協助!
您可能感興趣的文章:
- javascript dom操作之cloneNode文本節點複製提示
- JavaScript 用cloneNode方法複製節點的代碼
- JS.GetAllChild(element,deep,condition)使用介紹
- JS將制定內容複寫到剪下板範例程式碼
- javascript 密碼框防止使用者粘貼和複製的實現代碼
- 用js將內容複寫到剪貼簿相容瀏覽器
- 用於deeplink的js方法(判斷手機是否安裝app)
- js實現的複製相容chrome和IE
- 在Node.js中實現檔案複製的方法和執行個體
- Javascript 實現複製(Copy)動作方法大全
- js實現點擊後將文字或圖片複製到剪貼簿的方法
- 原生js實現複製對象、擴充項物件 類似jquery中的extend()方法
- 相容主流瀏覽器的JS複製內容到剪貼簿