我有一个对象x。我想将其复制为对象y,这样对y的更改不会修改x。我意识到复制从内置JavaScript对象派生的对象会导致额外的、不需要的财产。这不是问题,因为我正在复制我自己的一个文字构造对象。

如何正确克隆JavaScript对象?


当前回答

要处理JSON.stringify无法处理的循环对象,可以引入一个名为JSOG的库,该库将任意图形序列化和反序列化为JSON格式。

var clone = JSOG.parse(JSOG.stringify(original));

尝试使用此技巧修补JSOG以进行克隆可能也很有趣(目前没有时间,但如果有人想尝试一下…):

序列化简单函数:

foo.f = function(a) { return a }
var stringForm = foo.f.toString() // "function (a) { return a }"

反序列化函数:

eval("foo.f = " + stringForm)

需要一些约定(可能是以属性的名称)来标识函数与正则字符串(可能是@func_f)。

当然,如果该函数调用第二个函数,则第二个功能将需要与原始函数一样存在。

然而,如果您要接受来自不可信来源的序列化表单,则上述内容非常危险,但接受来自不可靠来源的任何形式的函数都是危险的,因此如果您对克隆函数感兴趣,则必须已经建立了信任(或者您已经打算编写安全漏洞!)。

免责声明:我没有测试JSOG字符串/解析与JSON字符串/解析的速度,但它确实适用于我测试的简单(循环)对象。

其他回答

我认为,在没有库的情况下,缓存的重复性是最好的。

被低估的WeakMap涉及到循环的问题,其中存储对新旧对象的引用可以帮助我们很容易地重建整个树。

我阻止了DOM元素的深度克隆,可能您不想克隆整个页面:)

function deepCopy(object) {
    const cache = new WeakMap(); // Map of old - new references

    function copy(obj) {
        if (typeof obj !== 'object' ||
            obj === null ||
            obj instanceof HTMLElement
        )
            return obj; // primitive value or HTMLElement

        if (obj instanceof Date) 
            return new Date().setTime(obj.getTime());

        if (obj instanceof RegExp) 
            return new RegExp(obj.source, obj.flags);

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

        const result = obj instanceof Array ? [] : {};

        cache.set(obj, result); // store reference to object before the recursive starts

        if (obj instanceof Array) {
            for(const o of obj) {
                 result.push(copy(o));
            }
            return result;
        }

        const keys = Object.keys(obj); 

        for (const key of keys)
            result[key] = copy(obj[key]);

        return result;
    }

    return copy(object);
}

一些测试:

// #1
const obj1 = { };
const obj2 = { };
obj1.obj2 = obj2;
obj2.obj1 = obj1; // Trivial circular reference

var copy = deepCopy(obj1);
copy == obj1 // false
copy.obj2 === obj1.obj2 // false
copy.obj2.obj1.obj2 // and so on - no error (correctly cloned).

// #2
const obj = { x: 0 }
const clone = deepCopy({ a: obj, b: obj });
clone.a == clone.b // true

// #3
const arr = [];
arr[0] = arr; // A little bit weird but who cares
clone = deepCopy(arr)
clone == arr // false;
clone[0][0][0][0] == clone // true;

注意:我使用常量、for of循环、=>运算符和WeakMaps来创建更重要的代码。当前的浏览器支持此语法(ES6)

我不知道这在哪些情况下不起作用,但它给了我一个数组的副本。我觉得它很可爱:)希望它有帮助

copiedArr = origArr.filter(function(x){return true})

如果您正在使用TypeScript,需要支持较旧的web浏览器(因此无法使用Object.assign),并且没有使用内置有克隆方法的库,那么您可以在几行代码中使自己成为组合助手。它结合了对象,如果只有一个,就克隆它。

/** Creates a new object that combines the properties of the specified objects. */
function combine(...objs: {}[]) {
    const combined = {};
    objs.forEach(o => Object.keys(o).forEach(p => combined[p] = o[p]));
    return combined;
}

根据MDN:

如果要浅层复制,请使用Object.assign({},a)对于“深度”复制,请使用JSON.parse(JSON.stringify(a))

不需要外部库,但您需要首先检查浏览器兼容性。

const objClone = { ...obj };

请注意,嵌套对象仍作为引用复制。