老生常谈之深浅拷贝

1,091 阅读3分钟

前言

在聊深浅拷贝之前,先复习一下JS中的数据类型:基本类型和引用类型。

基本类型:

  1. undefined
  2. null
  3. Boolean
  4. String
  5. Number
  6. Symbol
  7. ....

引用类型:

  1. Object
  2. Array
  3. Date
  4. function
  5. RegExp
  6. ....

基本类型值在内存中占据固定大小,保存在栈内存中。

引用类型的值是对象,保存在堆内存中,而栈内存储的是对象的变量标识符和对象在堆内存中的存储地址。

不同类型的复制方式是不同的。

浅拷贝

image.png

对于基本类型,从一个变量向另外一个新变量复制基本类型的值,会创建这个值的一个副本,并将该副本复制给新变量。

对于引用类型,从一个变量向另一个新变量复制引用类型的值,其实复制的是指针,最终两个变量都指向同一个对象(地址),两者的写入操作会相互影响。

手动版

var obj = {
    hero: 'vn',
    hp: 1000,
    ad: 200,
    goods: ['无尽', '绿叉', '破败']
}

var target = {}
copy(obj, target)

function copy(src, tar) {
    for(let key in src) {
        tar[key] = src[key]
    }
}

target.goods[0] = '天才'
console.log(obj.goods[0])

合并版

除了自己封装,JS还提供了一些api来供我们合并对象使用(浅拷贝)。

var target = Object.assign(obj, {})
target.goods[0] = '天才'
console.log(obj.goods[0])

解构版

当然,通过解构赋值也是可以的。

var target = { ...obj }
target.goods[0] = '天才'
console.log(obj.goods[0])

上面三种方式的效果是一样的(都是浅拷贝):

image.png

深拷贝

深拷贝主要针对的是Object Array等这类稍复杂的数据结构,可以从图中看到深拷贝之后的引用类型数据都各自指向各自的内存地址,两者相互独立互不影响。

image.png

粗暴版

我们所熟知的最为简单粗暴的一种方法

var obj = {
    hero: 'vn',
    hp: 1000,
    ad: 200,
    goods: ['无尽', '绿叉', '破败'],
    say: () => {
        console.log('暮已成终')
    }
}
var target = JSON.parse(JSON.stringify(obj))

该方法拷贝简单数据类型尚可,若是要拷贝函数、拷贝其中的引用类型等,那这个方法就不行,看接下来的手动版。

手动版

想要深拷贝一份独立的数据类型可以通过递归遍历,遇到Array等可以做单独处理。

function copy(src) {
    if(typeof src === 'object' && src) {
        let target = Array.isArray(src) ? [] : {}
        for(const key in src) {
            target[key] = copy(src[key])
        }
        return target
    } else {
        return src
    }
}

在日常开发中,上面的方法足以支撑。当然也可以使用第三放库如loadash等。

优化版

var obj = {
    hero: 'vn',
    hp: 1000,
    ad: 200,
    goods: ['无尽', '绿叉', '破败'],
    say: () => {
        console.log('暮已成终')
    }
}
obj.son = obj

image.png

有时候难免会有这种需求,循环引用引发起的bug,若数据比较大也会因为递归进入死循环导致栈溢出。

要解决这个问题,就需要将当前对象(src)和拷贝对象(target)的关系存起来(以键值对的形式)。

在开始拷贝前将当前对象(src)作为key值,拷贝对象(target)作为value值存起来。

当需要拷贝当前对象时,在map里寻找有无拷贝过这个对象,有的话直接返回,无的话继续拷贝。

可以选择Map这种数据结构来解决。

var obj = {
    hero: 'vn',
    hp: 1000,
    ad: 200,
    goods: ['无尽', '绿叉', '破败'],
    say: () => {
        console.log('暮已成终')
    }
}
var obj1 = copy(obj)
obj1.obj = obj
function copy(src, map = new Map()) {
    if(typeof src === 'object' && src) {
        let target = Array.isArray(src) ? [] : {}
        if(map.get(src)) return map.get(src)
        map.set(src, target)
        for(const key in src) {
            target[key] = copy(src[key])
        }
        return target
    } else {
        return src
    }
}

image.png

最后

欢迎补充,如果这篇文章帮助到了你,请不要吝啬点赞关注。

图自:参考文章