面试官:怎样实现一个深拷贝

249 阅读1分钟

对数据的深浅拷贝原生JS提供了很多的方法:

  • 浅拷贝:obj.slice(), Object.assign(obj), obj = [...obj1] 等都能实现
  • 深拷贝:最常用的是JSON.parse(JSON.stringify(obj))

    这种深拷贝是不彻底的, 数据中含有下列类型的内容时:

    1. Symbol/undefined/function 直接不显示;
    2. bigint 直接报错;
    3. 正则 转变成一个空对象;
    4. Date对象 转换成字符串后就转换不回来了.

下面是深拷贝的实现思路及代码:

/* 深拷贝其实就是递归调用浅拷贝, 所以先来实现一个浅拷贝:
 * 思路: 通过传入值(obj)的构造器, new一个跟obj相同类型的变量(newObj), 
 * 遍历传入值的key值(keyList), 然后把obj的每项值映射到newObj上。 
*/
~function(){
  function getType(obj){
    return Object.prototype.toString.call(obj)
  }
  function shallowClone(obj){
    // 不知道传进来的是什么类型的数据, 通过new一个constructor得到相同类型的变量; 
    // new实例没参数可以不用写()
    let newObj = new obj.constructor,
        keyList = Object.keys(obj),
        type = getType(obj);
    // 处理date/正则类型, 直接用构造器new一下就是个新值
    if(/^(date|regexp)$/i.test(type)){return new newObj(obj)}
    // 处理bigint/Symbol类型, 这两种类型是不能new的
    if(/^(bigint|symbol)$/i.test(type)){return Object(obj)}
    // 处理错误对象: 用构造器new错误对象的message值
    if(/^error$/i.test(type)){return new newObj(obj.message)}
    // 处理函数
    if(/^function$/i.test(type)){
      // 返回一个函数, 构建一个新的作用域
      return function(){
        // this指向调用这个函数的上下文
        return obj.call(this, ...arguments)
      }
    }
    if(/^(object|array)$/i.test(type)){
      keyList.map(key => newObj[key] = obj[key])
      return newObj
    }
    // 基本类型直接返回即可
    return obj
  }
  function deepClone(obj, cache = new Set()){
    let newObj = new obj.constructor,
        keyList = Object.keys(obj),
        type = getType(obj);
    // 非数组或对象类型直接调用浅拷贝方法
    if(!/^(object|array)$/i.test(type)){return shallowClone(obj)}
    /* 
    * 为避免出现对象的无线套娃情况, 造成无限递归
    * 使用new Set()的add(), 和has方法 做个缓存
    * 因对象的key值不能为对象, 所以这里不好用对象的key是唯一值的机制做
    */
    if(cache.has(obj))return obj
    cache.add(obj)
    // 判断下级还是数组和对象, 就继续浅拷贝, 即递归调用
    keyList.map(key => newObj[key] = deepClone(obj[key], cache))
    return newObj
  }
}()