如何在js中得到一個克隆對象,能夠說是喜聞樂見的話題了。相信你們都瞭解引用類型與基本類型,也都知道有種叫作深拷貝的東西,傳說深拷貝能夠得到一個克隆對象!那麼像我這樣的萌新天然就去學習了一波,咱們能找到的代碼基本都是這樣的:數組
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
var deepClone = function(currobj){ if(typeof currobj !== 'object'){ return currobj; } if(currobj instanceof Array){ var newobj = []; }else{ var newobj = {} } for(var key in currobj){ if(typeof currobj[key] !== 'object'){ newobj[key] = currobj[key]; }else{ newobj[key] = deepClone(currobj[key]) } } return newobj } |
嘖嘖真是很精巧啊!對於Array和普通Object都作了區分。可是顯然,藉助遞歸實現的深拷貝若是要克隆層級不少的複雜對象,容易形成內存溢出,咱能夠作出一個小小改進:緩存
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 |
var deepClone = function(currobj){ if(typeof currobj !== 'object'){ return currobj; } if(currobj instanceof Array){ var newobj = []; }else{ var newobj = {} } var currQue = [currobj], newQue = [newobj]; //關鍵在這裏 while(currQue.length){ var obj1 = currQue.shift(),obj2 = newQue.shift(); for(var key in obj1){ if(typeof obj1[key] !== 'object'){ obj2[key] = obj1[key]; }else{ if(obj1[key] instanceof Array ){ obj2[key] = []; }else{ obj2[key] = {} }; // 妙啊 currQue.push(obj1[key]); newQue.push(obj2[key]); } } } return newobj; }; |
這裏利用了兩個隊列,還算優雅的避免了遞歸的弊端。函數
還有一種方法是利用JSON的內置方法,即所謂的JSON序列化:學習
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
var deepClone = function(obj){ var str, newobj = obj.constructor === Array ? [] : {}; if(typeof obj !== 'object'){ return; } else if(window.JSON){ str = JSON.stringify(obj), //系列化對象 newobj = JSON.parse(str); //還原 } else { for(var i in obj){ newobj[i] = typeof obj[i] === 'object' ? deepClone(obj[i]) : obj[i]; } } return newobj; }; |
不過不打緊,它與上面方法的效果基本相同。this
拜託,你們都很懂對象,上面的方法有幾個很大的問題:prototype
因而,咱們就要開始改造上面的深拷貝方法來進行完美的克隆了!………….麼?code
克隆克隆,咱們日常把它掛在嘴上,但面對一個對象,咱們真正想克隆的是什麼?我想在99%的狀況下,咱們想克隆的是對象的數據,而保留它的原型引用和方法引用,所以上面提到的侷限中的第二點,基本能夠不考慮。如今咱再來看看怎麼解決剩下兩點。對象
首先搞清什麼是循環引用,常見的循環引用有兩種:遞歸
1 2 |
var a = {}; a._self = a; |
這種循環引用能夠說非常常見。隊列
1 2 3 4 |
var a = {}; var b = {}; a.brother = b; b.brother = a; |
也不是沒見過,不過這是典型致使對象內存沒法被回收的寫法,自己就不推薦。
目前只找到了針對第一種引用的解決方法,來自於Jquery源碼:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
jQuery.extend = jQuery.fn.extend = function() { // options是一個緩存變量,用來緩存arguments[i] // name是用來接收將要被擴展對象的key // src改變以前target對象上每一個key對應的value // copy傳入對象上每一個key對應的valu // copyIsArray斷定copy是否爲一個數組 // clone深拷貝中用來臨時存對象或數組的src var options, name, src, copy, copyIsArray, clone, target = arguments[0] || {}, i = 1, length = arguments.length, deep = false; // 處理深拷貝的狀況 if (typeof target === "boolean") { deep = target; target = arguments[1] || {}; //跳過布爾值和目標 i++; } // 控制當target不是object或者function的狀況 if (typeof target !== "object" && !jQuery.isFunction(target)) { target = {}; } // 當參數列表長度等於i的時候,擴展jQuery對象自身 if (length === i) { target = this; --i; } for (; i < length; i++) { if ((options = arguments[i]) != null) { // 擴展基礎對象 for (name in options) { src = target[name]; copy = options[name]; // 防止永無止境的循環,這裏舉個例子,如var i = {};i.a = i;$.extend(true,{},i);若是沒有這個判斷變成死循環了 if (target === copy) { continue; } if (deep && copy && (jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)))) { if (copyIsArray) { copyIsArray = false; clone = src && jQuery.isArray(src) ? src: []; // 若是src存在且是數組的話就讓clone副本等於src不然等於空數組。 } else { clone = src && jQuery.isPlainObject(src) ? src: {}; // 若是src存在且是對象的話就讓clone副本等於src不然等於空數組。 } // 遞歸拷貝 target[name] = jQuery.extend(deep, clone, copy); } else if (copy !== undefined) { target[name] = copy; // 若原對象存在name屬性,則直接覆蓋掉;若不存在,則建立新的屬性。 } } } } // 返回修改的對象 return target; }; |
在咱們想辦法魔改深拷貝時,先看下以上這麼多深拷貝的基本原理:
利用for-in循環遍歷對象屬性,若是屬性值是對象則深拷貝,不是則直接賦值
因而俺眉頭一皺發現事情並不簡單,俺上一篇博客已經說明:for-in遍歷的是對象以及其原型鏈上可枚舉屬性,所以想在遍歷時對源對象的__proto__
作手腳是根本不存在的,__proto__
以及它的不可枚舉屬性根本不會被遍歷到。能夠經過下面的例子看出:
1 2 3 4 5 6 7 8 9 10 11 12 13 |
var deepClone = function() {...} // 隨便從上面拿一個 var A = function() { this.val = 1; } A.prototype.log = function() { console.log(this.val); } var obj1 = new A(); var obj2 = deepClone(obj1); console.log(obj1); // A {val: 1} console.log(obj2); // {val: 1, log: function(){...}} |
所以,一個解決方法很單純,就是像上面的jQuery.extend方法同樣,本身傳入拷貝的目標對象,extend方法本質上只是拓展目標對象的屬性,使其得到源對象上的數據,這樣一來只要咱們先建立好符合需求的目標對象便可。
另外一種方法則是不採用深拷貝,直接取出須要進行拷貝的對象的數據,而後再利用這份數據來實例化和設置一個新的對象出來:
1 2 3 4 5 6 7 8 9 10 11 |
var Foo = function( obj ){ this.name = obj.name; this.sex = obj.sex }; Foo.prototype.toJSON = funciton(){ return { name: this.name, sex: this.sex }; }; var foo = new Foo({ name: "bandit", sex: "male" }); var fooCopy = new Foo( foo.toJSON() ); |
問題一樣獲得解決【鼓掌】
回顧一下,沒有哪一種方法是萬用的魔法 —— 在咱們想要得到一個克隆對象以前,或許最好先搞清楚咱們究竟是在克隆什麼,再採用最適合的方法。而非是拘泥於「深拷貝淺拷貝」的說法,去複製一段代碼祈禱他能生效。我相信以上的示例代碼尚未考慮到克隆對象的全部問題,但它們在合適的場景下可以處理合適的問題。嗯,其實不少事情都是這樣蛤【帶!】