【前端芝士樹】淺拷貝、深拷貝以及Object.assign()的作用

【前端芝士樹】淺拷貝、深拷貝以及Object.assign()的作用

首先還是得回到Javascript的基本數據類型。

值類型[深拷貝]:數值Num、布爾值Boolean、字符串String、null、undefined。

基本類型值是指在棧內存保存的簡單數據段,在複製基本類型值的時候,會開闢出一個新的內存空間,將值複製到新的內存空間,舉個栗子:

var a = 1;
var b = a;
a = 2;
console.log(a);//輸出2;
console.log(b);//輸出1;

引用類型[淺拷貝]:對象、數組、函數等。

用類型值是保存在堆內存中的對象,變量保存的只是指向該內存的地址,在複製引用類型值的時候,其實只複製了指向該內存的地址,舉個栗子:

var a={b:1}
var a2 = a;
a2.b = 2;
console.log(a)  // 輸出 {b: 2}

所以深拷貝問題的出現就是爲了解決引用類型的數據的淺拷貝特性

實現對象深拷貝的幾種方法

  1. JSON.parse() && JSON.stringfy()
    將該對象轉換爲其 JSON 字符串表示形式,然後將其解析回對象。這感覺有點太過簡單了,但它確實有效:

    const obj = /* ... */;
    const copy = JSON.parse(JSON.stringify(obj));

    優點是,如果沒有循環對象,並且不需要保留內置類型,使用該方法皆可以獲得最快的跨瀏覽器的克隆性能。
    這裏的缺點是創建了一個臨時的,可能很大的字符串,只是爲了把它重新放回解析器。
    另一個缺點是這種方法不能處理循環對象,而且循環對象經常發生。
    例如,當我們構建樹狀數據結構,其中一個節點引用其父級,而父級又引用其子級。

    const x = {};
    const y = {x};
    x.y = y; // Cycle: x.y.x.y.x.y.x.y.x...
    const copy = JSON.parse(JSON.stringify(x)); // throws!

    另外,諸如 Map, Set, RegExp, Date, ArrayBuffer 和其他內置類型在進行序列化時會丟失。

  2. MessageChannel && postMessage 結構化克隆算法
    這種方法的缺點是它是異步的。雖然這並無大礙,但是有時候你需要使用同步的方式來深度拷貝一個對象。

    function structuralClone(obj) {
      return new Promise(resolve => {
        const {port1, port2} = new MessageChannel();
        port2.onmessage = ev => resolve(ev.data);
        port1.postMessage(obj);
      });
    }
    
    const obj = /* ... */;
    const clone = await structuralClone(obj);

Array.slice()Array.concat()方法屬於深拷貝嗎?

這個我都被弄糊塗了,網上找了些資料才捋清了一下。

對於一維數組而言

  1. arrayObj.slice(start, [end])

    var arr1 = ["1","2","3"];
    var arr2 = arr1.slice(0);
    arr2[1] = "9";
    console.log("數組的原始值:" + arr1 ); //1,2,3
    console.log("數組的新值:" + arr2 ); //1,9,3
  2. arrayObj.concat(arr1,arr2 ... )

    var arr1 = ["1","2","3"];
    var arr2 = arr1.concat();
    arr2[1] = "9";
    console.log("數組的原始值:" + arr1 ); //1,2,3
    console.log("數組的新值:" + arr2 );//1,9,3

那數組裏面如果包含對象呢?

var arr1 = [{"name":"weifeng"},{"name":"boy"}];//原數組
var arr2 = [].concat(arr1);//拷貝數組
arr1[1].name="girl";
console.log(arr1);// [{"name":"weifeng"},{"name":"girl"}]
console.log(arr2);//[{"name":"weifeng"},{"name":"girl"}]

var a1=[["1","2","3"],"2","3"],a2;
a2=a1.slice(0);
a1[0][0]=0; //改變a1第一個元素中的第一個元素
console.log(a2[0][0]);  //影響到了a2

從上面兩個例子可以看出,由於數組內部屬性值爲引用對象,因此使用slice和concat對對象數組的拷貝,整個拷貝還是淺拷貝,拷貝之後數組各個值的指針還是指向相同的存儲地址。

Array.slice()Array.concat() 這兩個方法,僅適用於對不包含引用對象的一維數組的深拷貝!

Object.assign() 方法

Object.assign()考察點是ES6中實現對象複製,關於Object.assign()這個函數這裏有一篇文章講得非常詳細明白。

ES6提供了Object.assign(),用於合併/複製對象的屬性。

Object.assign(target, source_1, ..., source_n)

下面是一個例子

var o1 = { a: 1, b: 1, c: 1 };
var o2 = { b: 2, c: 2 };
var o3 = { c: 3 };

var obj = Object.assign({}, o1, o2, o3);
console.log(obj); // { a: 1, b: 2, c: 3 }

那麼Object.assign()方法是淺拷貝還是深拷貝呢?請看下面這個例子:

function mutateDeepObject(obj) {
  obj.a.thing = true;
}

const obj = {a: {thing: false}};
const copy = Object.assign({}, obj);
mutateDeepObject(copy)
console.log(obj.a.thing); // prints true 
Object.assign(target, sources...)是一個簡單的拷貝對象的方式,屬於淺拷貝。它接受任意數量的源對象,主要作用就是枚舉它們的所有屬性並分配給target
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章