更新時間:2020年08月05日14時12分 來源:傳智播客 瀏覽次數(shù):
在實際開發(fā)當(dāng)中,我們經(jīng)常會遇到要對對象進(jìn)行深拷貝的情況。而且深拷貝這個問題在面試過程中也經(jīng)常會遇到,下面就對本人在學(xué)習(xí)過程中的收獲,做以簡單的總結(jié)。
關(guān)于淺拷貝的概念,我在網(wǎng)上看到一種說法,直接上代碼。
var person = {name: "Jason", age: 18, car: {brand: "Ferrari", type: "430"}}; var person1 = person; //他們認(rèn)為這是淺拷貝
但是我個人認(rèn)為,上面這個根本不涉及拷貝,只是一個簡單的引用賦值。以我的理解,淺拷貝應(yīng)該是不考慮對象的引用類型的屬性,只對當(dāng)前對象的所有成員進(jìn)行拷貝,代碼如下:
function copy(obj){ var objCopy = {}; for(var key in obj){ objCopy[key] = obj[key]; } return objCopy; } var person = {name: "Jason", age: 18, car: {brand: "Ferrari", type: "430"}}; var personCopy = copy(person);
上面這段代碼中,person對象擁有兩個基本類型的屬性name和age,一個引用類型的屬性car,當(dāng)使用如上方法進(jìn)行拷貝的時候,name和age屬性會被正常的拷貝,但是car屬性,只會進(jìn)行引用的拷貝,這樣會導(dǎo)致拷貝出來的對象personCopy和person會共用一個car對象。這樣就是所謂的淺拷貝。
深拷貝的就是在拷貝的時候,需要將當(dāng)前要拷貝的對象內(nèi)的所有引用類型的屬性進(jìn)行完整的拷貝,也就是說拷貝出來的對象和原對象之間沒有任何數(shù)據(jù)是共享的,所有的東西都是自己獨占的一份。
實現(xiàn)深拷貝需要考慮如下幾個因素:
·傳入的對象是使用對象字面量{}創(chuàng)建的對象還是由構(gòu)造函數(shù)生成的對象
·如果對象是由構(gòu)造函數(shù)創(chuàng)建出來的,那么是否要拷貝原型鏈上的屬性
·如果要拷貝原型鏈上的屬性,那么如果原型鏈上存在多個同名的屬性,保留哪個
·處理循環(huán)引用的問題
jQuery的$.extend()
我們可以通過$.extend()方法來完成深復(fù)制。值得慶幸的是,我們在jQuery中可以通過添加一個參數(shù)來實現(xiàn)遞歸extend。調(diào)用$.extend(true, {}, ...)就可以實現(xiàn)深復(fù)制,參考下面的例子:
var x = { a: 1, b: { f: { g: 1 } }, c: [ 1, 2, 3 ] }; var y = $.extend({}, x), //shallow copy z = $.extend(true, {}, x); //deep copy y.b.f === x.b.f // true z.b.f === x.b.f // false
但是jQuery的這個`$.extend()`方法,有弊端,什么弊端呢?我們看下面的例子:
var objA = {}; var objB = {}; objA.b = objB; objB.a = objA; $.extend(true,{},a); //這個時候就出現(xiàn)異常了 //Uncaught RangeError: Maximum call stack size exceeded(…)
也就是說,jQuery中的`$.extend()`并沒有處理循環(huán)引用的問題。
然而使用這種方法會有一些隱藏的坑,它能正確處理的對象只有 Number, String, Boolean, Array, 扁平對象,即那些能夠被 json 直接表示的數(shù)據(jù)結(jié)構(gòu)。
自己造輪子
下面我們給出一個簡單的解決方案,當(dāng)然這個方案是參考別人的方式來實現(xiàn)的。希望對大家有用。
var clone = (function() { //這個方法用來獲取對象的類型 返回值為字符串類型 "Object RegExp Date Array..." var classof = function(o) { if (o === null) { return "null"; } if (o === undefined) { return "undefined"; } // 這里的Object.prototype.toString很可能用的就是Object.prototype.constructor.name // 這里使用Object.prototype.toString來生成類型字符串 var className = Object.prototype.toString.call(o).slice(8, -1); return className; }; //這里這個變量我們用來存儲已經(jīng)保存過的屬性,目的在于處理循環(huán)引用的問題 var references = null; //遇到不同類型的對象的處理方式 var handlers = { //正則表達(dá)式的處理 'RegExp': function(reg) { var flags = ''; flags += reg.global ? 'g' : ''; flags += reg.multiline ? 'm' : ''; flags += reg.ignoreCase ? 'i' : ''; return new RegExp(reg.source, flags); }, //時間對象處理 'Date': function(date) { return new Date(+date); }, //數(shù)組處理 第二個參數(shù)為是否做淺拷貝 'Array': function(arr, shallow) { var newArr = [], i; for (i = 0; i < arr.length; i++) { if (shallow) { newArr[i] = arr[i]; } else { //這里我們通過reference數(shù)組來處理循環(huán)引用問題 if (references.indexOf(arr[i]) !== -1) { continue; } var handler = handlers[classof(arr[i])]; if (handler) { references.push(arr[i]); newArr[i] = handler(arr[i], false); } else { newArr[i] = arr[i]; } } } return newArr; }, //正常對象的處理 第二個參數(shù)為是否做淺拷貝 'Object': function(obj, shallow) { var newObj = {}, prop, handler; for (prop in obj) { //關(guān)于原型中屬性的處理太過復(fù)雜,我們這里暫時不做處理 //所以只對對象本身的屬性做拷貝 if (obj.hasOwnProperty(prop)) { if (shallow) { newObj[prop] = obj[prop]; } else { //這里還是處理循環(huán)引用的問題 if (references.indexOf(obj[prop]) !== -1) { continue; } handler = handlers[classof(obj[prop])]; //如果沒有對應(yīng)的處理方式,那么就直接復(fù)制 if (handler) { references.push(obj[prop]); newObj[prop] = handler(obj[prop], false); } else { newObj[prop] = obj[prop]; } } } } return newObj; } }; return function(obj, shallow) { //首先重置我們用來處理循環(huán)引用的這個變量 references = []; //我們默認(rèn)處理為淺拷貝 shallow = shallow === undefined ? true : false; var handler = handlers[classof(obj)]; return handler ? handler(obj, shallow) : obj; }; }()); (function() { //下面是一些測試代碼 var date = new Date(); var reg = /hello word/gi; var obj = { prop: 'this ia a string', arr: [1, 2, 3], o: { wow: 'aha' } }; var refer1 = { arr: [1, 2, 3] }; var refer2 = { refer: refer1 }; refer1.refer = refer2; var cloneDate = clone(date, false); var cloneReg = clone(reg, false); var cloneObj = clone(obj, false); alert((date !== cloneDate) && (date.valueOf() === cloneDate.valueOf())); alert((cloneReg !== reg) && (reg.toString() === cloneReg.toString())); alert((obj !== cloneObj) && (obj.arr !== cloneObj.arr) && (obj.o !== cloneObj.o) && (JSON.stringify(obj) === JSON.stringify(cloneObj))); clone(refer2, false); alert("I'm not dead yet!"); // Output: // true // true // true // I'm not dead yet! }());