js對(duì)象的深淺拷貝辨析

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

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

基本類(lèi)型和引用類(lèi)型

ECMAScript 中的變量類(lèi)型分為兩類(lèi):

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

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

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

基本類(lèi)型的變量

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


image.png
var  a = 'hello, world'
引用類(lèi)型變量

引用類(lèi)型 保存在 堆內(nèi)存 中, 棧內(nèi)存存儲(chǔ)的是變量的標(biāo)識(shí)符以及對(duì)象在堆內(nèi)存中的存儲(chǔ)地址,當(dāng)需要訪問(wèn)引用類(lèi)型(如對(duì)象,數(shù)組等)的值時(shí),首先從棧中獲得該對(duì)象的地址指針,然后再?gòu)膶?duì)應(yīng)的堆內(nèi)存中取得所需的數(shù)據(jù)。(盜圖一張如下)


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

1 基本類(lèi)型的復(fù)制: 當(dāng)你把基本類(lèi)型的變量復(fù)制給一個(gè)新的變量的時(shí)候,相當(dāng)于把基本類(lèi)型的值賦值給了新的變量。

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

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

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

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ā)生了改變

配圖過(guò)程如下:


image.png

image.png

image.png

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

對(duì)于僅僅是復(fù)制了引用(地址),換句話說(shuō),復(fù)制了之后,原來(lái)的變量和新的變量指向同一個(gè)東西,彼此之間的操作會(huì)互相影響,為 淺拷貝。

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

深淺拷貝 的主要區(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'
    }
}
/**
 * 
 * 對(duì)象的潛拷貝
 * @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é)果如下:可以看出淺拷貝對(duì)于引用類(lèi)型,只是復(fù)制了地址,他們指向同一個(gè)對(duì)象。


image.png

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

var obj = {
    name: 'willie',
    friends: ['jack', 'rose', 'bob'],
    address: {
        province: 'guangdong',
        city: 'shenzhen'
    }
}
/**
 * 
 * 對(duì)象的深度復(fù)制:引用類(lèi)型對(duì)象不會(huì)出現(xiàn)共享
 * @param {object} obj 
 */
function deepCopy (obj) {
    // 首先判斷參數(shù)是否存在以及參數(shù)是否是一個(gè)對(duì)象
    if (!obj && typeof obj !== 'object') {
        throw new Error('error params')
    }
    // 判斷參數(shù)是對(duì)組類(lèi)型還是對(duì)象類(lèi)型
    var deepObject = Array.isArray(obj) ? [] : {}
    for (var attr in obj) {
        // 判斷屬性是否為元素自身的
        if (obj.hasOwnProperty(attr)) {
            // 判斷屬性值存在并且屬性值還是為一個(gè)引用類(lèi)型的值(數(shù)組或?qū)ο螅瑒t進(jìn)行遞歸,直到拷貝到基本屬性值為止
            if(obj[attr] && typeof obj[attr] === 'object') {
                deepObject[attr] = deepCopy(obj[attr]) //進(jìn)行遞歸操作
            } else {
                // 非引用類(lèi)型的值,直接進(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)對(duì)引用類(lèi)型進(jìn)行操作時(shí),并不會(huì)影響另一個(gè)變量


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

看到以上,會(huì)認(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é)果可以看到,這兩個(gè)方法都不是真正的深拷貝,concat同上。

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

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

// 深拷貝
var obj = {name:"jack",age:24};
//extend方法,第一個(gè)參數(shù)為true,為深拷貝,為false,或者沒(méi)有為淺拷貝。
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方法,第一個(gè)參數(shù)為true,為深拷貝,為false,或者沒(méi)有為淺拷貝。
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}

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

  1. JSON 對(duì)象的 parse 和 stringify

JOSN 對(duì)象中的 stringify 可以把一個(gè) js 對(duì)象序列化為一個(gè) JSON 字符串,parse 可以把 JSON 字符串反序列化為一個(gè) js 對(duì)象,這兩個(gè)方法實(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)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容