深拷贝

158 阅读3分钟

碎碎念

准备面试的一开始,就被深拷贝折磨了一番。倒不是说很难,只是不同人写的不一样,考虑的方面也不尽相同,所以我按照自己的理解写了一下。

考虑类型

深拷贝的难点就在于你需要考虑你复制的数据的类型是非常多的,我这里只考虑自己常用到的类型

  • 基本类型
  • 引用类型-函数、Object、Array、Symbol、Map、Get、Reg、Date

判断是否是引用类型使用工具函数isObject

  //需要注意这里判断null,typeof null也是object
  function isObject(target){
      return typeof target === 'object' && (target !== null)
  }

获取引用类型的具体类型使用getType

  //获取类型基本就无脑用这种方法就行
  function getType(target){
      return Object.prototype.toString.call(target)
  }

非引用类型

一个if,没什么好说的,不过这里注意的是function类型也一并当做非引用类型直接返回了,并不做特殊处理。

function deepClone(obj,map=new WeakMap()){
    if(isObject(obj)){
    }else return obj
  }

引用类型

引用类型也大体分为两类,一类是像Date、Reg这种不需要遍历的,一类是像Array、Object这种需要遍历的。

将需要遍历的单独拿出来放入deepTag数组里

let deepTag = ['[object Map]','[object Set]','[object Array]','[object Object]']

不需要遍历的

Symbol

例如Object(Symbol(1))

因为Symbol的值是独一无二的,所以symbol对象的复制其实就是用同一个symbol创建一个Object,也即:

if(type==='[object Symbol]'){
    return Object(Symbol.prototype.valueOf.call(obj));
}

RegExp

克隆正则分为三部分,一部分时克隆源,一部分是克隆标志符,一部分是克隆lastIndex

function cloneReg(obj){
    const result = new RegExp(obj.source,/\w*$/.exec(obj))
    result.lastIndex = obj.lastIndex
    return result
}

/\w*$/.exec(obj)的原理是对于正则串/xzc/ig可以匹配到标志符ig

Date/String/Number等

这一类都可以直接使用return new obj.constructor(obj)直接生成一个新对象返回

需要遍历的

对于所有需要遍历的,都需要使用cloneObj = new obj.constructor()来保留原型链,同时这样也不用特意去区分数组与对象了。

Map、Set

也不难,Map用set,Set用add即可

    if(type==='[object Map]'){
        obj.forEach((value,key)=>{
            cloneObj.set(key,deepClone(value))
        })
    }

    if(type==='[object Set]'){
        obj.forEach((value)=>{
            cloneObj.add(deepClone(value))
        })
    }

对象、数组

对象和数组也是和前面一样的遍历赋值,但是有一点需要注意的是,不能直接用for in这类语法遍历,因为对象里可能会有不可枚举型变量。而应该使用Reflect.ownKeys(obj)

    for (let key of Reflect.ownKeys(obj)) { 
        cloneObj[key] = deepClone(obj[key])
    }

循环调用

a={} a.b=a这种情况,也很好解决,使用WeakMap就行。之所以使用WeakMap而不是Map是因为WeakMap对键是弱引用的

let a = {x: 12};
let b = {y: 13};

let map = new Map();
let weakMap = new WeakMap();
map.set(a, '14');
weakMap.set(b, '15');

a = null;
b = null;

上面代码中a,b设置为null以后,map不会被回收,而weakMap会被回收。

解决循环调用只需要两三行代码即可

    if(map.has(obj)){
        return map.get(obj)
    }
    map.set(obj,cloneObj)

最后

至此,深拷贝也就写完了。测试用例如下

const map = new Map();
map.set('key', 'value');
map.set('violet', 'erergarden');

const set = new Set();
set.add('violet');
set.add('erergarden');

  let obj = {
    num: 0,
    str: '',
    boolean: true,
    unf: undefined,
    nul: null,
    obj: { name: '我是一个对象', id: 1 },
    arr: [0, 1, 2],
    func: function () { console.log('我是一个函数') },
    date: new Date(0),
    reg: new RegExp(/\d+/ig) ,
    [Symbol('1')]: 1,
    symbol: Object(Symbol(1)),
    map,
    set
  };
  Object.defineProperty(obj, 'innumerable', {
    enumerable: false, value: '不可枚举属性' }
  );
  obj.loop = obj

完整代码如下

  function getType(target){
      return Object.prototype.toString.call(target)
  }
  function isObject(target){
      return typeof target === 'object' && (target !== null)
  }
  function cloneReg(target){
      const result = new RegExp(target.source,/\w*$/.exec(target))
      result.lastIndex = target.lastIndex
      return result
  }
  let a = {
      reg:'/xz/w*cz/gim'
  }
  let deepTag = ['[object Map]','[object Set]','[object Array]','[object Object]']

  function deepClone(obj,map=new WeakMap()){
    if(isObject(obj)){
        let type = getType(obj)
        
        let cloneObj
        if(deepTag.includes(type)){
            cloneObj = new obj.constructor()
        }else{
            if(type==='[object Date]'){
                return new obj.constructor(obj)
            }
    
            if(type==='[object RegExp]'){
                return cloneReg(obj)
            }
    
            if(type==='[object Symbol]'){
                return Object(Symbol.prototype.valueOf.call(obj));
            }
            return new obj.constructor(obj)
        }

        if(type==='[object Map]'){
            obj.forEach((value,key)=>{
                cloneObj.set(key,deepClone(value,map))
            })
        }

        if(type==='[object Set]'){
            obj.forEach((value)=>{
                cloneObj.add(deepClone(value,map))
            })
        }

        if(map.has(obj)){
            return map.get(obj)
        }
        map.set(obj,cloneObj)

        for (let key of Reflect.ownKeys(obj)) { 
            cloneObj[key] = deepClone(obj[key],map)
        }
        return cloneObj
        
    }else return obj
  }