了解Javascript深拷贝与浅拷贝

7/19/2021 深拷贝

# 了解Javascript深拷贝与浅拷贝

  1. # 深拷贝、浅拷贝概念

在学习JS时可能经常听到深拷贝、浅拷贝这两个概念,那么它们的概念和区别时什么呢?
浅拷贝只复制指向某个对象的指针,而不复制对象本身,新旧对象还是指向同一块内存;
深拷贝会另外创造一个一模一样的对象,新旧对象所指的内存不同,所以深拷贝修改新对象不会改到旧对象;

  1. # 常用数据类型的深浅拷贝判断

在上一篇文章——javascript数据类型判断 中,提到过JS主要有两类数据类型:

  • 基本数据类型 (Boolean, Number, String, undefined, BigInt, Symbol, Null )
  • 复杂数据类型 (Object) 其中,基本数据类型是保存在栈内存的,复杂数据类型是保存在堆类型的,引用数据类型的变量是一个指向堆内存中实际对象的引用,存在栈中;

所以,当基本数据类型拷贝时,只需要在栈中分配一个新值和旧值一样就行,根据深浅拷贝的概念,所以基本数据类型的拷贝是深拷贝;
而对于复杂数据类型拷贝时,系统也为新的变量在栈内存中分配了一个值,但这个值指向的堆中的对象是不变的,即和旧对象具有同一个对象的引用,那当新对象的值改变时,会改变引用的指向的堆内存,旧对象也会一起改变,所以复杂数据类型的引用是浅拷贝;
3. ### 深拷贝的实现以及手写深拷贝方法

  1. # JSON.parse(JSON.stringfy())方法

这种方法最为简单暴力,对于简单的JSON格式对象是可以的,但是还是会有如下缺点:

  • 这样会丢失原对象的constructor,失去之前的构造函数
  • 对于set,map,Date,RegExp等特殊对象会在转换的时候丢失
  1. # 函数库lodash的_.clone方法

这种方法引入lodash库就可以了,但是面试时不能这么说啊,所以还是老老实实琢磨下怎么自己手写个像样的深拷贝吧

  1. # 递归实现深拷贝

既然这样,那就自己手写一个深拷贝吧
首先思路如下:

  1. 如果是基本类型:无需继续拷贝,直接返回;但考虑到ES6新加入的数据类型Symbol,在for in 中是取不到的,所以使用Reflect.ownKeys()来遍历该属性
  2. 如果是复杂类型,则需要递归取值
  3. 循环引用的情况,如obj.param = obj使用WeakMap做个缓存取值
//手写深拷贝方法deepClone
function deepClone(obj, map = new WeakMap()){
    // 如果为基本类型
    if(!obj || typeof obj !== 'object'){
        return obj
    }
    // 判断是否为数组
    const cloneObj = Array.isArray(obj)? [] : {};
    // 判断是否为循环引用
    if(map.get(obj)){
        return map.get(obj)
    }
    map.set(obj, cloneObj)
    //递归取值赋值
    for(let key of Reflect.ownKeys(obj)){
        cloneObj[key] = deepClone(obj[key],map)
    }
    return cloneObj;
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19

这个深拷贝基本实现了对基本对象的深拷贝,但还有一些问题:

  • 如果对象的value值为set,map,Date,RegExp这些没有做特殊处理,不能拷贝
  • 但如果是面试,手写到这些也就够了,其他这些可以使用构造函数重新构造一个