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

如何正确克隆JavaScript对象?


当前回答

咨询http://www.w3.org/html/wg/drafts/html/master/infrastructure.html#safe-为W3C的“安全传递结构化数据”算法传递结构化数据,该算法旨在由浏览器实现,用于将数据传递给例如web工作者。然而,它有一些限制,因为它不处理函数。看见https://developer.mozilla.org/en-US/docs/DOM/The_structured_clone_algorithm获取更多信息,包括JS中的另一种算法,它可以帮助您实现这一目标。

其他回答

根据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

史蒂夫

互联网上的大多数解决方案都存在几个问题。所以我决定做一个跟进,包括为什么不应该接受接受的答案。

启动情况

我想深度复制一个Javascript对象及其所有子对象和它们的子对象等等。但由于我不是一个普通的开发人员,我的对象具有普通的财产、循环结构甚至嵌套对象。

因此,让我们先创建一个圆形结构和一个嵌套对象。

function Circ() {
    this.me = this;
}

function Nested(y) {
    this.y = y;
}

让我们把所有的东西放在一个名为a的对象中。

var a = {
    x: 'a',
    circ: new Circ(),
    nested: new Nested('a')
};

接下来,我们要将a复制到名为b的变量中,并对其进行变异。

var b = a;

b.x = 'b';
b.nested.y = 'b';

你知道这里发生了什么,因为如果不是这样,你甚至不会回答这个伟大的问题。

console.log(a, b);

a --> Object {
    x: "b",
    circ: Circ {
        me: Circ { ... }
    },
    nested: Nested {
        y: "b"
    }
}

b --> Object {
    x: "b",
    circ: Circ {
        me: Circ { ... }
    },
    nested: Nested {
        y: "b"
    }
}

现在让我们找到一个解决方案。

JSON

我第一次尝试是使用JSON。

var b = JSON.parse( JSON.stringify( a ) );

b.x = 'b';
b.nested.y = 'b';

不要在上面浪费太多时间,你会得到TypeError:将循环结构转换为JSON。

递归副本(接受的“答案”)

让我们来看看公认的答案。

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

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

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

    // Handle Object
    if (obj instanceof Object) {
        var copy = {};
        for (var attr in obj) {
            if (obj.hasOwnProperty(attr)) copy[attr] = cloneSO(obj[attr]);
        }
        return copy;
    }

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

看起来不错,呵呵?它是对象的递归副本,也处理其他类型,如Date,但这不是必需的。

var b = cloneSO(a);

b.x = 'b';
b.nested.y = 'b';

递归和循环结构不能很好地一起工作。。。RangeError:超出了最大调用堆栈大小

本地解决方案

在与同事争吵后,老板问我们发生了什么事,他在谷歌搜索后找到了一个简单的解决方案。它叫做Object.create。

var b = Object.create(a);

b.x = 'b';
b.nested.y = 'b';

这个解决方案不久前被添加到Javascript中,甚至可以处理循环结构。

console.log(a, b);

a --> Object {
    x: "a",
    circ: Circ {
        me: Circ { ... }
    },
    nested: Nested {
        y: "b"
    }
}

b --> Object {
    x: "b",
    circ: Circ {
        me: Circ { ... }
    },
    nested: Nested {
        y: "b"
    }
}

…你看,它不适用于内部的嵌套结构。

本地解决方案的polyfill

在旧浏览器中有一个用于Object.create的polyfill,就像IE 8一样。它有点像Mozilla推荐的,当然,它不是完美的,并且会导致与本机解决方案相同的问题。

function F() {};
function clonePF(o) {
    F.prototype = o;
    return new F();
}

var b = clonePF(a);

b.x = 'b';
b.nested.y = 'b';

我把F放在了范围之外,这样我们可以看看instanceof告诉我们什么。

console.log(a, b);

a --> Object {
    x: "a",
    circ: Circ {
        me: Circ { ... }
    },
    nested: Nested {
        y: "b"
    }
}

b --> F {
    x: "b",
    circ: Circ {
        me: Circ { ... }
    },
    nested: Nested {
        y: "b"
    }
}

console.log(typeof a, typeof b);

a --> object
b --> object

console.log(a instanceof Object, b instanceof Object);

a --> true
b --> true

console.log(a instanceof F, b instanceof F);

a --> false
b --> true

问题与本机解决方案相同,但输出稍差。

更好(但不是完美)的解决方案

当我仔细研究时,我发现了一个与这个问题类似的问题(在Javascript中,当执行深度复制时,我如何避免循环,因为属性是“this”?),但有一个更好的解决方案。

function cloneDR(o) {
    const gdcc = "__getDeepCircularCopy__";
    if (o !== Object(o)) {
        return o; // primitive value
    }

    var set = gdcc in o,
        cache = o[gdcc],
        result;
    if (set && typeof cache == "function") {
        return cache();
    }
    // else
    o[gdcc] = function() { return result; }; // overwrite
    if (o instanceof Array) {
        result = [];
        for (var i=0; i<o.length; i++) {
            result[i] = cloneDR(o[i]);
        }
    } else {
        result = {};
        for (var prop in o)
            if (prop != gdcc)
                result[prop] = cloneDR(o[prop]);
            else if (set)
                result[prop] = cloneDR(cache);
    }
    if (set) {
        o[gdcc] = cache; // reset
    } else {
        delete o[gdcc]; // unset again
    }
    return result;
}

var b = cloneDR(a);

b.x = 'b';
b.nested.y = 'b';

让我们看看输出。。。

console.log(a, b);

a --> Object {
    x: "a",
    circ: Object {
        me: Object { ... }
    },
    nested: Object {
        y: "a"
    }
}

b --> Object {
    x: "b",
    circ: Object {
        me: Object { ... }
    },
    nested: Object {
        y: "b"
    }
}

console.log(typeof a, typeof b);

a --> object
b --> object

console.log(a instanceof Object, b instanceof Object);

a --> true
b --> true

console.log(a instanceof F, b instanceof F);

a --> false
b --> false

需求是匹配的,但仍有一些较小的问题,包括将nested和circ的实例更改为Object。

共享一片叶子的树的结构不会被复制,它们将变成两片独立的叶子:

        [Object]                     [Object]
         /    \                       /    \
        /      \                     /      \
      |/_      _\|                 |/_      _\|  
  [Object]    [Object]   ===>  [Object]    [Object]
       \        /                 |           |
        \      /                  |           |
        _\|  |/_                 \|/         \|/
        [Object]               [Object]    [Object]

结论

最后一个使用递归和缓存的解决方案可能不是最好的,但它是对象的真正深度副本。它处理简单的财产、循环结构和嵌套对象,但在克隆时会弄乱它们的实例。

小提琴演奏家

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

    /** [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

我只是想在本文中添加到所有Object.create解决方案中,这并不能以nodejs所需的方式工作。

在Firefox中

var a = {"test":"test"};
var b = Object.create(a);
console.log(b);´

is

{test:“test”}。

在nodejs中

{}

我之所以来到这个页面是因为同样的问题,但我既没有使用JQuery,也没有一个克隆方法适用于我自己的对象。

我知道我的答案与这个问题没有太大关系,因为这是一种不同的方法。我不使用克隆函数,而是使用创建函数。它对我来说有以下(不幸的是限制)目的:

我主要使用JSP生成的Javascript我一开始就知道必须生成哪个Object(在我的例子中,它是来自数据库的信息,只获取一次,需要在JS中更频繁地部署。

首先,我这样定义对象:

var obj= new Object();
obj.Type='Row';
obj.ID=1;
obj.Value='Blah blah';

现在我移动了所有的东西,比如:

function getObjSelektor(id_nummer,selected){
var obj = document.createElement("select");
obj.setAttribute("id","Selektor_"+id_nummer);
obj.setAttribute("name","Selektor");
obj.setAttribute("size","1");

var obj_opt_1 = document.createElement("option");
obj_opt_1.setAttribute("value","1");
if(1==selected)
    posopval_opt_1.setAttribute("selected","selected");
obj_opt_1.innerHTML="Blah blah";
obj.appendChild(obj_opt_1);

var obj_opt_2 = document.createElement("option");
obj_opt_2.setAttribute("value","2");
if(2==selected)
    obj_opt_2.setAttribute("selected","selected");
obj_opt_2.innerHTML="2nd Row";
obj.appendChild(obj_opt_2);

...

return obj;
}

并调用常规代码中的函数:

myDiv.getObjSelektor(getObjSelektor(anotherObject.ID));

正如所说,这是一种不同的方法,它为我的目的解决了我的问题。