如何正确克隆 JavaScript 对象?

Posted

技术标签:

【中文标题】如何正确克隆 JavaScript 对象?【英文标题】:How do I correctly clone a JavaScript object? 【发布时间】:2010-10-18 05:17:24 【问题描述】:

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

【问题讨论】:

看到这个问题:***.com/questions/122102/… 对于 JSON,我使用 mObj=JSON.parse(JSON.stringify(jsonObject)); 我真的不明白为什么没有人建议Object.create(o),它可以满足作者的所有要求? var x = deep: key: 1 ; var y = Object.create(x); x.deep.key = 2; 执行此操作后,y.deep.key 也将为 2,因此 Object.create 不能用于克隆... @r3wt 这不起作用...请仅在对解决方案进行基本测试后发布.. 【参考方案1】:

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

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

    我主要使用 JSP 生成的 Javascript 我一开始就知道必须生成哪个对象(在我的例子中,它是来自数据库的信息,它被获取一次并且需要在 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));

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

【讨论】:

【参考方案2】:
function clone(obj)

    var cloneObj = Object.create(obj);

    return cloneObj;

在 Javascript 中对象单独继承另一个对象(原型继承)。 Object.create(obj) 返回一个对象,它是 obj 的子对象或子对象。在上述函数中,它将有效地返回对象的副本。

但是,这是一种非常奇怪的克隆方式,因为我没有将继承用于其真正目的。

【讨论】:

这是原型继承,不是克隆。新对象没有任何自己的属性,它只是使用原型上的属性。 @d13 我不需要新对象的新属性,我只想要新对象中父对象的属性,那为什么不继承呢? 这可能非常有用,但它不是克隆,这就是本主题的内容。克隆是您将一个对象的属性显式复制到另一个对象中,以便新对象不再引用原型上的属性。这样做的原因是为了防止混淆属性是属于对象还是属于它的原型。如果您不确定这种行为,这种混淆可能会导致难以诊断的错误。 Check this JSBin example and explanation. @Shuaib - 因为如果您更改原始对象上的属性值,它们将在新对象上更改......它们没有分开......新对象没有“拥有”这些属性,它只是引用旧对象。 ...这意味着您不妨去var cloned = otherObj; 并以这种方式进行参考。在这两种情况下,新变量引用旧对象的唯一原因是因为它实际上是指向它的指针。一个只是点本身,而另一个持有原型上的指针。 @JimboJonny 递归 Object.create() 怎么样?【参考方案3】:

我最喜欢和优雅的 JS 对象克隆解决方案是

function CloneObject() 
function cloneObject(o) 
   CloneObject.prototype = o;
   return new CloneObject();

使用cloneObject(object) 获取 JS 对象的克隆。

与许多复制解决方案不同,此克隆将原型关系保留在克隆对象中。

【讨论】:

我认为这不能回答操作员发布的问题。 code var o1 = a: 1 var o2 = cloneObject(o1) o2.b = 2 console.log(o1) // 将是 a:1, b:2 code 这和Object.create一样。 这是原型继承,不是克隆。 @d13——对很多人来说,这就是克隆。复制属性就是复制。 你能确定实现会总是在实例化时将原型引用复制到实例中吗?我不是说不会,但我有疑问。不管怎样,这只是以原始为原型制作一个空对象,这是不一样的。如果你不打算修改任何东西,那么你不需要克隆,如果你想保留一个快照,它也不会工作,因为原型的属性将会改变,因为原型 原来的对象。【参考方案4】:

请咨询http://www.w3.org/html/wg/drafts/html/master/infrastructure.html#safe-passing-of-structured-data,了解 W3C 的“结构化数据的安全传递”算法,该算法旨在由浏览器实现,用于将数据传递给网络工作者等。但是,它有一些限制,因为它不处理函数。请参阅https://developer.mozilla.org/en-US/docs/DOM/The_structured_clone_algorithm 了解更多信息,包括 JS 中的另一种算法,它可以让您部分实现。

【讨论】:

虽然这有一些很棒的链接,但这并不是一个真正的答案。如果它被扩展为包含引用的算法的实现,它可能是一个答案。【参考方案5】:

使用 Lodash:

var y = _.clone(x, true);

【讨论】:

天啊,重新发明克隆技术真是太疯狂了。这是唯一理智的答案。 我更喜欢_.cloneDeep(x),因为它本质上和上面一样,但是读起来更好。【参考方案6】:

A.Levy 的回答差不多完成了,这是我的一点贡献:有一种方法可以处理递归引用,请看这一行

if(this[attr]==this) copy[attr] = copy;

如果对象是 XML DOM 元素,我们必须使用 cloneNode 代替

if(this.cloneNode) return this.cloneNode(true);

受 A.Levy 详尽的研究和 Calvin 的原型制作方法的启发,我提供了以下解决方案:

Object.prototype.clone = function() 
  if(this.cloneNode) return this.cloneNode(true);
  var copy = this instanceof Array ? [] : ;
  for(var attr in this) 
    if(typeof this[attr] == "function" || this[attr]==null || !this[attr].clone)
      copy[attr] = this[attr];
    else if(this[attr]==this) copy[attr] = copy;
    else copy[attr] = this[attr].clone();
  
  return copy;


Date.prototype.clone = function() 
  var copy = new Date();
  copy.setTime(this.getTime());
  return copy;


Number.prototype.clone = 
Boolean.prototype.clone =
String.prototype.clone = function() 
  return this;

另请参阅答案中的 Andy Burke 注释。

【讨论】:

Date.prototype.clone = function() return new Date(+this);【参考方案7】:

由于mindeavor 声明要克隆的对象是“文字构造”对象,因此解决方案可能是简单地生成该对象多次,而不是克隆该对象的一个​​实例:

function createMyObject()

    var myObject =
    
        ...
    ;
    return myObject;


var myObjectInstance1 = createMyObject();
var myObjectInstance2 = createMyObject();

【讨论】:

【参考方案8】:

您可以使用一行代码克隆一个对象并从前一个对象中删除任何引用。只需这样做:

var obj1 =  text: 'moo1' ;
var obj2 = Object.create(obj1); // Creates a new clone without references

obj2.text = 'moo2'; // Only updates obj2's text property

console.log(obj1, obj2); // Outputs: obj1: text:'moo1', obj2: text:'moo2'

对于当前不支持 Object.create 的浏览器/引擎,您可以使用这个 polyfill:

// Polyfill Object.create if it does not exist
if (!Object.create) 
    Object.create = function (o) 
        var F = function () ;
        F.prototype = o;
        return new F();
    ;

【讨论】:

+1 Object.create(...) 似乎绝对是要走的路。 完美答案。也许您可以为Object.hasOwnProperty 添加解释?这样人们就知道如何防止搜索原型链接。 效果很好,但是 polyfill 在哪些浏览器中工作? 这是用 obj1 作为原型创建 obj2。它之所以有效,是因为您正在遮蔽 obj2 中的 text 成员。您不是在制作副本,只是在 obj2 上找不到成员时遵循原型链。 这不会在“没有引用的情况下”创建它,它只是将引用移动到原型。它仍然是一个参考。如果原始属性发生变化,“克隆”中的原型属性也会发生变化。它根本不是克隆。【参考方案9】:
function clone(src, deep) 

    var toString = Object.prototype.toString;
    if(!src && typeof src != "object")
        //any non-object ( Boolean, String, Number ), null, undefined, NaN
        return src;
    

    //Honor native/custom clone methods
    if(src.clone && toString.call(src.clone) == "[object Function]")
        return src.clone(deep);
    

    //DOM Elements
    if(src.nodeType && toString.call(src.cloneNode) == "[object Function]")
        return src.cloneNode(deep);
    

    //Date
    if(toString.call(src) == "[object Date]")
        return new Date(src.getTime());
    

    //RegExp
    if(toString.call(src) == "[object RegExp]")
        return new RegExp(src);
    

    //Function
    if(toString.call(src) == "[object Function]")
        //Wrap in another method to make sure == is not true;
        //Note: Huge performance issue due to closures, comment this :)
        return (function()
            src.apply(this, arguments);
        );

    

    var ret, index;
    //Array
    if(toString.call(src) == "[object Array]")
        //[].slice(0) would soft clone
        ret = src.slice();
        if(deep)
            index = ret.length;
            while(index--)
                ret[index] = clone(ret[index], true);
            
        
    
    //Object
    else 
        ret = src.constructor ? new src.constructor() : ;
        for (var prop in src) 
            ret[prop] = deep
                ? clone(src[prop], true)
                : src[prop];
        
    

    return ret;
;

【讨论】:

if(!src && typeof src != "object")。我认为应该是|| 而不是&&【参考方案10】:

这是对 A. Levy 的代码的改编,也可以处理函数的克隆和多个/循环引用 - 这意味着如果被克隆的树中的两个属性是同一对象的引用,则克隆的对象树将使这些属性指向被引用对象的同一个克隆。这也解决了循环依赖的情况,如果不加以处理,就会导致无限循环。算法复杂度为O(n)

function clone(obj)
    var clonedObjectsArray = [];
    var originalObjectsArray = []; //used to remove the unique ids when finished
    var next_objid = 0;

    function objectId(obj) 
        if (obj == null) return null;
        if (obj.__obj_id == undefined)
            obj.__obj_id = next_objid++;
            originalObjectsArray[obj.__obj_id] = obj;
        
        return obj.__obj_id;
    

    function cloneRecursive(obj) 
        if (null == obj || typeof obj == "string" || typeof obj == "number" || typeof obj == "boolean") 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; i < obj.length; ++i) 
                copy[i] = cloneRecursive(obj[i]);
            
            return copy;
        

        // Handle Object
        if (obj instanceof Object) 
            if (clonedObjectsArray[objectId(obj)] != undefined)
                return clonedObjectsArray[objectId(obj)];

            var copy;
            if (obj instanceof Function)//Handle Function
                copy = function()return obj.apply(this, arguments);;
            else
                copy = ;

            clonedObjectsArray[objectId(obj)] = copy;

            for (var attr in obj)
                if (attr != "__obj_id" && obj.hasOwnProperty(attr))
                    copy[attr] = cloneRecursive(obj[attr]);                 

            return copy;
               


        throw new Error("Unable to copy obj! Its type isn't supported.");
    
    var cloneObj = cloneRecursive(obj);



    //remove the unique ids
    for (var i = 0; i < originalObjectsArray.length; i++)
    
        delete originalObjectsArray[i].__obj_id;
    ;

    return cloneObj;

一些快速测试

var auxobj = 
    prop1 : "prop1 aux val", 
    prop2 : ["prop2 item1", "prop2 item2"]
    ;

var obj = new Object();
obj.prop1 = "prop1_value";
obj.prop2 = [auxobj, auxobj, "some extra val", undefined];
obj.nr = 3465;
obj.bool = true;

obj.f1 = function ()
    this.prop1 = "prop1 val changed by f1";
;

objclone = clone(obj);

//some tests i've made
console.log("test number, boolean and string cloning: " + (objclone.prop1 == obj.prop1 && objclone.nr == obj.nr && objclone.bool == obj.bool));

objclone.f1();
console.log("test function cloning 1: " + (objclone.prop1 == 'prop1 val changed by f1'));
objclone.f1.prop = 'some prop';
console.log("test function cloning 2: " + (obj.f1.prop == undefined));

objclone.prop2[0].prop1 = "prop1 aux val NEW";
console.log("test multiple references cloning 1: " + (objclone.prop2[1].prop1 == objclone.prop2[0].prop1));
console.log("test multiple references cloning 2: " + (objclone.prop2[1].prop1 != obj.prop2[0].prop1));

【讨论】:

截至 2016 年 9 月,这是该问题的唯一正确解决方案。【参考方案11】:

我只是想在这篇文章中添加到所有Object.create 解决方案,这不适用于nodejs。

在 Firefox 中的结果

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

test:"test".

在nodejs中是


【讨论】:

这是原型继承,不是克隆。 @d13 虽然您的论点有效,但请注意,JavaScript 中没有克隆对象的标准化方法。这是原型继承,但如果您了解这些概念,它仍然可以用作克隆。 @froginvasion。使用 Object.create 的唯一问题是嵌套对象和数组只是对原型嵌套对象和数组的指针引用。 jsbin.com/EKivInO/2/edit?js,console。从技术上讲,“克隆”对象应该有自己独特的属性,这些属性不是对其他对象属性的共享引用。 @d13 好的,我现在明白你的意思了。但我的意思是,太多人对原型继承的概念感到疏远,对我来说无法了解它是如何工作的。如果我没记错的话,可以通过调用Object.hasOwnProperty 检查您是否拥有该数组来修复您的示例。是的,这确实增加了处理原型继承的额外复杂性。【参考方案12】:

Jan Turoň 上面的答案非常接近,由于兼容性问题,可能最好在浏览器中使用,但它可能会导致一些奇怪的枚举问题。例如,执行:

for ( var i in someArray )  ... 

在遍历数组元素后将 clone() 方法分配给 i。这是避免枚举并与node.js一起使用的改编:

Object.defineProperty( Object.prototype, "clone", 
    value: function() 
        if ( this.cloneNode )
        
            return this.cloneNode( true );
        

        var copy = this instanceof Array ? [] : ;
        for( var attr in this )
        
            if ( typeof this[ attr ] == "function" || this[ attr ] == null || !this[ attr ].clone )
            
                copy[ attr ] = this[ attr ];
            
            else if ( this[ attr ] == this )
            
                copy[ attr ] = copy;
            
            else
            
                copy[ attr ] = this[ attr ].clone();
            
        
        return copy;
    
);

Object.defineProperty( Date.prototype, "clone", 
    value: function() 
        var copy = new Date();
        copy.setTime( this.getTime() );
        return copy;
    
);

Object.defineProperty( Number.prototype, "clone",  value: function()  return this;   );
Object.defineProperty( Boolean.prototype, "clone",  value: function()  return this;   );
Object.defineProperty( String.prototype, "clone",  value: function()  return this;   );

这避免了使 clone() 方法可枚举,因为 defineProperty() 默认可枚举为 false。

【讨论】:

【参考方案13】:

有很多答案,但没有一个提到 ECMAScript 5 中的Object.create,它诚然没有给你一个精确的副本,但将源设置为新对象的原型。

因此,这不是问题的确切答案,但它是一种单行解决方案,因此很优雅。它最适用于 2 种情况:

    这种继承在哪里有用(呵呵!) 源对象不会被修改,因此这两个对象之间的关系不成问题。

例子:

var foo =  a : 1 ;
var bar = Object.create(foo);
foo.a; // 1
bar.a; // 1
foo.a = 2;
bar.a; // 2 - prototype changed
bar.a = 3;
foo.a; // Still 2, since setting bar.a makes it an "own" property

为什么我认为这个解决方案更好?它是原生的,因此没有循环,没有递归。但是,较旧的浏览器将需要一个 polyfill。

【讨论】:

这是原型继承,不是克隆。这些是完全不同的事情。新对象没有任何它自己的属性,它只是指向原型的属性。克隆的重点是创建一个不引用另一个对象中任何属性的全新对象。【参考方案14】:

来自Apple JavaScript Coding Guidelines:

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

史蒂夫

【讨论】:

没有克隆方法的对象属性将被此代码浅拷贝。因此,对原件的更改会影响副本。所以这并不能解决问题。【参考方案15】:

如果您的对象中没有循环依赖,我建议使用其他答案之一或jQuery's copy methods,因为它们看起来都非常有效。

如果存在循环依赖关系(即,两个子对象相互链接),那么(从理论的角度)no way to solve this issue elegantly,您就有点搞砸了。

【讨论】:

实际上,Python 的对象序列化通过跟踪对象图中已经处理过的节点来处理循环引用。您可以使用该方法来实现强大的复制例程。不过,这将是更多的工作!

以上是关于如何正确克隆 JavaScript 对象?的主要内容,如果未能解决你的问题,请参考以下文章

如何正确克隆 JavaScript 对象?

如何正确克隆 JavaScript 对象?

如何正确克隆 JavaScript 对象?

如何正确克隆 JavaScript 对象?

如何正确克隆 JavaScript 对象?

关于JavaScript对象深度克隆