【前端面经】JS-深浅拷贝

理解深浅拷贝

深浅拷贝问题的出现是由于JavaScript对不同类型的存储方式而引发的。
对于原始数据类型,它们的值是直接存储在栈内存中;
而复杂数据类型,则在栈内存中记录它的指针,而指针指向堆内存中真正的值。
所以对于原始数据类型,就没有深浅拷贝一说;而对于复杂数据类型,浅拷贝就是仅复制指针,但被复制对象改变时,新复制的对象也会跟着改变;深拷贝则是连同堆内存中的数据完全拷贝一份,新旧对象的变化互不影响。

深浅拷贝的定义

浅拷贝:

拷贝复杂类型时,仅拷贝对象的指针;当原对象改变时,拷贝对象也会跟着改变。

深拷贝:

拷贝复制类型时,拷贝对象的值;当原对象改变时,拷贝对象不会跟着改变。

深浅拷贝的实现方案

浅拷贝方案:

  1. 展开运算符(ES6)
  2. Object.assign()
  3. concat

深拷贝:

  1. 手写递归
  2. JSON.parse(JSON.stringfy())
    • 先转换成字符串,再解析回对象
    • 缺点是不能处理正则表达式和函数等特殊数据类型
  3. 一些库封装的深拷贝方法,例如Lodash的_.cloneDeep()

手写实现

// 先写一个浅拷贝
function _deepClone(target) {
    let cloneRes = {};
    for(const key in target) {
        cloneRes[key] = target[key]
    }
    return cloneRes
    // 函数出口
    // if (typeof target !== 'obejct') return target
}

// 2. 普通深拷贝(只考虑对象/非对象)
function _deepClone(target) {
    if (typeof target === 'object'){
        // 如果是对象,进行处理
        let cloneRes = {};
        for(const key in target) {
            cloneRes[key] = _deepClone(target[key])
        }
        return cloneRes
    } else {
        // 如果不是对象,直接返回
        return target
    }
}

// 3.考虑数组的深拷贝
function _deepClone(target) {
    if (typeof target === 'object') {
        let cloneRes = Array.isArray(target) ? [] : {}; // 核心
        for (const key in target) {
            cloneRes[key] = _deepClone(target[key])
        }
        return cloneRes
    } else {
        return target
    }
}

// 4. 考虑循环引用
// Q:为什么要用WeakMap替代Map?
// A:强引用和弱引用的区别,强引用只能手动释放,弱引用能够被垃圾回收机制释放,使用Map会造成内存额外消耗。
//    WeakMap中的键是弱引用,Map中的键是强引用
function _deepClone(target, map=new WeakMap()) {
    if (typeof target === 'object') {
        let cloneRes = Array.isArray(target) ? [] : {}; // 核心
        // 防止循环调用导致的栈内存溢出
        if (map.get(target)) {
            return map.get(target)
        }
        map.set(target, cloneRes)
        for (const key in target) {
            cloneRes[key] = _deepClone(target[key], map)
        }
        return cloneRes
    } else {
        return target
    }
}

// 5. 考虑其他数据类型
function _deepClone (target, map = new WeakMap()) {
    if (target === null) return null
    if (target instanceof Date) return new Date(target)
    if (target instanceof RegExp) return new RegExp(target);
    if (typeof target !== 'object') return target
    if (map.get(target)) return map.get(target);

    let cloneRes = new target.constructor()
    map.set(target, cloneRes)
    for (const key in target) {
        if (target.hasOwnProperty(key)) {
            cloneRes[key] = _deepClone(target[key], map);
        }
    }
    return cloneRes
}
// 6. 性能优化
// 把for..in改为普通的for循环,性能会有所提高

// 测试案例
const target = {
    field1: 1,
    field2: undefined,
    field3: {
        child: 'child'
    },
    field4: [2, 4, 8]
};
const newTarget = _deepClone(target)
newTarget.field4[1] = "Child2"
console.log(target);
console.log(newTarget);
console.log(target === newTarget);
// 输出:
// {
//   field1: 1,
//   field2: undefined,
//   field3: { child: 'child' },
//   field4: [ 2, 4, 8 ]
// }
// {
//   field1: 1,
//   field2: undefined,
//   field3: { child: 'child' },
//   field4: [ 2, 'Child2', 8 ]
// }
// false

参考链接&&相关文章

前端早读课【第2810期】JavaScript 深拷贝性能分析

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

深海大凤梨_

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值