如何实现深拷贝

867 阅读2分钟

前言

深拷贝这个知识点可以说是面试必问,而且在实际项目开发中用的也挺多,常见的深拷贝实现方式有:借助 Lodash 工具库、JSON.parse(JSON.stringify(obj))。用别人的很香,但为了知其然更知其所以然,何不自己实现一个呢。

JSON.parse(JSON.stringify(obj)) 的弊端

JSON.parse(JSON.stringify(obj))实现深拷贝的过程是利用JSON.stringify()将对象序列化(转换成一个JSON字符串),再使用JSON.parse()进行反序列化(还原)。

如果 obj 里有时间对象,则JSON.parse(JSON.stringify(obj))的结果将只是字符串,而不是时间对象:

const date = new Date();
const newDate = JSON.parse(JSON.stringify(date));
console.log(typeof newDate); // string

如果 obj 里有 RegExp、Error 对象,则序列化的结果将只会得到空对象:

const reg = new RegExp();
const err = new Error();
const newReg = JSON.parse(JSON.stringify(reg));
const newErr = JSON.parse(JSON.stringify(err));
console.log(newReg); // {}
console.log(newErr); // {}

如果 obj 里有函数、undefined,则序列化的结果会将函数、undefined 丢失:

const object = {
    fn: () => {},
    x: undefined,
}
const newObject = JSON.parse(JSON.stringify(object));
console.log(newObject); // {}

如果 obj 里有 NaN、Infinity、-Infinity,则序列化的结果会得到 null:

const object = {
    x: NaN,
    y: Infinity,
    z: -Infinity,
}
const newObject = JSON.parse(JSON.stringify(object));
console.log(newObject); // {x: null, y: null, z: null}

JSON.stringify()只能序列化对象的可枚举的自有属性,如果 obj 中有对象是构造函数生成的,则使用JSON.parse(JSON.stringify(obj))深拷贝后,会丢失对象的 constructor:

function Person(name, age) {
    this.name = name;
    this.age = age;
}
const dlrb = new Person('迪丽热巴', 18);

const object = {
    x: dlrb
}

const newObject = JSON.parse(JSON.stringify(object));
console.log(newObject.x.__proto__.constructor === Person); // fasle

如果 obj 中存在循环引用,会陷入死循环:

const object = {
    x: 1,
}
object.object = object;
const newObject = JSON.parse(JSON.stringify(object)); // error

排除以上情况,可以使用JSON.parse(JSON.stringify(obj))来实现深拷贝。

封装 deepClone 函数

/** 
* origin 给定的需要深拷贝的对象
* hashMap 利用WeakMap记录是否已经拷贝过,避免循环引用导致死循环
*/

function deepClone(origin, hashMap = new WeakMap()) {
    /**
    * 简写小技巧
    * null == undefined -> true
    * null === undefined -> false
    */
    if (origin == null || typeof origin !== 'object') {
        return origin;
    }
    
    if (origin instanceof Date) {
        return new Date(origin);
    }
    
    if (origin instanceof RegExp) {
        return new RegExp(origin);
    }
    
    const hashKey = hashMap.get(origin);
    
    /** 如果已经拷贝过,直接返回 */
    if (hashKey) {
        return hashKey;
    }
    
    /** 通过构造器来生成新对象,就无需考虑是 [] 还是 {} */
    const target = new origin.constructor();
    
    /** 如果没有拷贝过,就记录下来 */
    hashMap.set(origin, target);
    
    for (let k in origin) {
        /** 区分原型属性和自有属性 */
        if (origin.hasOwnProperty(k)) {
            target[k] = deepClone(origin[k], hashMap);
        }
    }
    
    return target;
}

const obj = {
    x: 1,
    y: {
        z: 2,
    }
}

const _obj = deepClone(obj);
_obj.y.z = 3;
console.log(obj); // {x: 1, y: {z: 2}}
console.log(_obj); // {x: 1, y: {z: 3}}

最后

如果文中有错误或者不足之处,欢迎大家在评论区指正。

你的点赞是对我莫大的鼓励!感谢阅读~