一文教你手撕深浅拷贝

84 阅读2分钟

前言

深浅拷贝是在面试中经常出现的考题,今天让让我们深入理解深浅拷贝以及它们的区别。

深拷贝 VS 浅拷贝

浅拷贝是什么?
创建一个新对象,这个对象有着原始对象属性值的一份精准拷贝。如果属性是基本类型,拷贝的就是基本类型的值。如果属性是引用类型,那么拷贝的就是内存地址。所以如果其中一个对象改变了这个地址,就会影响到另一个对象。

深拷贝是什么?
创建一个新对象,把原对象从内存里完整的拷贝一份出来,从堆内存里开辟一个新的区域来存放新对象,并且修改新对象的属性值不会影响原对象。

浅拷贝

image.png 手写浅拷贝

function shallowCopy(obj) {
    if (typeof obj !== 'object') return obj
    let newObj = Array.isArray(obj) ? [] : {}
    for (let key in obj) {
        if (obj.hasOwnProperty(key)) {
            newObj[key] = obj[key]
        }
    }
    return newObj
}

深拷贝

入门方法:JSON.stringify() 利用JSON.stringfy()把JS对象或值转换为JSON字符串,最后利用JSON.parse()方法把JSON字符串生成一个新对象。该方法虽然简单,但是还是存在些问题。

  1. 执行会报错:存在BigInt类型、循环引用
  2. 拷贝Date引用类型会变成字符串
  3. 键值会消失:对象的值中为function、undefined、Symbol这几种类型
  4. 键值会变成空对象:对象的值中为Map、Set、RegExp这几种类型
  5. 无法拷贝:不可枚举属性,对象的原型链 由于以上问题,我们如果要拷贝很复杂的数据类型只能换种方法了。接下来让我们手动实现深拷贝吧。

手写深拷贝基础版

function deepCopy(obj) {
    if (typeof obj !== 'object' || obj === null) return obj
    let newObj = Array.isArray(obj) ? [] : {}
    for (let key in obj) {
        if (obj.hasOwnProperty(key)) {
            if (typeof obj[key] === 'object') {
                newObj[key] = deepCopy(obj[key])
            } else {
                newObj[key] = obj[key]
            }
        }
    }
    return newObj
}

手写深拷贝进阶版

function deepCopyBest(obj, hash = new WeakMap()) {
    if (obj === null || typeof obj !== 'object') {
        return obj;
    }

    // 处理循环引用:如果对象已经拷贝过,则直接返回其拷贝副本
    if (hash.has(obj)) {
        return hash.get(obj);
    }

    // 处理 Date 对象
    if (obj instanceof Date) {
        return new Date(obj);
    }

    // 处理 RegExp 对象
    if (obj instanceof RegExp) {
        return new RegExp(obj.source, obj.flags);
    }

    // 根据原始对象类型(数组或对象)创建新的容器
    const newObj = Array.isArray(obj) ? [] : {};

    // 将新创建的拷贝对象存入 hash 表,以便后续处理循环引用
    hash.set(obj, newObj);

    // 拷贝普通属性 (own properties)
    for (let key in obj) {
        if (obj.hasOwnProperty(key)) {
            newObj[key] = deepCopy(obj[key], hash);
        }
    }

    // 拷贝 Symbol 属性 (own properties)
    const symbols = Object.getOwnPropertySymbols(obj);
    for (let symbol of symbols) {
        // Symbol 属性的描述符也需要考虑,这里简单处理直接赋值
        // 如果需要精确拷贝属性描述符,可以使用 Object.defineProperty
        newObj[symbol] = deepCopy(obj[symbol], hash);
    }

    return newObj;
}