淺拷貝與深拷貝

拷貝即復制。
本文只討論js中復雜數據類型的復制問題(Object,Array等),不討論基本數據類型(null,undefined,string,number和boolean),這些類型的值本身就存儲在棧內存中(string類型的實際值還是存儲在堆內存中的,但是js把string當做基本類型來處理 ),不存在引用值的情況。
淺復制和深復制都可在已有對象的基礎上再生一份,但對象的實例存儲在堆內存中然后通過一個引用值去操作對象,由此復制的時候就存在兩種情況了:復制引用和復制實例,這也是淺復制和深復制的區別所在。

  • 淺復制:淺復制是復制引用,復制后的引用都是指向同一個對象的實例,彼此之間的操作會互相影響。
  • 深復制:深復制不是簡單的復制引用,而是在堆中重新分配內存,并且把源對象實例的所有屬性都進行新建復制,以保證深復制的對象的引用不包含任何原有對象或對象上的任何對象,復制后的對象與原來的對象是完全隔離的。

淺復制:

淺復制就是簡單的引用復制,示例如下:

var src = {
       name:"src";
   }
   var target = src; //復制一份src對象的引用
   target.name = "target"; //改變復制后對象的name值
   console.log(src.name);   //輸出target,即原對象也發生了改變,證明復制的是指向堆內存內對象位置的引用

Array的slice()和concat()方法都會返回一個新的數組實例,這與直接引用復制不同,但數組中的對象元素(Object、Array等)仍只是復制了引用,并不是真正的深復制。

var array = [1, [1, 2, 3], { name: "array" }];
var array_concat = array.concat();//copy array返回新數組
var array_slice = array.slice(0);//copy array返回新數組
array_concat[0] = 3//改變基本類型值不會相互影響
array_concat[1][0] = 5; //改變array_concat中數組元素的值
array_slice[0] = 4//改變基本類型值不會相互影響
array_slice[2].name = "array_slice"; //改變引用類型值會相互影響

console.log(array[1]);    //[5,2,3]//改變引用類型值會相互影響
console.log(array_slice[1]);  //[5,2,3]//改變引用類型值會相互影響
console.log(array[2].name);   //array_slice
console.log(array_concat[2].name); //array_slice
console.log(array)//[ 1, [ 5, 2, 3 ], { name: 'array_slice' } ]
console.log(array_concat)//[ 3, [ 5, 2, 3 ], { name: 'array_slice' } ]
console.log(array_slice)//[ 4, [ 5, 2, 3 ], { name: 'array_slice' } ]

深復制:

由深復制的定義來看,深復制要求如果源對象存在對象屬性,那么需要進行遞歸復制,從而保證復制的對象與源對象完全隔離。然而還有一種處在淺復制和深復制范圍之間的復制——jQuery的extend方法在deep參數為false時所謂的“淺復制”。這種復制只進行一個層級的復制:即如果源對象中存在對象屬性,那么復制的對象上也會引用相同的對象。這不符合深復制的要求,但又比簡單的復制引用的復制程度有了加深。

  • 遞歸實現
function deepCopy(oldObj, newObj) {
    var newObj = newObj || {}
    for (var i in oldObj) {
        if (oldObj[i] instanceof Object) {
            if (oldObj[i].constructor === Array) {
                newObj[i] = []
            } else {
                newObj[i] = {}
            }
            deepCopy(oldObj[i], newObj[i])
        } else {
            newObj[i] = oldObj[i]
        }
    }
    return newObj
}

var obj1 = {
    country: 'China',
    city: ['Beijing,Shanghai,Nanjing'],
    age: 16,
    friends: {
        name: 'dot',
        sex: 'female',
        age: null
    }
}

var obj2 = {
    name: 'dolby',
    fav: 'food'
}

var obj3 = null

console.log(deepCopy(obj1, obj2))
//{ name: 'dolby',
//   fav: 'food',
//   country: 'China',
//   city: [ 'Beijing,Shanghai,Nanjing' ],
//   age: 16,
//   friends: { name: 'dot', sex: 'female', age: null } }

console.log(deepCopy(obj1, obj3))
//{ country: 'China',
//   city: [ 'Beijing,Shanghai,Nanjing' ],
//   age: 16,
//   friends: { name: 'dot', sex: 'female', age: null } }
  • JSON對象的parse()和stringify()方法
    stringify()方法將JS對象序列化成JSON字符串,parse()方法將JSON字符串反序列化成JS對象,借助這兩個方法可以實現對象的深復制。
var obj1 = {
    country: 'China',
    city: ['Beijing', 'Shanghai', 'Nanjing'],
    age: 16,
    friends: {
        name: 'dot',
        sex: 'female',
        age: null
    }
}
var deepCopyObj = JSON.parse(JSON.stringify(obj1))
console.log(deepCopyObj)
//{ country: 'China',
//   city: [ 'Beijing', 'Shanghai', 'Nanjing' ],
//   age: 16,
//   friends: { name: 'dot', sex: 'female', age: null } }

deepCopyObj.age = 0
deepCopyObj.city[1] = 'tianjin'

console.log(deepCopyObj)
//{ country: 'China',
//   city: [ 'Beijing', 'tianjin', 'Nanjing' ],
//   age: 0,
//   friends: { name: 'dot', sex: 'female', age: null } 

console.log(obj1)
//{ country: 'China',
//   city: [ 'Beijing', 'Shanghai', 'Nanjing' ],
//   age: 16,
//   friends: { name: 'dot', sex: 'female', age: null } }

從代碼輸出看出,復制后的deepCopyObj與obj1完全隔離,二者不會相互影響。
這個方法可滿足基本的深復制需求,而且能夠處理JSON格式能表示的所有數據類型(即 Number, String, Boolean, Array, 扁平對象),但對于正則表達式類型、函數類型等無法進行深復制(而且會直接丟失相應的值),同時如果對象中存在循環引用的情況也無法正確處理。

  • jQuery庫中的extend復制方法
    在 jQuery 中可通過添加一個參數來實現遞歸extend。調用$.extend(true, {}, ...)就可以實現深復制。jQuery 無法正確深復制 JSON 對象以外的對象.
var x = {
    a: 1,
    b: { f: { g: 1 } },
    c: [ 1, 2, 3 ]
};

var y = $.extend({}, x),          //淺復制
    z = $.extend(true, {}, x);    //深復制

y.b.f === x.b.f       // true
z.b.f === x.b.f       // false
  • lodash庫的.clone() 或 .cloneDeep()方法——擁抱未來的庫
    lodash中關于復制的方法有兩個,分別是
    .clone()和
    .cloneDeep()。其中.clone(obj, true)等價于.cloneDeep(obj)。
var $ = require("jquery"),
    _ = require("lodash");

var arr = new Int16Array(5),
    obj = { a: arr },
    obj2;
arr[0] = 5;
arr[1] = 6;

// 1. jQuery
obj2 = $.extend(true, {}, obj);
console.log(obj2.a);                            // [5, 6, 0, 0, 0]
Object.prototype.toString.call(obj2);           // [object Int16Array]
obj2.a[0] = 100;
console.log(obj);                               // [100, 6, 0, 0, 0]
//此處jQuery不能正確處理Int16Array的深復制!!!

// 2. lodash
obj2 = _.cloneDeep(obj);                       
console.log(obj2.a);                            // [5, 6, 0, 0, 0]
Object.prototype.toString.call(arr2);           // [object Int16Array]
obj2.a[0] = 100;
console.log(obj);                               // [5, 6, 0, 0, 0]

注意:如果對象比較大,層級比較多,深復制會帶來性能上的問題,在遇到需要采用深復制的場景時可以考慮看看有沒有其他替代的方案,在實際應用場景中也是淺復制更為常用。

參考資料

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 229,732評論 6 539
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,214評論 3 426
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 177,781評論 0 382
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,588評論 1 316
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,315評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,699評論 1 327
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,698評論 3 446
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,882評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,441評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,189評論 3 356
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,388評論 1 372
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,933評論 5 363
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,613評論 3 348
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,023評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,310評論 1 293
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,112評論 3 398
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,334評論 2 377

推薦閱讀更多精彩內容