深拷贝与浅拷贝的区别

我们常说的浅拷贝与深拷贝主要是针对Object,Array这样的引用类型, 为什么? 因为引用类型在JS里面存储的内存地址。

浅拷贝: 只会复制一层对象的属性 深拷贝: 递归复制对象所有层级属性

因此就会存在一个问题, 如果遇到引用类型, 浅拷贝它复制下来的是对象的指针地址, 导致复制后的对象中的引用类型属性是共享的。

下面分别从Array/Object来看看它们有哪些原生的拷贝方法。

Array的拷贝

原生数组支持的拷贝方法有: slice()/concat()/Array.from()/扩展运算符

// 只含基本数据类型的拷贝
const a = [1,2,3]
const b = a.slice()
const c = [...a]
const d = [].concat(a)
const e = Array.from(a)

a[0] = 0

// [ 0, 2, 3 ] [ 1, 2, 3 ] [ 1, 2, 3 ] [ 1, 2, 3 ] [ 1, 2, 3 ]
console.log(a, b, c, d, e)

// 包含引用类型的拷贝
const a1 = [1,2,3,{name: 'kobe'}]
const b1 = a1.slice()
const c1 = [...a1]
const d1 = [].concat(a1)
const e1 = Array.from(a1)

a1[3].name = 'change'

// [ 1, 2, 3, { name: 'change' } ] [ 1, 2, 3, { name: 'change' } ] 
// [ 1, 2, 3, { name: 'change' } ]
// [ 1, 2, 3, { name: 'change' } ] [ 1, 2, 3, { name: 'change' } ]
console.log(a1, b1, c1, d1, e1)
可见这些方法都只是浅拷贝。

Object的拷贝

原生的对象支持拷贝的方法有: Object.assign()/扩展运算符/JSON.parse(JSON.stringify(obj))

// 基本数据类型

const a = {
  name: 'kobe',
  age: 10
}
const b = {...a}
const c = Object.assign({},a)
const d = JSON.parse(JSON.stringify(a))
a.name = 'change'

// { name: 'change', age: 10 } { name: 'kobe', age: 10 } 
// { name: 'kobe', age: 10 } { name: 'kobe', age: 10 }
console.log(a,b,c,d) // 引用数据类型 const a1 = { info: { name: 'kobe', age: 10 } } const b1 = {...a1} const c1 = Object.assign({}, a1) const d1 = JSON.parse(JSON.stringify(a1)) a1.info.name = 'change' // { info: { name: 'change', age: 10 } } { info: { name: 'change', age: 10 } }
// { info: { name: 'change', age: 10 } } { info: { name: 'kobe', age: 10 } }
console.log(a1,b1,c1,d1) 发现Object.assign()/扩展运算符只是浅拷贝。 至于JSON.parse(JSON.stringify(obj))好像可以, 但是如果你去查询MDN文档会发现。
const test = {
  a: function () {
    console.log('a')
  },
  [Symbol('age')]: 10,
  name: undefined,
  address: null,
  time: new Date()
}


const clone = JSON.parse(JSON.stringify(test))

console.log(clone) // { address: null, time: '2019-05-18T14:24:49.050Z' }

手写深拷贝

可见JS本身不能提供深拷贝机制, 需要自己实现一个。

const test = {
  info: {
    name: 'kobe',
    nums: [1,2,3]
  }
}

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

function deepClone (source) {
  const obj = Array.isArray(source) ? [] : {}
  for (let key in source) {
    obj[key] = isObject(source[key]) ? deepClone(source[key]) : source[key]
  }
  return obj
}

const copy = deepClone(test)
test.info.nums.push(4)

// { info: { name: 'kobe', nums: [ 1, 2, 3, 4 ] } }
// { info: { name: 'kobe', nums: [ 1, 2, 3 ] } }
console.log(test, copy)

环就是我们常说的循环引用拷。

const test = {
  info: {
    name: 'kobe',
    nums: [1,2,3]
  }
}

test.loop = test;

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

function deepClone (source) {
  const obj = Array.isArray(source) ? [] : {}
  for (let key in source) {
    obj[key] = isObject(source[key]) ? deepClone(source[key]) : source[key]
  }
  return obj
}

const copy = deepClone(test)
test.info.nums.push(4)

console.log(test, copy) // Maximum call stack size exceeded

解决思路如下: 通过一个WeakMap来存储拷贝过的对象, 每一次拷贝之前先向WeakMap询问是否拷贝, 有直接返回没有就拷贝。

所以我们需要修改下代码:

const test = {
  info: {
    name: 'kobe',
    nums: [1,2,3]
  }
}

test.loop = test;

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

function deepClone (source, hash = new WeakMap()) {
  if (hash.has(source)) {
    return hash.get(source)
  }
  const obj = Array.isArray(source) ? [] : {}
  hash.set(source, obj)
  for (let key in source) {
    obj[key] = isObject(source[key]) ? deepClone(source[key], hash) : source[key]
  }

  return obj
}

const copy = deepClone(test)
test.info.nums.push(4)

console.log(test, copy)

特殊对象的拷贝

由于JS的对象类型太多, 这里我们只考虑比如Date, 正则, 其他自己扩展

const test = {
  info: {
    name: 'kobe',
    nums: [1, 2, 3]
  },
  reg: /[0-9]{4}/gi,
  Date: new Date()
}

test.loop = test;

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

function deepClone(source, hash = new WeakMap()) {
  let obj
  const Constructor = source.constructor
  switch (Constructor) {
    case Date:
      obj = new Constructor(source.getTime())
      break
    case RegExp:
      obj = new Constructor(source)
      break
    default:
      if (hash.has(source)) {
        return hash.get(source)
      }
      obj = new Constructor()
      hash.set(source, obj)
      break
  }

  for (let key in source) {
    obj[key] = isObject(source[key]) ? deepClone(source[key], hash) : source[key]
  }

  return obj
}

const copy = deepClone(test)
test.info.nums.push(4)

console.log(test, copy)

 

posted @ 2020-12-11 16:09  张胖胖-007  阅读(151)  评论(0)    收藏  举报