Javascript深拷贝常见实现方法

深拷贝与浅拷贝

在JavaScript中,深拷贝是一个常见的需求,特别是在处理复杂数据结构(如对象、数组等)时,需要确保原始数据不被修改。下面通过表格形式列出几种常见的深拷贝方法,并简要说明其优缺点。

方法优点缺点
JSON 方法1. 简单快捷,一行代码即可实现。1. 无法处理函数、undefinedSymbolRegExp等特殊对象。
 2. 适用于大多数基本数据类型和简单对象结构。2. 可能会丢失对象的原型链。
  3. 递归对象(对象中包含自身引用)会导致无限循环。
手动递归1. 可以精确控制拷贝过程,包括如何处理特殊对象。1. 实现复杂,需要手动处理各种数据类型和特殊情况。
 2. 可以保留对象的原型链(如果需要)。2. 性能可能不如其他自动化方法,特别是在处理大型对象时。
  • JSON 方法(如JSON.parse(JSON.stringify(obj)))虽然简单,但因其局限性,并不适用于所有场景。
  • 手动递归方法虽然灵活,但实现起来较为复杂,特别是在处理复杂数据结构时。

递归实现深拷贝

bd3024d1502d4d8395e2a50ac20e0c4a.png

当然,下面是一个利用递归实现的JavaScript简单深拷贝函数的示例。这个函数将处理对象、数组以及它们的嵌套结构,但为了简化,我们不会处理像DateRegExpMapSet等特殊对象类型,也不会处理函数对象(因为函数在JavaScript中是按引用传递的,且深拷贝函数对象通常不是必要的或期望的)。

function deepClone(obj) {  
    // 对于非对象或null,直接返回  
    if (obj === null || typeof obj !== 'object') {  
        return obj;  
    }  
  
    // 初始化拷贝目标  
    let cloneObj;  
  
    // 处理数组  
    if (Array.isArray(obj)) {  
        cloneObj = [];  
        obj.forEach(item => {  
            cloneObj.push(deepClone(item)); // 递归拷贝数组中的每一项  
        });  
    }  
    // 处理对象  
    else {  
        cloneObj = {};  
        Object.keys(obj).forEach(key => {  
            cloneObj[key] = deepClone(obj[key]); // 递归拷贝对象的每一个属性值  
        });  
    }  
  
    return cloneObj;  
}  
  
// 示例使用  
const original = {  
    a: 1,  
    b: { c: 2, d: [3, 4] },  
    e: [5, 6, { f: 7 }]  
};  
  
const cloned = deepClone(original);  
console.log(cloned); // 输出深拷贝后的对象  
console.log(cloned.b.d === original.b.d); // false,说明数组也是深拷贝的  
console.log(cloned.e[2].f === original.e[2].f); // false,说明嵌套对象也是深拷贝的

在JavaScript中,实现一个利用递归的深拷贝函数可以处理大多数数据类型,包括对象、数组、以及它们的嵌套结构。下面是一个简单的实现示例:

function deepClone(obj, hash = new WeakMap()) {  
    // 对于非对象或null,直接返回  
    if (obj === null || typeof obj !== 'object') {  
        return obj;  
    }  
  
    // 如果对象已经存在于哈希表中,则直接返回其引用  
    if (hash.has(obj)) {  
        return hash.get(obj);  
    }  
  
    let cloneObj;  
  
    // 处理数组  
    if (Array.isArray(obj)) {  
        cloneObj = [];  
        hash.set(obj, cloneObj);  
        obj.forEach(item => {  
            cloneObj.push(deepClone(item, hash));  
        });  
    }  
    // 处理Date对象  
    else if (obj instanceof Date) {  
        cloneObj = new Date(obj);  
    }  
    // 处理RegExp对象  
    else if (obj instanceof RegExp) {  
        // 注意:RegExp的flags属性在ES6中引入  
        const flags = obj.flags ? `${obj.source}/${obj.flags}` : `${obj.source}/${(obj.global ? 'g' : '') + (obj.ignoreCase ? 'i' : '') + (obj.multiline ? 'm' : '') + (obj.unicode ? 'u' : '') + (obj.sticky ? 'y' : '')}`;  
        cloneObj = new RegExp(flags, obj.flags);  
    }  
    // 处理普通对象  
    else {  
        cloneObj = {};  
        hash.set(obj, cloneObj);  
        Object.keys(obj).forEach(key => {  
            cloneObj[key] = deepClone(obj[key], hash);  
        });  
  
        // 如果需要保持原型链,可以添加以下代码  
        // Object.setPrototypeOf(cloneObj, Object.getPrototypeOf(obj));  
    }  
  
    return cloneObj;  
}  
  
// 示例使用  
const original = {  
    a: 1,  
    b: { c: 2, d: [3, 4] },  
    c: new Date(),  
    d: /abc/gi,  
    e: function() { console.log('hello'); },  
    f: {  
        g: original // 循环引用  
    }  
};  
  
const cloned = deepClone(original);  
console.log(cloned);  
console.log(cloned.f.g === cloned); // true,因为处理了循环引用

注意:

  1. 这个函数使用了WeakMap来存储已经拷贝过的对象,以避免循环引用导致的无限递归。
  2. 对于特殊对象如DateRegExp,我们分别进行了特殊处理。
  3. 函数默认不保持原型的继承关系,如果需要,可以取消注释Object.setPrototypeOf相关的代码行。
  4. 函数能够处理函数对象,但请注意,函数对象在JavaScript中是按引用传递的,即使进行了深拷贝,函数本身还是同一个函数,只是函数对象作为属性被复制到了新的对象中。
  5. 对于更复杂的对象(如MapSetBlobFile等),你可能需要添加额外的逻辑来支持它们的深拷贝。

 

  • 4
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值