js es6淺克隆與深克隆(deep clone)詳解與實現

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

在JavaScript中,我們瞭解對象類型在賦值的過程中其實是複製了地址,從而會導致改變了一方其他也都被改變的情況。通常在開發中我們不希望出現這樣的問題,我們可以使用淺拷貝來解決這個情況。

淺拷貝

首先可以通過Object.assign來解決這個問題,很多人認爲這個函數是用來深拷貝的。其實並不是,Object.assign只會拷貝所有的屬性值到新的對象中,如果屬性值是對象的話,拷貝的是地址,所以並不是深拷貝。

let a = {
  age: 1
}
let b = Object.assign({}, a)
a.age = 2
console.log(b.age) // 1

另外我們還可以通過展開運算符...來實現淺拷貝

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

通常淺拷貝就能解決大部分問題了,但是當我們遇到如下情況就可能需要使用到深拷貝了

let a = {
  age: 1,
  jobs: {
    first: 'FE'
  }
}
let b = { ...a }
a.jobs.first = 'native'
console.log(b.jobs.first) // native

淺拷貝只解決了第一層的問題,如果接下去的值中還有對象的話,那麼就又回到最開始的話題了,兩者享有相同的地址。要解決這個問題,我們就得使用深拷貝了。

深拷貝

這個問題通常可以通過JSON.parse(JSON.stringify(object))來解決。

let a = {
  age: 1,
  jobs: {
    first: 'FE'
  }
}
let b = JSON.parse(JSON.stringify(a))
a.jobs.first = 'native'
console.log(b.jobs.first) // FE

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

  • 會忽略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)

如果你有這麼一個循環引用對象,你會發現並不能通過該方法實現深拷貝

在遇到函數、undefined或者symbol的時候,該對象也不能正常的序列化

let a = {
  age: undefined,
  sex: Symbol('male'),
  jobs: function() {},
  name: 'vue'
}
let b = JSON.parse(JSON.stringify(a))
console.log(b) // {name: "vue"}

你會發現在上述情況中,該方法會忽略掉函數和undefined

但是在通常情況下,複雜數據都是可以序列化的,所以這個函數可以解決大部分問題。

如果你所需拷貝的對象含有內置類型並且不包含函數,可以使用MessageChannel

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

var obj = {
  a: 1,
  b: {
    c: 2
  }
}

obj.b.d = obj.b

// 注意該方法是異步的
// 可以處理 undefined 和循環引用對象
const test = async () => {
  const clone = await structuralClone(obj)
  console.log(clone)
}
test()

當然你可能想自己來實現一個深拷貝,但是其實實現一個深拷貝是很困難的,需要我們考慮好多種邊界情況,比如原型鏈如何處理、DOM 如何處理等等,所以這裏我們實現的深拷貝只是簡易版,並且我其實更推薦使用lodash 的深拷貝函數

function deepClone(obj) {
  function isObject(o) {
    return (typeof o === 'object' || typeof o === 'function') && o !== null
  }

  if (!isObject(obj)) {
    throw new Error('非對象')
  }

  let isArray = Array.isArray(obj)
  let newObj = isArray ? [...obj] : { ...obj }
  Reflect.ownKeys(newObj).forEach(key => {
    newObj[key] = isObject(obj[key]) ? deepClone(obj[key]) : obj[key]
  })

  return newObj
}

let obj = {
  a: [1, 2, 3],
  b: {
    c: 2,
    d: 3
  }
}
let newObj = deepClone(obj)
newObj.b.c = 1
console.log(obj.b.c) // 2
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章