面試官:能否用JavaScript實現深度克隆

前言

 前拷貝和深拷貝都是對於JS中的引用類型而言的,淺拷貝就只是複製對象的引用,如果拷貝後的對象發生變化,原對象也會發生變化。只有深拷貝纔是真正地對對象的拷貝。

首先,需要知道,JavaScript中的數據類型分爲基本數據類型和引用數據類型,對於基本數據類型而言,沒有深淺拷貝的區別,我們所說的深拷貝都是對於引用數據類型而言的。

淺拷貝

    const obj = { a: 'a', b: 'b', c: [1, 2, 3], d: { dd: 'dd' } }
    const array = [1, 2, 3, 4, 5]
    const CloneObj = obj
    const CloneArray = array
    CloneObj.a = 'c'
    CloneArray.push(6)
    console.log(CloneArray, CloneObj) 
 //  [1, 2, 3, 4, 5, 6]
   console.log(CloneObj)
 // a: "c"
 // b: "b"
 // c: (3) [1, 2, 3]
 // d: {dd: "dd"}

可以清楚地看到當CloneObj和CloneArray發生變化時,obj和array也發生了變化。

深拷貝

深拷貝就是對目標完全的拷貝,只要進行了深拷貝,他們就不會再互相影響。

目前實現深度拷貝的方法主要有兩種:

1. 利用JSON對象中的parse和stringify

2.利用遞歸來實現每一層重新創建對象並賦值

首先看第一種方式

    // 使用JSON來實現深度克隆
    const obj1 = { a: 'a', b: 'b', c: [1, 2, 3], d: { dd: 'dd' } }
    const CloneObj1 = JSON.parse(JSON.stringify(obj1))
    console.log(obj1 === CloneObj1) // false
    CloneObj1.a = { aa: 'aa' }
    console.log(obj1) //a: "a", b: "b",c: [1, 2, 3],d: {dd: "dd"}
    const array1 = [1, 2, 3, 4, 5]
    const CloneArray1 = JSON.parse(JSON.stringify(array1))
    console.log(array1 === CloneArray1) // false
    CloneArray1.push(6)
    console.log(array1)  // [1, 2, 3, 4, 5]

以上代碼確實實現了深度拷貝,但是這種方法只能使用一些簡單的情況,比如如果對象中的一個屬性值是一個函數,那麼使用這個方法會導致屬性丟失。

因爲undefined、function、symbol會在轉換過程中被忽略,所以當一個對象中有函數時,就不能用這個方法進行深拷貝。

第二種方式:遞歸

  function DeepClone(source) {
      // 判斷目標是數組還是對象
      const targetObj = source.constructor === Array ? [] : {}
      for (let key in source) {
        if (source.hasOwnProperty(key)) {
          //   如果是對象就遞歸
          if (source[key] && typeof source[key] === 'object') {
            targetObj[key] = source[key].constructor === Array ? [] : {}
            targetObj[key] = DeepClone(source[key])
          } else {
            targetObj[key] = source[key]
          }
        }
      }
      return targetObj
    }

首先判斷目標的類型,但是要怎麼判斷呢??? 我們可以根據constructor屬性來判斷,因爲實例對象的constructor屬性指向它的構造函數之前我們提到過,只有引用類型的數據纔有淺拷貝深拷貝之分,所以接下來就是要判斷是不是引用類型,如果是就繼續向下進行遞歸,如果不是則直接賦值,這樣就實現了深度拷貝。

那麼我們來測試一下:

    var a = {
      msg: '213123',
      data: '2018141142',
      getName: function() {
        return 'Bob'
      }
    }
    var b = DeepClone(a)
    b.data = '0'
    console.log(a)  // {msg: "213123", data: "2018141142", getName: ƒ}

可以,沒有問題!

補充:JavaScript中拷貝的方法

我們知道在JavaScript中,數組有兩個方法 concat 和 slice 是可以實現對原數組的拷貝的,這兩個方法都不會修改原數組,而是返回一個修改後的新數組。

同時,ES6 中 引入了 Object.assgn 方法和 ... 展開運算符也能實現對對象的拷貝。

經過我自己的測試,發現他們都是首層淺拷貝。

總結:

1. 賦值運算符=實現的是淺拷貝,只拷貝對象的引用值

2. JavaScript中數組和對象自帶的拷貝方法都是首層淺拷貝

3. JSON實現的是深拷貝,但是對目標對象有要求

4. 如果想實現真正意義上的深拷貝,採用上面的遞歸方式

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章