关于javascript:你以为面试官在问深拷贝的时候仅仅是在问深拷贝吗

2次阅读

共计 3431 个字符,预计需要花费 9 分钟才能阅读完成。

深拷贝能够说是前端面试中十分高频的问题,也是一道根底题。所谓的根底不是说深拷贝自身是一个非常简单、十分根底的问题,而是面试官要通过深拷贝来考查候选人的 JavaScript 根底,甚至是程序设计能力。

为什么须要深拷贝?

第一个问题,也是最通俗的问题,为什么 JavaScript 中须要深拷贝?或者说如果不应用深拷贝复制对象会带来哪些问题?

咱们晓得在 JavaScript 中存在“援用类型“和“值类型“的概念。因为“援用类型“的特殊性,导致咱们复制对象不能通过简略的clone = target, 所以须要把原对象的属性值一一赋给新对象。

而对象的属性其值也可能是另一个对象,所以咱们须要 递归

如何获取原对象的属性?

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

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

如何判断对象的类型?

能够应用 typeof 判断指标是否为援用类型,这里有一处须要留神:typeof null也是object

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 或者正则这样的对象。为什么?

“回字的四样写法“– 具体类型的辨认

获取一个对象具体类型有哪些形式?

罕用的形式有 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 则不须要放心这个问题。

后记

如果下面几个问题都失去了妥善的解决,那么这样的深拷贝就能够说是一个足够感动面试官的深拷贝了。当然这个深拷贝还不够优良,有很多待欠缺的中央,置信长于思考的你曾经有了本人的思路。

但本文的重点并不单单是实现一个深拷贝,更多的是心愿它可能帮忙你更好的了解面试官的思路,从而更好的施展本身的能力。

参考资料

  • lodash
  • Global_Objects

正文完
 0