深入探討深拷貝淺拷貝兩兄弟

基本數(shù)據(jù)類型

在深入探討深拷貝和淺拷貝之前,我們需要先了解一下Javascript得數(shù)據(jù)類型。眾所周知JavaScript得數(shù)據(jù)類型,分為基本數(shù)據(jù)類型和引用數(shù)據(jù)類型。那么這兩種類型到底有什么區(qū)別?接下來我們?cè)敿?xì)的談?wù)劇?/p>

導(dǎo)圖:














js內(nèi)存

接下來我們還需要了解一個(gè)重要的知識(shí)點(diǎn)----js中的內(nèi)存

js中的內(nèi)存為兩種-----棧和堆。
基本數(shù)據(jù)類型存儲(chǔ)

基本數(shù)據(jù)類型的值存在棧里面,并且值與值之間獨(dú)立存在,修改一個(gè)值,不會(huì)影響其他的值。舉個(gè)列子:把a(bǔ)的值傳給b









下面解釋為什么當(dāng)a的值變?yōu)?24時(shí),b為什么不改變






















可以看出a和b的值相互獨(dú)立,當(dāng)代碼執(zhí)行到a++時(shí),只是a的值變?yōu)?24,而b也是123
引用數(shù)據(jù)類型存儲(chǔ):

    對(duì)象保存在堆內(nèi)存中
    每創(chuàng)建一個(gè)新的對(duì)象就會(huì)在堆內(nèi)存開辟一個(gè)新的空間
    變量保存的是內(nèi)存地址(對(duì)象引用)
    兩個(gè)變量保存同一個(gè)引用,一個(gè)變量修改屬性時(shí),另一個(gè)變量屬性值也會(huì)變化

引用數(shù)據(jù)類型在棧中存儲(chǔ)了指針,該指針指向堆中該實(shí)體的起始地址。當(dāng)解釋器尋找引用值時(shí),會(huì)首先檢索其在棧中的地址,取得地址后從堆中獲得實(shí)體。

舉例說明:














當(dāng)obj屬性name變?yōu)?孫悟空"時(shí),obj1屬性name也變?yōu)?孫悟空"

內(nèi)存分析——解釋上述現(xiàn)象




















當(dāng)棧存放引用類型時(shí),值為對(duì)象的地址,obj與obj1指向同一個(gè)地址,所以當(dāng)obj的name值變?yōu)椤皩O悟空”時(shí),obj1也會(huì)發(fā)生變化

看完這個(gè),相信小伙伴們已經(jīng)很清晰的明白,為什么會(huì)有淺拷貝和深拷貝這兩兄弟了吧。那么這兩個(gè)到底有什么區(qū)別尼?別著急,我們一步一步道來。
深拷貝和淺拷貝的區(qū)別

深拷貝和淺拷貝是只針對(duì)Object和Array這樣的引用數(shù)據(jù)類型的。示意圖大致如下:










淺拷貝只復(fù)制指向某個(gè)對(duì)象的指針,而不復(fù)制對(duì)象本身,新舊對(duì)象還是共享同一塊內(nèi)存。但深拷貝會(huì)另外創(chuàng)造一個(gè)一模一樣的對(duì)象,新對(duì)象跟原對(duì)象不共享內(nèi)存,修改新對(duì)象不會(huì)改到原對(duì)象。
賦值和淺拷貝的區(qū)別

當(dāng)我們把一個(gè)對(duì)象賦值給一個(gè)新的變量時(shí),賦的其實(shí)是該對(duì)象的在棧中的地址,而不是堆中的數(shù)據(jù)。也就是兩個(gè)對(duì)象指向的是同一個(gè)存儲(chǔ)空間,無論哪個(gè)對(duì)象發(fā)生改變,其實(shí)都是改變的存儲(chǔ)空間的內(nèi)容,因此,兩個(gè)對(duì)象是聯(lián)動(dòng)的。

淺拷貝是按位拷貝對(duì)象,它會(huì)創(chuàng)建一個(gè)新對(duì)象,這個(gè)對(duì)象有著原始對(duì)象屬性值的一份精確拷貝。如果屬性是基本類型,拷貝的就是基本類型的值;如果屬性是內(nèi)存地址(引用類型),拷貝的就是內(nèi)存地址 ,因此如果其中一個(gè)對(duì)象改變了這個(gè)地址,就會(huì)影響到另一個(gè)對(duì)象。即默認(rèn)拷貝構(gòu)造函數(shù)只是對(duì)對(duì)象進(jìn)行淺拷貝復(fù)制(逐個(gè)成員依次拷貝),即只復(fù)制對(duì)象空間而不復(fù)制資源。






我們先來看兩個(gè)例子,對(duì)比賦值與淺拷貝會(huì)對(duì)原對(duì)象帶來哪些改變?


























































上面例子中,obj1是原始數(shù)據(jù),obj2是賦值操作得到,而obj3淺拷貝得到。我們可以很清晰看到對(duì)原始數(shù)據(jù)的影響,具體請(qǐng)看下表:










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

    1、Object.assign()

Object.assign()方法可以將任意多個(gè)的源對(duì)象自身的可枚舉屬性拷貝給目標(biāo)對(duì)象,然后返回目標(biāo)對(duì)象。但是該方法拷貝的時(shí)候是淺拷貝,拷貝的是對(duì)象的屬性的引用,并不是對(duì)象本身。(注意:當(dāng)object只有一層的時(shí)候,就是淺拷貝)

    2、Array.prototype.concat()
    3、Array.prototype.slice()

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

    1、JSON.parse(JSON.stringify())

JSON.stringify()是前端開發(fā)過程中比較常用的深拷貝方式。原理是把一個(gè)對(duì)象序列化成為一個(gè)JSON字符串,將對(duì)象的內(nèi)容轉(zhuǎn)換成字符串的形式再保存在磁盤上,再用JSON.parse()反序列化將JSON字符串變成一個(gè)新的對(duì)象。

    2、自己遞歸實(shí)現(xiàn)一個(gè)簡(jiǎn)單深拷貝

深拷貝,主要用到的思想是遞歸,遍歷對(duì)象、數(shù)組直到里邊都是基本數(shù)據(jù)類型,然后再去復(fù)制,就是深度拷貝。

示例代碼:

//定義檢測(cè)數(shù)據(jù)類型的功能函數(shù)
    function isObject(obj) {
        return typeof obj === 'object' && obj != null;
    }
   function cloneDeep(source) {

    if (!isObject(source)) return source; // 非對(duì)象返回自身
      
    var target = Array.isArray(source) ? [] : {};
    for(var key in source) {
        if (Object.prototype.hasOwnProperty.call(source, key)) {
            if (isObject(source[key])) {
                target[key] = cloneDeep(source[key]); // 注意這里
            } else {
                target[key] = source[key];
            }
        }
    }
    return target;
}

 

    3、第三方深拷貝庫
 


歡迎關(guān)注微信公眾號(hào):猴哥說前端