深耕系列之深拷贝和浅拷贝

495 阅读2分钟

小知识,大挑战!本文正在参与“程序员必备小知识”创作活动。

问题引入

大家先来看以下这段代码:

let obj = {
    val: 1
}
let copy = obj
obj.val = 2
console.log(copy.val)

相信大家都知道打印出来的是2,说明obj的改动会影响到copy,原因在于obj是引用类型。

那如果希望obj的改动不影响到copy,该怎么办呢?

拷贝的类型

在这里,引入了两个概念:深拷贝和浅拷贝

浅拷贝:只是拷贝了obj的第一层,如果obj有多层,那么obj的改动也会影响到拷贝后的变量。

深拷贝:完全拷贝了obj,obj的改动不会影响到拷贝后的变量。

实际开发经常遇到拷贝的情况,那如何实现深拷贝和浅拷贝呢?

实现代码

浅拷贝

  1. 通过Object.assign
let obj = {
    val: 1
}
let copy = Object.assign({}, obj)
obj.val = 2
console.log(copy.val)
  1. 通过展开运算符...
let obj = {
    val: 1
}
let copy = { ...obj }
obj.val = 2
console.log(copy.val)

通常浅拷贝就能解决大部分的问题,但是如果遇到更深一层的拷贝就需要深拷贝了

let obj = {
    val: {
	val: 1
    }
}
let copy = { ...obj }
obj.val.val = 2
console.log(copy.val.val)

运算得到的值是2,说明obj的改动影响到了copy变量,那么深拷贝如何使用?

深拷贝

  1. 通过JSON的序列化
let obj = {
    val: {
	val: 1
    }
}
let copy = JSON.parse(JSON.stringify(obj))
obj.val.val = 2
console.log(copy.val.val)

该方法存在局限性:

  • 会忽略undefined
  • 不能序列化函数
  • 不能解决循环调用的对象

比如:

let obj = {
    val: {
	val: function() { console.log('test') }
    }
}
let copy = JSON.parse(JSON.stringify(obj))
copy.val.val()

结果会报错,如下:

image.png

因此,在使用JSON实现深拷贝时,要注意这些边界条件。

  1. 自己实现简易深拷贝函数
function deepClone(source) {
  if (!source && typeof source !== 'object') {
    throw new Error('error arguments', 'deepClone')
  }
  const targetObj = source.constructor === Array ? [] : {}
  Object.keys(source).forEach(keys => {
    if (source[keys] && typeof source[keys] === 'object') {
      targetObj[keys] = deepClone(source[keys])
    } else {
      targetObj[keys] = source[keys]
    }
  })
  return targetObj
}

let obj = {
    val: {
	val: function() { console.log('test') }
    }
}
let copy = deepClone(obj)
copy.val.val()
  1. 通过lodash的深拷贝函数
import _ from 'lodash'

let obj = {
    val: {
	val: function() { console.log('test') }
    }
}
let copy = _.cloneDeep(obj)_
copy.val.val()

总结

我更推荐使用lodash的深拷贝函数,因为一个深拷贝的实现是很困难的,需要处理很多边界条件,比如原型链,DOM,函数等等。

以上就是我对深拷贝和浅拷贝的理解,如果觉得对你有帮助,请帮忙点个赞+评论+收藏。