淺談JS深拷貝(深克隆)@郝晨光


在學習深拷貝之前,我們要先搞明白什么是深拷貝?

在JS中,數據類型分為基本數據類型和引用數據類型兩種,對于基本數據類型來說,它的值直接存儲在棧內存中,而對于引用類型來說,它在棧內存中僅僅存儲了一個引用,而真正的數據存儲在堆內存中

當我們對數據進行操作的時候,會發生兩種情況

一、基本數據類型

var a = 3;
var b = a;
b = 5;
console.log(a); // 3
console.log(b); // 5

可以看到的是對于基本類型來說,我們將一個基本類型的值賦予 a 變量,接著將 a 的值賦予變量 b ;然后我們修改 b ;可以看到 b 被修改了,而 a 的值沒有被修改,兩個變量都使用的是獨立的數據;

二、引用數據類型

var obj1 = {
    a:  1,
    b:  2,
    c:  3
}
var obj2 = obj1;
obj2.a = 5;
console.log(obj1.a);  // 5
console.log(obj2.a);  // 5

可以看到的是,兩個對象的值全部被修改了
對象是引用類型的值,對于引用類型來說,我們將 obj1 賦予 obj2 的時候,我們其實僅僅只是將 obj1 存儲在棧堆中的的引用賦予了 obj2 ,而兩個對象此時指向的是在堆內存中的同一個數據,所以當我們修改任意一個值的時候,修改的都是堆內存中的數據,而不是引用,所以只要修改了,同樣引用的對象的值也自然而然的發生了改變

其實,上面的例子就是一個簡單的淺拷貝,而淺拷貝對應的,就是我們文章的主題,深拷貝!

一、 淺拷貝

對于淺拷貝而言,就是只拷貝對象的引用,而不深層次的拷貝對象的值,多個對象指向堆內存中的同一對象,任何一個修改都會使得所有對象的值修改,因為它們公用一條數據

二、深拷貝

我們在實際的項目中,肯定不能讓每個對象的值都指向同一個堆內存,這樣的話不便于我們做操作,所以自然而然的誕生了深拷貝
深拷貝作用在引用類型上!例如:Object,Array
深拷貝不會拷貝引用類型的引用,而是將引用類型的值全部拷貝一份,形成一個新的引用類型,這樣就不會發生引用錯亂的問題,使得我們可以多次使用同樣的數據,而不用擔心數據之間會起沖突

三、深拷貝的實現

  1. 首先看一下乞丐版的深拷貝吧!JSON.stringify()以及JSON.parse()
 var obj1 = {
    a: 1,
    b: 2,
    c: 3
}
var objString = JSON.stringify(obj1);
var obj2 = JSON.parse(objString);
obj2.a = 5;
console.log(obj1.a);  // 1
console.log(obj2.a); // 5

可以看到沒有發生引用問題,修改obj2的數據,并不會對obj1造成任何影響
但是為什么說它是乞丐版的呢?
那是因為 使用JSON.stringify()以及JSON.parse()它是不可以拷貝 undefined , function, RegExp 等等類型的

  1. 接著來看第二種方式 Object.assign(target, source)
 var obj1 = {
    a: 1,
    b: 2,
    c: 3
}
var obj2 = Object.assign({}, obj1);
obj2.b = 5;
console.log(obj1.b); // 2
console.log(obj2.b); // 5

第二種方式實現的看起來也沒有任何的問題,但是這是一層對象,如果是有多層嵌套呢

 var obj1 = {
    a: 1,
    b: 2,
    c: ['a','b','c']
}
var obj2 = Object.assign({}, obj1);
obj2.c[1] = 5;
console.log(obj1.c); // ["a", 5, "c"]
console.log(obj2.c); // ["a", 5, "c"]

可以看到對于一層對象來說是沒有任何問題的,但是如果對象的屬性對應的是其它的引用類型的話,還是只拷貝了引用,修改的話還是會有問題

  1. 第三種方式 遞歸拷貝
// 定義一個深拷貝函數  接收目標target參數
function deepClone(target) {
    // 定義一個變量
    let result;
    // 如果當前需要深拷貝的是一個對象的話
    if (typeof target === 'object') {
    // 如果是一個數組的話
        if (Array.isArray(target)) {
            result = []; // 將result賦值為一個數組,并且執行遍歷
            for (let i in target) {
                // 遞歸克隆數組中的每一項
                result.push(deepClone(target[i]))
            }
         // 判斷如果當前的值是null的話;直接賦值為null
        } else if(target===null) {
            result = null;
         // 判斷如果當前的值是一個RegExp對象的話,直接賦值    
        } else if(target.constructor===RegExp){
            result = target;
        }else {
         // 否則是普通對象,直接for in循環,遞歸賦值對象的所有值
            result = {};
            for (let i in target) {
                result[i] = deepClone(target[i]);
            }
        }
     // 如果不是對象的話,就是基本數據類型,那么直接賦值
    } else {
        result = target;
    }
     // 返回最終結果
    return result;
}
可以看一下效果
    let obj1 = {
        a: {
            c: /a/,
            d: undefined,
            b: null
        },
        b: function () {
            console.log(this.a)
        },
        c: [
            {
                a: 'c',
                b: /b/,
                c: undefined
            },
            'a',
            3
        ]
    }
    let obj2 = deepClone(obj1);
        console.log(obj2);

深拷貝

可以看到最終拷貝的結果是null、undefinde、function、RegExp等特殊的值也全部拷貝成功了,而且我們修改里邊的值也不會有任何問題的
到這里我們就實現了一個簡單的深拷貝,當然,我的這個也只是簡單實現一下,還有很多問題沒有解決,只是給您提供一個思路


結言
感謝您的查閱,代碼冗余或者有錯誤的地方望不吝賜教;菜鳥一枚,請多關照
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容

  • Swift1> Swift和OC的區別1.1> Swift沒有地址/指針的概念1.2> 泛型1.3> 類型嚴謹 對...
    cosWriter閱讀 11,136評論 1 32
  • 在說深拷貝與淺拷貝前,我們先看兩個簡單的案例: //案例1var num1 = 1, num2 = num1;co...
    lueyoo閱讀 625評論 0 1
  • 寫在前面 各類技術論壇關于深拷貝的博客有很多,有些寫的也比我好,那為什么我還要堅持寫這篇博客呢,之前看到的一篇博客...
    心_c2a2閱讀 21,184評論 3 18
  • 在 JS 中有一些基本類型像是Number、String、Boolean,而對象就是像這樣的東西{ name: '...
    tobAlier閱讀 583評論 0 0
  • 本文思維導圖如下: 本文首發于我的個人網站: http://cherryblog.site/本文作者: Cherr...
    sunshine小小倩閱讀 1,028評論 2 14