js對象的深淺拷貝辨析

在網(wǎng)上瀏覽了不少關(guān)于深淺拷貝的解析,在此,要總結(jié)的幾點(diǎn)內(nèi)容大致如下:

  1. 基本類型和引用類型
  2. 淺拷貝與深拷貝的區(qū)別
  3. 常用的淺拷貝與深拷貝的方法

基本類型和引用類型

ECMAScript 中的變量類型分為兩類:

基本類型:undefined,null,布爾值(boolean),字符串(string),數(shù)值(number)
引用類型: 統(tǒng)稱為Object類型,細(xì)分的話粘秆,有:Object類型希停,Array類型,Date類型赖舟,F(xiàn)unction類型等。

這里補(bǔ)充一下夸楣,很多人認(rèn)為null也是引用類型,因?yàn)?typeof null === 'object',之所以會這樣可能是js語言設(shè)計(jì)的缺陷宾抓,與底層的二進(jìn)制判斷有關(guān)。其實(shí)null是基本類型的變量豫喧,null不指向任何變量石洗,它是一個常量,所以說是基本類型紧显。引用類型的變量其實(shí)也是基本類型讲衫,而引用指向的對象本身才是引用類型。

原理是這樣的孵班,不同的對象在底層都表示為二進(jìn)制涉兽,在javascript中二進(jìn)制前3位都為0的話會被判斷為object類型,null的二進(jìn)制表示全為0篙程,自然前3位也是0枷畏,所以執(zhí)行typeof 時會返回''object'' —— 來自《你不知道的javascript》

基本類型的變量

基本類型的變量保存在棧內(nèi)存,棧內(nèi)存中分別存儲著變量的標(biāo)識符以及變量的值(盜圖一張如下)


image.png
var  a = 'hello, world'
引用類型變量

引用類型 保存在 堆內(nèi)存 中房午, 棧內(nèi)存存儲的是變量的標(biāo)識符以及對象在堆內(nèi)存中的存儲地址矿辽,當(dāng)需要訪問引用類型(如對象,數(shù)組等)的值時郭厌,首先從棧中獲得該對象的地址指針袋倔,然后再從對應(yīng)的堆內(nèi)存中取得所需的數(shù)據(jù)。(盜圖一張如下)


image.png
var a = {name: 'jack'}
image.png
上述2種類型是如何復(fù)制的

1 基本類型的復(fù)制: 當(dāng)你把基本類型的變量復(fù)制給一個新的變量的時候折柠,相當(dāng)于把基本類型的值賦值給了新的變量宾娜。

var a = 'jack'
var b = a 
console.log(a === b)
a = 'bob'
console.log(a)
console.log(b)
image.png

從上述結(jié)果可以看出,改變a 的值不會影響 b 的值扇售。

2 引用類型的復(fù)制:當(dāng)把引用類型賦值給一個新的變量的時候前塔,實(shí)際上復(fù)制了指向堆內(nèi)存的地址,原變量和新變量指向同一個對象承冰。

var  a = {name: 'jack' , age: '30'}
var b = a 
console.log(a  === b)
a.age = 30
console.log(a)
console.log(b)
image.png

從上述結(jié)果可以看出华弓,改變a 的值,b的值也發(fā)生了改變

配圖過程如下:


image.png

image.png

image.png

淺拷貝和深拷貝的區(qū)別

對于僅僅是復(fù)制了引用(地址)困乒,換句話說寂屏,復(fù)制了之后,原來的變量和新的變量指向同一個東西,彼此之間的操作會互相影響迁霎,為 淺拷貝吱抚。

而如果是在堆中重新分配內(nèi)存,擁有不同的地址考廉,但是值是一樣的秘豹,復(fù)制后的對象與原來的對象是完全隔離,互不影響昌粤,為 深拷貝既绕。

深淺拷貝 的主要區(qū)別就是:復(fù)制的是引用(地址)還是復(fù)制的是實(shí)例。

image.png

從上述圖可以看出是深拷貝婚苹。

下面看下淺拷貝的實(shí)現(xiàn)

淺拷貝的實(shí)現(xiàn)

var obj = {
    name: 'willie',
    friends: ['jack', 'rose', 'bob'],
    address: {
        province: 'guangdong',
        city: 'shenzhen'
    }
}
/**
 * 
 * 對象的潛拷貝
 * @param {ojects} obj 
 * @returns object
 */
function shallowCopy (obj) {
    var copyObject = {}
    for (var attr in obj) {
        if (obj.hasOwnProperty(attr)) {
            copyObject[attr]  = obj[attr]
        }
    }
    return copyObject
}
var person = shallowCopy(obj)
console.log(person === obj)
person.friends.push('tom')
console.log(person)
console.log(obj)

運(yùn)行結(jié)果如下:可以看出淺拷貝對于引用類型岸更,只是復(fù)制了地址,他們指向同一個對象膊升。


image.png

深拷貝的實(shí)現(xiàn)

var obj = {
    name: 'willie',
    friends: ['jack', 'rose', 'bob'],
    address: {
        province: 'guangdong',
        city: 'shenzhen'
    }
}
/**
 * 
 * 對象的深度復(fù)制:引用類型對象不會出現(xiàn)共享
 * @param {object} obj 
 */
function deepCopy (obj) {
    // 首先判斷參數(shù)是否存在以及參數(shù)是否是一個對象
    if (!obj && typeof obj !== 'object') {
        throw new Error('error params')
    }
    // 判斷參數(shù)是對組類型還是對象類型
    var deepObject = Array.isArray(obj) ? [] : {}
    for (var attr in obj) {
        // 判斷屬性是否為元素自身的
        if (obj.hasOwnProperty(attr)) {
            // 判斷屬性值存在并且屬性值還是為一個引用類型的值(數(shù)組或?qū)ο螅瑒t進(jìn)行遞歸谭企,直到拷貝到基本屬性值為止
            if(obj[attr] && typeof obj[attr] === 'object') {
                deepObject[attr] = deepCopy(obj[attr]) //進(jìn)行遞歸操作
            } else {
                // 非引用類型的值廓译,直接進(jìn)行賦值
                deepObject[attr] = obj[attr]
            }
        }
    }
    return deepObject
}
var person = deepCopy(obj)
console.log(person === obj)
person.friends.push('tom')
console.log(person)
console.log(obj)

運(yùn)行結(jié)果如下:當(dāng)對引用類型進(jìn)行操作時,并不會影響另一個變量


image.png

常用的淺拷貝與深拷貝的方法

  1. slice 和 concat
var a  = [1,2,3,4]
var b = a.slice()
console.log(a === b)
b.push(5)
console.log(a)
console.log(b)
image.png
var a = [1,2,3,4]
var b = a.concat()
console.log(a === b)
b.push(5)
console.log(a)
console.log(b)
image.png

看到以上债查,會認(rèn)為非区,slice和concat是深拷貝方法,那繼續(xù)往下看

var a = [[1,2,3],4,5];
var b = a.slice();
console.log(a === b);
b[0][0] = 6;
console.log(a);
console.log(b);
image.png

從以上結(jié)果可以看到盹廷,這兩個方法都不是真正的深拷貝征绸,concat同上。

  1. jQuery中的 extend 復(fù)制方法

可以用來擴(kuò)展對象俄占,這個方法可以傳入一個參數(shù):deep(true or false)管怠,表示是否執(zhí)行深復(fù)制(如果是深復(fù)制則會執(zhí)行遞歸復(fù)制)。

// 深拷貝
var obj = {name:"jack",age:24};
//extend方法缸榄,第一個參數(shù)為true渤弛,為深拷貝,為false甚带,或者沒有為淺拷貝她肯。
var obj_extend = $.extend(true,{}, obj); 
console.log(obj === obj_extend);  // false
obj.name = "rose";
console.log(obj);  // 輸出 {name:"rose",age:24}
console.log(obj_extend); 輸出 {name:"jack",age:24}
// 淺拷貝
var obj = {name:"jack",age:24};
//extend方法,第一個參數(shù)為true鹰贵,為深拷貝晴氨,為false,或者沒有為淺拷貝碉输。
var obj_extend = $.extend(false,{}, obj); 
console.log(obj === obj_extend); // false
obj.name = "rose";
console.log(obj); // 輸出 {name:"rose",age:24}
console.log(obj_extend); // 輸出 {name:"rose",age:24}

效果怎么會一樣呢
其實(shí)總結(jié)一下就是:
Array 的 slice 和 concat 方法 和 jQuery 中的 extend 復(fù)制方法籽前,他們都會復(fù)制第一層的值,對于 第一層 的值都是 深拷貝,而到 第二層 的時候 Array 的 slice 和 concat 方法就是 復(fù)制引用 聚假,jQuery 中的 extend 復(fù)制方法 則 取決于 你的 第一個參數(shù)块蚌, 也就是是否進(jìn)行遞歸復(fù)制。所謂第一層 就是 key 所對應(yīng)的 value 值是基本數(shù)據(jù)類型膘格,也就像上面例子中的name峭范、age,而對于 value 值是引用類型 則為第二層瘪贱,例如{name: 'jack', address: {province: 'guangdong', city: 'shenzhen'}}

  1. JSON 對象的 parse 和 stringify

JOSN 對象中的 stringify 可以把一個 js 對象序列化為一個 JSON 字符串纱控,parse 可以把 JSON 字符串反序列化為一個 js 對象,這兩個方法實(shí)現(xiàn)的是深拷貝菜秦。

var obj = {name:'jack', age:24, address : {province : 'guangdong',city : 'shenzhen'} };
var obj_json = JSON.parse(JSON.stringify(obj));
console.log(obj === obj_json);
obj.address.city = "fushan";
obj.name = "abc";
console.log(obj);
console.log(obj_json);
image.png

從以上可以看出甜害,進(jìn)行了深拷貝

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市球昨,隨后出現(xiàn)的幾起案子尔店,更是在濱河造成了極大的恐慌,老刑警劉巖主慰,帶你破解...
    沈念sama閱讀 221,273評論 6 515
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件嚣州,死亡現(xiàn)場離奇詭異,居然都是意外死亡共螺,警方通過查閱死者的電腦和手機(jī)该肴,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 94,349評論 3 398
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來藐不,“玉大人匀哄,你說我怎么就攤上這事〕” “怎么了涎嚼?”我有些...
    開封第一講書人閱讀 167,709評論 0 360
  • 文/不壞的土叔 我叫張陵,是天一觀的道長底扳。 經(jīng)常有香客問我铸抑,道長,這世上最難降的妖魔是什么衷模? 我笑而不...
    開封第一講書人閱讀 59,520評論 1 296
  • 正文 為了忘掉前任鹊汛,我火速辦了婚禮,結(jié)果婚禮上阱冶,老公的妹妹穿的比我還像新娘刁憋。我一直安慰自己,他們只是感情好木蹬,可當(dāng)我...
    茶點(diǎn)故事閱讀 68,515評論 6 397
  • 文/花漫 我一把揭開白布至耻。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪尘颓。 梳的紋絲不亂的頭發(fā)上走触,一...
    開封第一講書人閱讀 52,158評論 1 308
  • 那天,我揣著相機(jī)與錄音疤苹,去河邊找鬼互广。 笑死,一個胖子當(dāng)著我的面吹牛卧土,可吹牛的內(nèi)容都是我干的惫皱。 我是一名探鬼主播,決...
    沈念sama閱讀 40,755評論 3 421
  • 文/蒼蘭香墨 我猛地睜開眼尤莺,長吁一口氣:“原來是場噩夢啊……” “哼旅敷!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起颤霎,我...
    開封第一講書人閱讀 39,660評論 0 276
  • 序言:老撾萬榮一對情侶失蹤媳谁,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后友酱,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體韩脑,經(jīng)...
    沈念sama閱讀 46,203評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,287評論 3 340
  • 正文 我和宋清朗相戀三年粹污,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片首量。...
    茶點(diǎn)故事閱讀 40,427評論 1 352
  • 序言:一個原本活蹦亂跳的男人離奇死亡壮吩,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出加缘,到底是詐尸還是另有隱情鸭叙,我是刑警寧澤,帶...
    沈念sama閱讀 36,122評論 5 349
  • 正文 年R本政府宣布拣宏,位于F島的核電站沈贝,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏勋乾。R本人自食惡果不足惜宋下,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,801評論 3 333
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望辑莫。 院中可真熱鬧学歧,春花似錦、人聲如沸各吨。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,272評論 0 23
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至横浑,卻和暖如春剔桨,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背徙融。 一陣腳步聲響...
    開封第一講書人閱讀 33,393評論 1 272
  • 我被黑心中介騙來泰國打工洒缀, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人张咳。 一個月前我還...
    沈念sama閱讀 48,808評論 3 376
  • 正文 我出身青樓帝洪,卻偏偏與公主長得像,于是被迫代替她去往敵國和親脚猾。 傳聞我的和親對象是個殘疾皇子葱峡,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,440評論 2 359

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