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

如何正确克隆JavaScript对象?


当前回答

老问题的新答案!如果您有幸将ECMAScript 2016(ES6)与Spread Syntax一起使用,这很简单。

keepMeTheSame = {first: "Me!", second: "You!"};
cloned = {...keepMeTheSame}

这为对象的浅层副本提供了一种干净的方法。创建一个深度副本,意味着为每个递归嵌套对象中的每个值创建一个新副本,这需要使用上面更重的解决方案。

JavaScript不断发展。

其他回答

您可以在不修改父对象的情况下克隆对象-

    /** [Object Extend]*/
    ( typeof Object.extend === 'function' ? undefined : ( Object.extend = function ( destination, source ) {
        for ( var property in source )
            destination[property] = source[property];
        return destination;
    } ) );
    /** [/Object Extend]*/
    /** [Object clone]*/
    ( typeof Object.clone === 'function' ? undefined : ( Object.clone = function ( object ) {
        return this.extend( {}, object );
    } ) );
    /** [/Object clone]*/

    let myObj = {
        a:1, b:2, c:3, d:{
            a:1, b:2, c:3
        }
    };

    let clone = Object.clone( myObj );

    clone.a = 10;

    console.log('clone.a==>', clone.a); //==> 10

    console.log('myObj.a==>', myObj.a); //==> 1 // object not modified here

    let clone2 = Object.clone( clone );

    clone2.a = 20;

    console.log('clone2.a==>', clone2.a); //==> 20

    console.log('clone.a==>', clone.a); //==> 10 // object not modified here

如果对象中没有循环依赖关系,我建议使用其他答案之一或jQuery的复制方法,因为它们看起来都很有效。

如果存在循环依赖关系(即,两个子对象彼此链接),那么你就有点糟糕了,因为(从理论角度)没有办法优雅地解决这个问题。

基于模板克隆对象。如果您不想要精确的副本,但确实想要某种可靠的克隆操作的健壮性,但您只想要克隆位,或者希望确保可以控制每个属性值的存在或格式,该怎么办?

我之所以这么做,是因为它对我们有用,我们之所以创建它,是因为我们找不到类似的东西。您可以使用它来基于模板对象克隆对象,该模板对象指定了要克隆的对象的属性,如果源对象上不存在这些属性,或者您希望如何处理克隆,则模板允许函数将这些属性转换为不同的属性。如果没有用,我相信有人可以删除这个答案。

   function isFunction(functionToCheck) {
       var getType = {};
       return functionToCheck && getType.toString.call(functionToCheck) === '[object Function]';
   }

   function cloneObjectByTemplate(obj, tpl, cloneConstructor) {
       if (typeof cloneConstructor === "undefined") {
           cloneConstructor = false;
       }
       if (obj == null || typeof (obj) != 'object') return obj;

       //if we have an array, work through it's contents and apply the template to each item...
       if (Array.isArray(obj)) {
           var ret = [];
           for (var i = 0; i < obj.length; i++) {
               ret.push(cloneObjectByTemplate(obj[i], tpl, cloneConstructor));
           }
           return ret;
       }

       //otherwise we have an object...
       //var temp:any = {}; // obj.constructor(); // we can't call obj.constructor because typescript defines this, so if we are dealing with a typescript object it might reset values.
       var temp = cloneConstructor ? new obj.constructor() : {};

       for (var key in tpl) {
           //if we are provided with a function to determine the value of this property, call it...
           if (isFunction(tpl[key])) {
               temp[key] = tpl[key](obj); //assign the result of the function call, passing in the value
           } else {
               //if our object has this property...
               if (obj[key] != undefined) {
                   if (Array.isArray(obj[key])) {
                       temp[key] = [];
                       for (var i = 0; i < obj[key].length; i++) {
                           temp[key].push(cloneObjectByTemplate(obj[key][i], tpl[key], cloneConstructor));
                       }
                   } else {
                       temp[key] = cloneObjectByTemplate(obj[key], tpl[key], cloneConstructor);
                   }
               }
           }
       }

       return temp;
   }

一个简单的调用方法如下:

var source = {
       a: "whatever",
       b: {
           x: "yeah",
           y: "haha"
       }
   };
   var template = {
       a: true, //we want to clone "a"
       b: {
           x: true //we want to clone "b.x" too
       }
   }; 
   var destination = cloneObjectByTemplate(source, template);

如果您想使用函数来确保返回属性或确保它是特定类型,请使用这样的模板。我们提供了一个函数,它仍然只是复制源对象的ID属性,但它确保它是一个数字,即使它不存在于源对象上。

 var template = {
    ID: function (srcObj) {
        if(srcObj.ID == undefined){ return -1; }
        return parseInt(srcObj.ID.toString());
    }
}

数组可以很好地进行克隆,但如果您愿意,也可以使用自己的函数来处理这些单独的属性,并执行以下特殊操作:

 var template = {
    tags: function (srcObj) {
        var tags = [];
        if (process.tags != undefined) {
            for (var i = 0; i < process.tags.length; i++) {

                tags.push(cloneObjectByTemplate(
                  srcObj.tags[i],
                  { a : true, b : true } //another template for each item in the array
                );
            }
        }
        return tags;
    }
 }

所以在上面,我们的模板只复制源对象的标签属性(如果它存在的话)(假设它是一个数组),并且对该数组中的每个元素调用clone函数,根据第二个模板单独克隆它,该模板只复制这些标签元素的a和b属性。

如果您要将对象移入和移出节点,并且希望控制这些对象的哪些属性被克隆,那么这是在node.js中控制这些属性的一种很好的方法,代码也可以在浏览器中工作。

下面是它的用法示例:http://jsfiddle.net/hjchyLt1/

根据Apple JavaScript编码指南:

// Create an inner object with a variable x whose default
// value is 3.
function innerObj()
{
        this.x = 3;
}
innerObj.prototype.clone = function() {
    var temp = new innerObj();
    for (myvar in this) {
        // this object does not contain any objects, so
        // use the lightweight copy code.
        temp[myvar] = this[myvar];
    }
    return temp;
}

// Create an outer object with a variable y whose default
// value is 77.
function outerObj()
{
        // The outer object contains an inner object.  Allocate it here.
        this.inner = new innerObj();
        this.y = 77;
}
outerObj.prototype.clone = function() {
    var temp = new outerObj();
    for (myvar in this) {
        if (this[myvar].clone) {
            // This variable contains an object with a
            // clone operator.  Call it to create a copy.
            temp[myvar] = this[myvar].clone();
        } else {
            // This variable contains a scalar value,
            // a string value, or an object with no
            // clone function.  Assign it directly.
            temp[myvar] = this[myvar];
        }
    }
    return temp;
}

// Allocate an outer object and assign non-default values to variables in
// both the outer and inner objects.
outer = new outerObj;
outer.inner.x = 4;
outer.y = 16;

// Clone the outer object (which, in turn, clones the inner object).
newouter = outer.clone();

// Verify that both values were copied.
alert('inner x is '+newouter.inner.x); // prints 4
alert('y is '+newouter.y); // prints 16

史蒂夫

我已经编写了自己的实现。不确定它是否算作更好的解决方案:

/*
    a function for deep cloning objects that contains other nested objects and circular structures.
    objects are stored in a 3D array, according to their length (number of properties) and their depth in the original object.
                                    index (z)
                                         |
                                         |
                                         |
                                         |
                                         |
                                         |                      depth (x)
                                         |_ _ _ _ _ _ _ _ _ _ _ _
                                        /_/_/_/_/_/_/_/_/_/
                                       /_/_/_/_/_/_/_/_/_/
                                      /_/_/_/_/_/_/...../
                                     /................./
                                    /.....            /
                                   /                 /
                                  /------------------
            object length (y)    /
*/

实施情况如下:

function deepClone(obj) {
    var depth = -1;
    var arr = [];
    return clone(obj, arr, depth);
}

/**
 *
 * @param obj source object
 * @param arr 3D array to store the references to objects
 * @param depth depth of the current object relative to the passed 'obj'
 * @returns {*}
 */
function clone(obj, arr, depth){
    if (typeof obj !== "object") {
        return obj;
    }

    var length = Object.keys(obj).length; // native method to get the number of properties in 'obj'

    var result = Object.create(Object.getPrototypeOf(obj)); // inherit the prototype of the original object
    if(result instanceof Array){
        result.length = length;
    }

    depth++; // depth is increased because we entered an object here

    arr[depth] = []; // this is the x-axis, each index here is the depth
    arr[depth][length] = []; // this is the y-axis, each index is the length of the object (aka number of props)
    // start the depth at current and go down, cyclic structures won't form on depths more than the current one
    for(var x = depth; x >= 0; x--){
        // loop only if the array at this depth and length already have elements
        if(arr[x][length]){
            for(var index = 0; index < arr[x][length].length; index++){
                if(obj === arr[x][length][index]){
                    return obj;
                }
            }
        }
    }

    arr[depth][length].push(obj); // store the object in the array at the current depth and length
    for (var prop in obj) {
        if (obj.hasOwnProperty(prop)) result[prop] = clone(obj[prop], arr, depth);
    }

    return result;
}