JS-深淺拷貝

爲了方便知識的複習和查看,將其記錄下來,一塊兒努力吧!前端

涉及的面試題:什麼是淺拷貝?如何實現淺拷貝?什麼是深拷貝?如何實現深拷貝?面試

在說深淺拷貝以前,咱們須要瞭解一下不一樣數據類型的變量的存儲方式。數組

咱們都知道js的數據類型分爲兩大類:bash

  • 基本數據類型異步

    Number 、String、 Boolean、 Undefined、 Null、 Symbol函數

  • 引用數據類型post

    Object(Array/Rex)ui

基本數據類型保存在棧內存,引用類型保存在堆內存中。根本緣由在於保存在棧內存的必須是大小固定的數據,引用類型的大小不固定,只能保存在堆內存中,可是能夠把它的地址寫在棧內存中以供咱們訪問。spa

let eg1 = {
    num:1,
    obj: {
        name:'gxm',
        age:18
    }
}
複製代碼

這個eg1對象裏的屬性的儲存狀況以下:3d

淺拷貝

什麼是淺拷貝?

淺拷貝只複製指向某個對象的指針,而不復制對象自己,新舊對象仍是共享同一塊內存。因此原對象和拷貝後的對象是相互影響的。

如何實現淺拷貝?

(1)經過 Object.assign 來解決問題。

let real = {
    str: '我是本體',
    obj: {
        name:'gxm',
        age:18
    }
}
let clone = Object.assign({}, real)
clone.str = '我克隆了';
console.log(real.str);      //我是本體
console.log(clone.str);     //我克隆了
clone.obj.age = 16 ; 
console.log(real.obj.age);  //16
console.log(clone.obj.age); //16
複製代碼

經過這個例子,咱們也能夠看出Object.assign()只會拷貝全部屬性值到新的對象中,若是屬性值是對象的話,拷貝的是地址。因此Object.assign不是深拷貝。

(2)經過展開運算符(…)來解決

let a = {
    age: 1
}
let b = {...a}
a.age = 2
console.log(b.age) // 1
複製代碼

一般淺拷貝就能解決大部分問題了,可是當咱們遇到以下狀況就須要使用到深拷貝了

let real = {
    str: '我是本體',
    obj: {
        name:'gxm',
        age:18
    }
}
let clone = {...real}
clone.str = '我克隆了';
console.log(real.str);      //我是本體
console.log(clone.str);     //我克隆了
clone.obj.age = 16 ; 
console.log(real.obj.age);  //16
console.log(clone.obj.age); //16
複製代碼

淺拷貝只能解決了第一層的問題,若是接下去的值中還有對象的話,二者享有相同的引用。要解決這個問題,咱們須要引入深拷貝。

深拷貝

什麼深拷貝?

深拷貝就是拷貝多層,嵌套的對象也會被拷貝出來,至關於開闢一個新的內存地址用於存放拷貝的對象。

個人理解深拷貝就是徹底拷貝,且原對象和拷貝後的對象沒有任何關聯。

如何實現深拷貝?

(1)使用 JSON.parse(JSON.stringify(object)) 深層拷貝

let realObj = {
    name: 'gxm',
    jobs: {
        first: 'BAT'
    }
}
let deepCloneObj = JSON.parse(JSON.stringify(realObj))
realObj.jobs.first = 'Google'
console.log(deepCloneObj.jobs.first) // BAT
複製代碼

可是該方法也是有侷限性的:

  • 會忽略 undefined
  • 會忽略 symbol
  • 不能序列化函數
  • 不能解決循環引用的對象
let obj = {
  a: 1,
  b: {
    c: 2,
    d: 3,
  },
}
obj.c = obj.b
obj.e = obj.a
obj.b.c = obj.c
obj.b.d = obj.b
obj.b.e = obj.b.c
let newObj = JSON.parse(JSON.stringify(obj))
console.log(newObj)
複製代碼

若是你有這麼一個循環引用對象,你會發現你不能經過該方法深拷貝。

img

(2)MessageChannel 深層拷貝

MessageChannel 深層拷貝能夠用在含有有undefined和循環引用的場景。

// 有undefined + 循環引用
let obj = {
  a: 1,
  b: {
    c: 2,
    d: 3,
  },
  f: undefined
}
obj.c = obj.b;
obj.e = obj.a
obj.b.c = obj.c
obj.b.d = obj.b
obj.b.e = obj.b.c

function deepCopy(obj) {
  return new Promise((resolve) => {
    const {port1, port2} = new MessageChannel();
    port2.onmessage = ev => resolve(ev.data);
    port1.postMessage(obj);
  });
}

deepCopy(obj).then((copy) => {           // 請記住`MessageChannel`是異步的這個前提!
    let copyObj = copy;
    console.log(copyObj, obj)
    console.log(copyObj == obj)
});
複製代碼

運行結果以下:

但拷貝有函數對象時,仍是會報錯。

(3)自實現深拷貝

eg1: 這個例子能夠實現基本數據類型、引用對象類型和對象裏嵌套對象的深層拷貝,但不適用於循環引用。所謂循環引用就是本身調用本身。

function deepClone(obj) {
    //obj是null的狀況
    if(obj == undefined){
      return obj;
    }
    //obj是基本數據類型的狀況
    if(typeof obj !== 'object'){//此處的object必定要小寫
      return obj;
    }
    //正則、時間
    if(obj instanceof RegExp){
      return new RegExp(obj);
    }
    if(obj instanceof Date){
      return new Date(obj)
    }
    //若是obj是的數組或對象時
    let cloneObj = new obj.constructor;//得到obj的構造函數
    for(let key in obj ){
      if(obj.hasOwnProperty(key)){
        // cloneObj[key] = obj[key];
        //這種狀況是obj[key]不是引用類型的值時,能夠直接使用,可是若是obj[key]是多層嵌套的引用類型呢?就使用以下狀況:
        cloneObj[key] = deepClone(obj[key]);
      }
    }
    return cloneObj;
}

let arrObj = {
    name:'gxm',
    age:18,
    friends:['cc','yy','mm','nn',['cc','xx',['LL','QQ']]],
    others:undefined,
    address:null
}
let newArrObj = deepClone(arrObj);
newArrObj.age = 20;
newArrObj.friends[4][0] = 'gg';
console.log('原對象------',arrObj);
console.log('新對象------',newArrObj);
複製代碼

運行結果以下:

原對象------
{   name: "gxm",
    age: 18, 
    friends: ['cc','yy','mm','nn',['cc','xx',['LL','QQ']]],
    others: undefined,
    address: null
}
新對象------
{
    name: "gxm",
    age: 20, 
    friends: ['cc','yy','mm','nn',['gg','xx',['LL','QQ']]],
    others: undefined,
    address: null
}
複製代碼

eg2: 若是拷貝的對象中的屬性的值是循環引用的話,上面的深拷貝就不適用了,會崩掉的。要想解決這個問題,能夠採用hash表進行映射。

function deepClone(obj,hash = new WeakMap()) {
//hash爲了保存全部的數據,其實是使用WeakMap將數據暫存下來,能夠防止內存泄漏

    //obj是null的狀況
    if(obj == undefined){
      return obj;
    }
    //obj是基本數據類型的狀況
    if(typeof obj !== 'object'){
      return obj;
    }
    //正則、時間
    if(obj instanceof RegExp){
      return new RegExp(obj);
    }
    if(obj instanceof Date){
      return new Date(obj)
    }
    
    /* 
    * 第一次拷貝的時候hash表裏確定沒有,下面的判斷就不會執行。
    * 以後若是hash表裏有這個對象,就return出來,下面的for循環就不會執行。
    * 即若是已經拷貝過了該屬性,就不會再接着拷貝了,防止遞歸拷貝。
    */
    if(hash.get(obj)){
      return hash.get(obj);
    }
    
    //若是obj是的數組或對象時
    let cloneObj = new obj.constructor;//得到obj的構造函數
    
    //拷貝前和拷貝後進行對比
    hash.set(obj , cloneObj);
    
    for(let key in obj ){
      if(obj.hasOwnProperty(key)){
        cloneObj[key] = deepClone(obj[key], hash);
      }
    }
    return cloneObj;
}

let arrObj = {
    name:'gxm',
    age:18
}
arrObj.objj = arrObj;
let newArrObj = deepClone(arrObj);
console.log('原對象------',arrObj);
複製代碼

運行結果以下:

參考掘金小冊->前端面試之道

若是有誤,請留言!

相關文章
相關標籤/搜索