你觉得面试官在问深拷贝的时候,仅仅是在问深拷贝吗?

deep.001.jpeg

深拷贝能够说是前端面试中很是高频的问题,也是一道基础题。所谓的基础不是说深拷贝自己是一个很是简单、很是基础的问题,而是面试官要经过深拷贝来考察候选人的JavaScript基础,甚至是程序设计能力。前端

为何须要深拷贝?

第一个问题,也是最浅显的问题,为何 JavaScript 中须要深拷贝?或者说若是不使用深拷贝复制对象会带来哪些问题?面试

咱们知道在 JavaScript 中存在“引用类型“和“值类型“的概念。由于“引用类型“的特殊性,致使咱们复制对象不能经过简单的clone = target,因此须要把原对象的属性值一一赋给新对象。数组

而对象的属性其值也多是另外一个对象,因此咱们须要递归spa

如何获取原对象的属性?

经过for...in可以遍历对象上的属性;也能够经过Object.keys(target)获取到对象上的属性数组后再进行遍历。
这里选用for...in由于相比Object.keys(target)它还会遍历对象原型链上的属性。prototype

ES6 Symbol 类型也能够做为对象的 key ,如何获取它们?设计

如何判断对象的类型?

可使用typeof判断目标是否为引用类型,这里有一处须要注意:typeof null也是objectcode

function deepClone(target) {
    const targetType = typeof target;
    if (targetType === 'object' || targetType === 'function') {
        let clone = Array.isArray(target)?[]:{}
        for (const key in target) {
            clone[key] = deepClone(target[key])
        }
        return clone;
    }
    return target;
}

上述代码就完成了一个很是基础的深拷贝。可是对于引用类型的处理,它仍然是不完善的:对象

它无法处理Date或者正则这样的对象。为何?blog

“回字的四样写法“--具体类型的识别

获取一个对象具体类型有哪些方式?教程

经常使用的方式有target.constructor.nameObject.prototype.toString.call(target)instanceOf

  • instacneOf能够用来判断对象类型,可是Date的实例同时也是Object的实例,此处用于判断是不许确的;
  • target.constructor.name获得的是构造器名称,而构造器是能够被修改的;
  • Object.prototype.toString.call(target)返回的是类名,而在ES5中只有内置类型对象才有类名。

因此此处咱们最合适的选择是Object.prototype.toString.call(target)

Object.prototype.toString.call(target)也存在一些问题,你知道吗?

稍微改进一下代码,作一些简单的类型判断:

function deepClone(target) {
    const targetType = typeof target;
    if (targetType === 'object' || targetType === 'function') {
        let clone = Array.isArray(target)?[]:{};

        if(Object.prototype.toString.call(target) === '[object Date]'){
            clone = new Date(target)
        }
        
        if(Object.prototype.toString.call(target) === '[object Object]'
        ||Object.prototype.toString.call(target) === '[object Array]'){
            for (const key in target) {
                clone[key] = deepClone(target[key])
            }
        }

        return clone;
    }
    return target;
}

怎么可以更优雅的作类型判断?

你据说过“循环引用“吗?

假如目标对象的属性间接或直接的引用了自身,就会造成循环引用,致使在递归的时候爆栈。
因此咱们的代码须要循环检测,设置一个Map用于存储已拷贝过的对象,当检测到对象已存在于Map中时,取出该值并返回便可避免爆栈。

function deepClone(target, map = new Map()) {
    const targetType = typeof target;
    if (targetType === 'object' || targetType === 'function') {
        let clone = Array.isArray(target)?[]:{};
        if (map.get(target)) {
            return map.get(target);
        }
        
        map.set(target, clone);

        if(Object.prototype.toString.call(target) === '[object Date]'){
            clone = new Date(target)
        }
        
        if(Object.prototype.toString.call(target) === '[object Object]'
            ||Object.prototype.toString.call(target) === '[object Array]'){
            for (const key in target) {
                clone[key] = deepClone(target[key],map)
            }
        }

        return clone;
    }
    return target;
}

好多教程使用 WeakMap 作存储,相比Map,WeakMap好在哪儿?

通往优秀的阶梯

以上咱们就完成了一个基础的深拷贝。可是它仅仅是及格而已,想要作到优秀,还要处理一下以前留下的几个问题。

获取Symbol属性

ES6Symbol类型也能够做为对象的 key ,可是for...inObject.keys(target)都拿不到 Symbol类型的属性名。

好在咱们能够经过Object.getOwnPropertySymbols(target) 获取对象上全部的Symbol属性,再结合for...inObject.keys()就可以拿到所有的 key。不过这种方式有些麻烦,有没有更好用的方法?

有!Reflect.ownKeys(target) 正是这样一个集优雅与强大与一身的方法。可是正如同人无完人,这个方法也不完美:顾名思义,ownKeys是拿不到原型链上的属性的。因此须要结合具体场景来组合使用上述方法。

特殊的内置类型

DateError等特殊的内置类型虽然是对象,可是并不能遍历属性,因此针对这些类型须要从新调用对应的构造器进行初始化。JavaScript 内置了许多相似的特殊类型,然而咱们并非无情的 API 机器,面试中可以回答上述要点也就足够了。

上述内置类型咱们均可以经过Object.prototype.toString.call(target) 的方式拿到,因此这里能够封装一个类型判断的方法用于判断target 是否可以继续遍历,以便于及后续的处理。

然而 ES6 新增了Symbol.toStringTag方法,能够用来自定义类名,这就致使 Object.prototype.toString.call(target)拿到的类型名也可能不够准确:

class ValidatorClass {
  get [Symbol.toStringTag]() {
    return "Validator";
  }
}

Object.prototype.toString.call(new ValidatorClass()); 
// "[object Validator]"

使用WeakMap作循环检测,比使用Map好在哪儿?

原生的WeakMap持有的是每一个键对象的“弱引用”,这意味着在没有其余引用存在时垃圾回收能正确进行。若是 target 很是庞大,那么使用Map 后若是没有进行手动释放,这块内存就会持续的被占用。而WeakMap则不须要担忧这个问题。

后记

若是上面几个问题都获得了妥善的处理,那么这样的深拷贝就能够说是一个足够打动面试官的深拷贝了。固然这个深拷贝还不够优秀,有不少待完善的地方,相信善于思考的你已经有了本身的思路。

但本文的重点并不仅仅是实现一个深拷贝,更多的是但愿它可以帮助你更好的理解面试官的思路,从而更好的发挥自身的能力。

参考资料

关注「JS漫步指南」公众号,获取更多面试秘籍!

相关文章
相关标签/搜索