如何正确克隆 JavaScript 对象

如何正确克隆 JavaScript 对象

技术背景

在 JavaScript 中,对象是引用类型。当我们将一个对象赋值给另一个变量时,实际上是复制了对象的引用,而不是对象本身。这意味着对新变量的修改会影响原始对象。在某些情况下,我们需要创建一个对象的副本,使得对副本的修改不会影响原始对象,这就需要进行对象克隆。

实现步骤

浅拷贝

浅拷贝只复制对象的一层属性,如果对象的属性是引用类型,则只复制引用,而不是对象本身。

1. 使用 Object.assign()
const original = { a: 1, b: { c: 2 } };
const shallowClone = Object.assign({}, original);
2. 使用扩展运算符(Spread Operator)
const original = { a: 1, b: { c: 2 } };
const shallowClone = { ...original };

深拷贝

深拷贝会递归地复制对象的所有属性,包括嵌套对象,从而创建一个完全独立的对象副本。

1. 使用 JSON.parse(JSON.stringify())
const original = { a: 1, b: { c: 2 } };
const deepClone = JSON.parse(JSON.stringify(original));

注意:该方法有局限性,它会忽略 undefinedfunctionSymbol 等属性,并且不能处理循环引用的对象。

2. 自定义递归函数
function clone(obj) {
    if (null == obj || "object" != typeof obj) return obj;

    if (obj instanceof Date) {
        const copy = new Date();
        copy.setTime(obj.getTime());
        return copy;
    }

    if (obj instanceof Array) {
        const copy = [];
        for (let i = 0, len = obj.length; i < len; i++) {
            copy[i] = clone(obj[i]);
        }
        return copy;
    }

    if (obj instanceof Object) {
        const copy = {};
        for (const attr in obj) {
            if (obj.hasOwnProperty(attr)) copy[attr] = clone(obj[attr]);
        }
        return copy;
    }

    throw new Error("Unable to copy obj! Its type isn't supported.");
}

const original = { a: 1, b: { c: 2 } };
const deepClone = clone(original);
3. 使用 structuredClone()
const original = { a: 1, b: { c: 2 } };
const deepClone = structuredClone(original);

注意structuredClone() 是一个新的 JavaScript 标准,浏览器兼容性需要检查。

使用第三方库

1. jQuery
const original = { a: 1, b: { c: 2 } };
// 浅拷贝
const shallowClone = jQuery.extend({}, original);
// 深拷贝
const deepClone = jQuery.extend(true, {}, original);
2. Lodash
const _ = require('lodash');
const original = { a: 1, b: { c: 2 } };
// 浅拷贝
const shallowClone = _.clone(original);
// 深拷贝
const deepClone = _.cloneDeep(original);

最佳实践

  • 浅拷贝:如果对象只有一层属性或者不需要对嵌套对象进行独立修改,使用 Object.assign() 或扩展运算符进行浅拷贝,它们简洁高效。
  • 深拷贝
    • 如果对象不包含 undefinedfunctionSymbol 等特殊属性,且没有循环引用,可以使用 JSON.parse(JSON.stringify())
    • 如果需要处理复杂对象,包括日期、数组等,自定义递归函数是一个不错的选择。
    • 如果浏览器支持,structuredClone() 是最简单的深拷贝方法。
    • 如果项目中已经使用了 jQuery 或 Lodash 等库,使用它们提供的克隆方法更方便。

常见问题

循环引用问题

使用 JSON.parse(JSON.stringify()) 和简单的递归函数会遇到循环引用问题,导致错误。可以使用带有缓存的递归函数来解决,例如:

function cloneWithCache(obj) {
    const cache = new WeakMap();

    function copy(object) {
        if (typeof object !== 'object' || object === null) return object;

        if (cache.has(object)) return cache.get(object);

        let result;
        if (object instanceof Date) {
            result = new Date(object.getTime());
        } else if (object instanceof Array) {
            result = [];
            for (const item of object) {
                result.push(copy(item));
            }
        } else {
            result = {};
            for (const key in object) {
                if (object.hasOwnProperty(key)) {
                    result[key] = copy(object[key]);
                }
            }
        }

        cache.set(object, result);
        return result;
    }

    return copy(obj);
}

const circularObj = {};
circularObj.self = circularObj;
const clonedCircularObj = cloneWithCache(circularObj);

性能问题

JSON.parse(JSON.stringify()) 涉及字符串的序列化和反序列化,性能较低,不适合处理大型对象。在性能敏感的场景下,建议使用自定义递归函数或第三方库的克隆方法。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

打赏作者

1010n111

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

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

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

打赏作者

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

抵扣说明:

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

余额充值