「深拷貝」 與 「淺拷貝」 的區別,JS實現深淺拷貝的幾種方法

1、 「深拷貝」 與 「淺拷貝」 的區別

對於這個問題,能夠考慮從深拷貝和淺拷貝的使用或者起源提及,也就是爲何會出現這個問題。javascript

首先了解一些javascript的基本知識。java


【1】javascript變量包含兩種不一樣數據類型的值:基本類型和引用類型。es6

①基本類型值指的是簡單的數據段,包括es6裏面新增的一共是有6種,具體以下:數組

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

②引用類型值指那些可能由多個值構成的對象,具體以下:測試

Object(Object、Array、Function)ui

在將一個值賦給變量時,解析器必須肯定這個值是基本類型值仍是引用類型值。基本數據類型是按值訪問的,由於能夠操做保存在變量中的實際的值。spa

引用類型的值是保存在內存中的對象。與其餘語言不一樣,JavaScript 不容許直接訪問內存中的位置,也就是說不能直接操做對象的內存空間。 在操做對象時, 其實是在操做對象的引用而不是實際的對象。3d


【2】javascript的變量的存儲方式--棧(stack)和堆(heap)code

棧:自動分配內存空間,系統自動釋放,裏面存放的是基本類型的值和引用類型的地址

堆:動態分配的內存,大小不定,也不會自動釋放。裏面存放引用類型的值。

【3】javascript值傳遞與址傳遞

基本類型與引用類型最大的區別實際就是傳值與傳址的區別

值傳遞:基本類型採用的是值傳遞。

let a = 10; // 定義一個變量a並賦值爲10
    let b = a;  // 將a的值10賦值給b (a、b都是基本類型,值傳遞)
    b++;  // b自加
    console.log(a, b) // 10, 11複製代碼

址傳遞:引用類型則是地址傳遞,將存放在棧內存中的地址賦值給接收的變量。

let a = ['a', 'b', 'c']; // 定義一個數組a並賦值 
    let b = a;   // 數組是引用類型,採用地址傳遞,將a的地址賦值給b
    b.push('d'); // 給b數組增長一個'd'元素
    console.log(a) // ['a', 'b', 'c', 'd']
    console.log(b) // ['a', 'b', 'c', 'd']複製代碼

分析:因爲a和b都是引用類型,採用的是址傳遞,即a將地址傳遞給b,那麼a和b必然指向同一個地址(引用類型的地址存放在棧內存中),而這個地址都指向了堆內存中引用類型的值。當b改變了這個值的同時,由於a的地址也指向了這個值,故a的值也跟着變化。
舉例:就比如是a租了一間房,將房間的地址給了b,b經過地址找到了房間,那麼b對房間作的任何改變(添加了一些綠色植物)對a來講確定一樣是可見的。

那麼如何解決上面出現的問題,就是使用淺拷貝或者深拷貝了。 JS的基本類型不存在淺拷貝仍是深拷貝的問題,主要是針對於引用類型

【4】淺拷貝和深拷貝區別總結

字面意思:

淺拷貝---拷貝的級別淺。

深拷貝---拷貝級別更深。

具體:

淺拷貝---淺拷貝是指複製對象的時候,只對第一層鍵值對進行獨立的複製,若是對象內還有對象,則只能複製嵌套對象的地址

深拷貝---深拷貝是指複製對象的時候徹底的拷貝一份對象,即便嵌套了對象,二者也相互分離,修改一個對象的屬性,也不會影響另外一個。其實只要遞歸下去,把那些屬性的值仍然是對象的再次進入對象內部一 一進行復制便可。

淺拷貝案例

淺拷貝解決就是先設置一個新的對象obj2,經過遍歷的方式將obj1對象的值一 一賦值給obj2對象。

// 數組的淺拷貝
    let arr1 = [1, 2, 3]
    let arr2 = []
    for (let i in arr1) {
      arr2[i] = arr1[i]
    }
    arr2.push(4)
    console.log(arr1)  // [1, 2, 3]
    console.log(arr2)  // [1, 2, 3, 4]

    // 對象的淺拷貝
    let obj1 = {
      a: '1',
      b: '2',
      c: '3'
    }
    let obj2 = {}
    for (let i in obj1) {
      obj2[i] = obj1[i]
    }
    obj2.d = '4'
    console.log(obj1) // {a: "1", b: "2", c: "3"}
    console.log(obj2) // {a: "1", b: "2", c: "3", d: "4"}

    // 淺拷貝函數封裝
    function shallowCopy(obj1, obj2) {
      for(var key in obj1) {
        obj2[key] = obj1[key]
      }
    }複製代碼

但上面代碼只能實現一層的拷貝,沒法進行深層次的拷貝,封裝函數再次經過對象數組嵌套測試以下:

// 淺拷貝函數封裝
    function shallowCopy(obj1, obj2) {
      for(var key in obj1) {
        obj2[key] = obj1[key]
      }
    }

    // 對象的淺拷貝
    let obj1 = {
      a: '1',
      b: '2',
      c: {
        name: 'Demi'
      }
    }
    let obj2 = {}
    shallowCopy(obj1, obj2) //將obj1的數據拷貝到obj2
    obj2.c.name = 'dingFY'
    console.log(obj1) // {a: "1", b: "2", c: {name: 'dingFY'}}
    console.log(obj2) // {a: "1", b: "2", c: {name: 'dingFY'}}複製代碼

結果證實,若是對象內還有對象,則只能複製嵌套對象的地址,沒法進行深層次的拷貝,當改變obj2嵌套對象c的值後,obj1嵌套對象c的值也跟着變了

這個時候咱們可使用深拷貝來完成,所謂深拷貝,就是可以實現真正意義上的數組和對象的拷貝,咱們經過遞歸調用淺拷貝的方式實現。

深拷貝案例

// 深拷貝函數封裝
    function deepCopy(obj) {
      // 根據obj的類型判斷是新建一個數組仍是對象
      let newObj = Array.isArray(obj)? [] : {};
      // 判斷傳入的obj存在,且類型爲對象
      if (obj && typeof obj === 'object') {
        for(key in obj) {
          // 若是obj的子元素是對象,則進行遞歸操做
          if(obj[key] && typeof obj[key] ==='object') {
            newObj[key] = deepCopy(obj[key])
          } else {
          // // 若是obj的子元素不是對象,則直接賦值
            newObj[key] = obj[key]
          }
        }
      }
      return newObj // 返回新對象
    }

    // 對象的深拷貝
    let obj1 = {
      a: '1',
      b: '2',
      c: {
        name: 'Demi'
      }
    }
    let obj2 = deepCopy(obj1) //將obj1的數據拷貝到obj2
    obj2.c.name = 'dingFY'
    console.log(obj1) // {a: "1", b: "2", c: {name: 'Demi'}}
    console.log(obj2) // {a: "1", b: "2", c: {name: 'dingFY'}}複製代碼

結果證實上面的代碼能夠實現深層次的克隆。

數組的淺拷貝

若是是數組,咱們能夠利用數組的一些方法好比:slice、concat 返回一個新數組的特性來實現拷貝。

【1】Array.concat()

let arr = ['one', 'two', 'three'];
    let newArr = arr.concat();
    newArr.push('four')

    console.log(arr)    // ["one", "two", "three"]
    console.log(newArr) // ["one", "two", "three", "four"]複製代碼

【2】Array.slice()

let arr = ['one', 'two', 'three'];
    let newArr = arr.slice();
    newArr.push('four')

    console.log(arr)    // ["one", "two", "three"]
    console.log(newArr) // ["one", "two", "three", "four"]複製代碼

數組的深拷貝

這裏介紹一個技巧,不只適用於數組還適用於對象!不過存在一個問題,就是不能拷貝函數

let arr = {
      a: 'one', 
      b: 'two', 
      c: {
        name: 'Demi'
      }
    };

    let newArr = JSON.parse( JSON.stringify(arr) );
    newArr.c.name = 'dingFY'
    console.log(arr);    // {a: "one", b: "two", c: {name: 'Demi'}}
    console.log(newArr); // {a: "one", b: "two", c: {name: 'dingFY'}}


    // 測試函數可否複製
    let arr = {
      a: 'one', 
      b: ()=>{
        console.log('test')
      }
    };

    let newArr = JSON.parse( JSON.stringify(arr) );
    console.log(arr);    // {a: "one", b: ()=>{console.log('test')}}
    console.log(newArr); // {a: "one"} // 函數沒有複製成功複製代碼

對象的淺拷貝

Object.assign()方法能夠把任意多個的源對象自身的可枚舉屬性拷貝給目標對象,而後返回目標對象。可是 Object.assign() 進行的是淺拷貝

let arr = {
      a: 'one', 
      b: 'two', 
      c: 'three'
    };

    let newArr = Object.assign({}, arr)
    newArr.d = 'four'
    console.log(arr);    // {a: "one", b: "two", c: "three"}
    console.log(newArr); // {a: "one", b: "two", c: "three", d: "four"}複製代碼

淺拷貝封裝方法

原理:遍歷對象,而後把屬性和屬性值都放在一個新的對象

let shallowCopy = function (obj) {
      // 只拷貝對象
      if (typeof obj !== 'object') return;
      // 根據obj的類型判斷是新建一個數組仍是對象
      let newObj = obj instanceof Array ? [] : {};
      // 遍歷obj,而且判斷是obj的屬性才拷貝
      for (let key in obj) {
        if (obj.hasOwnProperty(key)) {
          newObj[key] = obj[key];
        }
      }
      return newObj;
    }複製代碼

深拷貝封裝方法

原理:咱們在拷貝的時候判斷一下屬性值的類型,若是是對象,咱們遞歸調用深拷貝函數就行了

let deepCopy = function (obj) {
      // 只拷貝對象
      if (typeof obj !== 'object') return;
      // 根據obj的類型判斷是新建一個數組仍是對象
      let newObj = obj instanceof Array ? [] : {};
      // 遍歷obj,而且判斷是obj的屬性才拷貝
      for (let key in obj) {
        if (obj.hasOwnProperty(key)) {
          // 若是obj的子屬性是對象,則進行遞歸操做,不然直接賦值
          newObj[key] = typeof obj[key] === 'object' ? deepCopy(obj[key]) : obj[key];
        }
      }
      return newObj;
    }複製代碼
相關文章
相關標籤/搜索