淺拷貝與深拷貝

拷貝即復(fù)制。
本文只討論js中復(fù)雜數(shù)據(jù)類型的復(fù)制問題(Object,Array等),不討論基本數(shù)據(jù)類型(null,undefined,string,number和boolean)萍鲸,這些類型的值本身就存儲在棧內(nèi)存中(string類型的實(shí)際值還是存儲在堆內(nèi)存中的,但是js把string當(dāng)做基本類型來處理 )炮障,不存在引用值的情況槐壳。
淺復(fù)制和深復(fù)制都可在已有對象的基礎(chǔ)上再生一份,但對象的實(shí)例存儲在堆內(nèi)存中然后通過一個引用值去操作對象仔役,由此復(fù)制的時候就存在兩種情況了:復(fù)制引用和復(fù)制實(shí)例掷伙,這也是淺復(fù)制和深復(fù)制的區(qū)別所在。

  • 淺復(fù)制:淺復(fù)制是復(fù)制引用又兵,復(fù)制后的引用都是指向同一個對象的實(shí)例任柜,彼此之間的操作會互相影響。
  • 深復(fù)制:深復(fù)制不是簡單的復(fù)制引用沛厨,而是在堆中重新分配內(nèi)存宙地,并且把源對象實(shí)例的所有屬性都進(jìn)行新建復(fù)制,以保證深復(fù)制的對象的引用不包含任何原有對象或?qū)ο笊系娜魏螌ο竽嫫ぃ瑥?fù)制后的對象與原來的對象是完全隔離的宅粥。

淺復(fù)制:

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

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

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

var array = [1, [1, 2, 3], { name: "array" }];
var array_concat = array.concat();//copy array返回新數(shù)組
var array_slice = array.slice(0);//copy array返回新數(shù)組
array_concat[0] = 3//改變基本類型值不會相互影響
array_concat[1][0] = 5; //改變array_concat中數(shù)組元素的值
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' } ]

深復(fù)制:

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

  • 遞歸實(shí)現(xiàn)
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對象瞎领,借助這兩個方法可以實(shí)現(xiàn)對象的深復(fù)制。
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 } }

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

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

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

y.b.f === x.b.f       // true
z.b.f === x.b.f       // false
  • lodash庫的.clone() 或 .cloneDeep()方法——擁抱未來的庫
    lodash中關(guān)于復(fù)制的方法有兩個觅丰,分別是
    .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的深復(fù)制!M善蟆咬荷!

// 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]

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

參考資料

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末罕扎,一起剝皮案震驚了整個濱河市聚唐,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌腔召,老刑警劉巖杆查,帶你破解...
    沈念sama閱讀 222,378評論 6 516
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異臀蛛,居然都是意外死亡亲桦,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,970評論 3 399
  • 文/潘曉璐 我一進(jìn)店門浊仆,熙熙樓的掌柜王于貴愁眉苦臉地迎上來客峭,“玉大人,你說我怎么就攤上這事抡柿√蚶牛” “怎么了?”我有些...
    開封第一講書人閱讀 168,983評論 0 362
  • 文/不壞的土叔 我叫張陵洲劣,是天一觀的道長备蚓。 經(jīng)常有香客問我,道長闪檬,這世上最難降的妖魔是什么星著? 我笑而不...
    開封第一講書人閱讀 59,938評論 1 299
  • 正文 為了忘掉前任,我火速辦了婚禮粗悯,結(jié)果婚禮上虚循,老公的妹妹穿的比我還像新娘。我一直安慰自己样傍,他們只是感情好横缔,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,955評論 6 398
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著衫哥,像睡著了一般茎刚。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上撤逢,一...
    開封第一講書人閱讀 52,549評論 1 312
  • 那天膛锭,我揣著相機(jī)與錄音,去河邊找鬼蚊荣。 笑死初狰,一個胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的互例。 我是一名探鬼主播奢入,決...
    沈念sama閱讀 41,063評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼媳叨!你這毒婦竟也來了腥光?” 一聲冷哼從身側(cè)響起关顷,我...
    開封第一講書人閱讀 39,991評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎武福,沒想到半個月后议双,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,522評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡艘儒,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,604評論 3 342
  • 正文 我和宋清朗相戀三年聋伦,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片界睁。...
    茶點(diǎn)故事閱讀 40,742評論 1 353
  • 序言:一個原本活蹦亂跳的男人離奇死亡觉增,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出翻斟,到底是詐尸還是另有隱情逾礁,我是刑警寧澤,帶...
    沈念sama閱讀 36,413評論 5 351
  • 正文 年R本政府宣布访惜,位于F島的核電站嘹履,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏债热。R本人自食惡果不足惜砾嫉,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,094評論 3 335
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望窒篱。 院中可真熱鬧焕刮,春花似錦、人聲如沸墙杯。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,572評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽高镐。三九已至溉旋,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間嫉髓,已是汗流浹背观腊。 一陣腳步聲響...
    開封第一講書人閱讀 33,671評論 1 274
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留算行,地道東北人恕沫。 一個月前我還...
    沈念sama閱讀 49,159評論 3 378
  • 正文 我出身青樓,卻偏偏與公主長得像纱意,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子鲸阔,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,747評論 2 361

推薦閱讀更多精彩內(nèi)容