How to properly clone a JavaScript object?
P粉334721359
P粉334721359 2023-08-23 12:26:59
0
2
527
<p>I have an object <code>x</code>. I want to copy it as an object <code>y</code> so that changes to <code>y</code> do not modify <code>x</code>. I realized that copying an object derived from a built-in JavaScript object would create extra, unwanted properties. This isn't a problem since I'm copying one of my own text-constructed objects. </p> <p>How to properly clone a JavaScript object? </p>
P粉334721359
P粉334721359

reply all(2)
P粉691958181

If you are not using Date, Function, Undefined, regExp or Infinity in the object, a very simple line is JSON.parse(JSON.stringify(object)):

const a = {
  string: 'string',
  number: 123,
  bool: false,
  nul: null,
  date: new Date(),  // stringified
  undef: undefined,  // lost
  inf: Infinity,  // forced to 'null'
}
console.log(a);
console.log(typeof a.date);  // Date object
const clone = JSON.parse(JSON.stringify(a));
console.log(clone);
console.log(typeof clone.date);  // result of .toISOString()

This applies to all types of objects including objects, arrays, strings, booleans and numbers.

See also this article about the Structured Cloning Algorithm Browser , used when publishing messages to or from workers. It also includes deep cloning functionality.

P粉122932466

Updated 2022

There is a new JS standard called Structured Cloning. It works in many browsers (see Can I use it).

const clone = structuredClone(object);

Old Answer

Doing this with any object in JavaScript is never going to be simple or straightforward. You'll run into the problem of incorrectly getting properties from the object's prototype, which should remain in the prototype instead of being copied to the new instance. For example, if you were to add a clone method to Object.prototype (as some answers suggest), you would need to explicitly skip that property. But what if there are other additional methods added in Object.prototype or other intermediate prototypes that you don't know about? In this case, you are copying a property that shouldn't be copied, so you need to use the hasOwnProperty method.

In addition to non-enumerable properties, you also run into a trickier problem when you try to copy an object with hidden properties. For example, prototype is a hidden attribute of a function. Additionally, the object's prototype is referenced through the property __proto__, which is also hidden and is not copied by for/in loops that iterate over the source object's properties. I think __proto__ may be specific to Firefox's JavaScript interpreter and may be different in other browsers, but you get the idea. Not everything is enumerable. If you know the name of the hidden property you can copy it, but I don't know of any way to discover it automatically.

Another obstacle in the search for an elegant solution is the problem of setting up prototypal inheritance correctly. If the source object's prototype is Object, then just create a new generic object using {}, but if the source object's prototype is Object some descendant code>, then you will lose other members in that prototype that were skipped using the hasOwnProperty filter, or other members in the prototype that were not enumerable in the first place. One solution might be to call the source object's constructor property to get the initial copy object and then copy the properties, but you still won't get the non-enumerable properties. For example, the Date code> object stores its data as hidden members:

function clone(obj) {
    if (null == obj || "object" != typeof obj) return obj;
    var copy = obj.constructor();
    for (var attr in obj) {
        if (obj.hasOwnProperty(attr)) copy[attr] = obj[attr];
    }
    return copy;
}

var d1 = new Date();

/* Executes function after 5 seconds. */
setTimeout(function(){
    var d2 = clone(d1);
    alert("d1 = " + d1.toString() + "\nd2 = " + d2.toString());
}, 5000);
The date string for

d1 will be 5 seconds later than d2. The way to make one Date identical to another is to call the setTime method, but this is specific to the Date class. I don't think there's a foolproof universal solution to this problem, although I'm happy to be wrong!

When I had to implement a general deep copy, I finally compromised and assumed that I just needed to copy a normal Object, Array, Date代码>,String代码>, Number代码>, or Boolean代码>. The last 3 types are immutable so I can perform shallow copies without worrying about it changing. I further assume that any element contained in Object or Array will also be one of the 6 simple types in this list. This can be done with code like this:

function clone(obj) {
    var copy;

    // Handle the 3 simple types, and null or undefined
    if (null == obj || "object" != typeof obj) return obj;

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

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

    // Handle Object
    if (obj instanceof Object) {
        copy = {};
        for (var 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.");
}

As long as the data in the object and array form a tree structure, the above function is sufficient for the 6 simple types I mentioned. That is, the same data in the object is referenced no more than once. For example:

// This would be cloneable:
var tree = {
    "left"  : { "left" : null, "right" : null, "data" : 3 },
    "right" : null,
    "data"  : 8
};

// This would kind-of work, but you would get 2 copies of the 
// inner node instead of 2 references to the same copy
var directedAcylicGraph = {
    "left"  : { "left" : null, "right" : null, "data" : 3 },
    "data"  : 8
};
directedAcyclicGraph["right"] = directedAcyclicGraph["left"];

// Cloning this would cause a stack overflow due to infinite recursion:
var cyclicGraph = {
    "left"  : { "left" : null, "right" : null, "data" : 3 },
    "data"  : 8
};
cyclicGraph["right"] = cyclicGraph;

It can't handle any JavaScript objects, but it may be sufficient for many purposes, as long as you don't think it will only work with anything you throw at it.

Latest Downloads
More>
Web Effects
Website Source Code
Website Materials
Front End Template