2014-05-20 11 views
9

Betrachten Sie den folgenden Code oder überprüfen Sie diese fiddle.Objekt in JavaScript klonen

Ich habe ein Objekt mit dem Namen obj erstellt und es hat zwei Eigenschaften. Jetzt weise ich obj einem anderen Objekt namens objTwo zu. Jetzt aktualisiere ich eine der Eigenschaften in objTwo. Dieselbe Änderung spiegelt sich auch in obj wider. Wie kann ich Werte von einem Objekt zum anderen zuweisen, ohne eine Referenz zu erstellen?

+1

"Ohne Referenz zuweisen" heißt in JavaScript und ähnlichen Sprachen "Klon". Siehe [diese Frage] (http://stackoverflow.com/questions/122102/what-ist-most-efficient-way-to-clone-an- object) für mögliche Implementierungen. – georg

+0

@georg: Danke. Klon ist das richtige Wort. Ich habe es verpasst – SharpCoder

+0

@ georg-die akzeptierte Antwort gibt es keine besonders gute Antwort, es sagt im Wesentlichen "jQuery verwenden". – RobG

Antwort

2

dies zuweisen nicht durch Verweis

<script> 
    var obj = 
    { 
     name: 'abc', 
     age: '30' 
    }; 

    var objTwo = {}; 

    for(var i in obj) 
    { 
     objTwo[i] = obj[i]; 
    } 
</script> 

Ansicht fiddle

+2

Sie sollten eine [* hasOwnProperty *] (http://ecma-international.org/ecma-262/) 5.1/# sec-15.2.4.5) filter (oder benutze [* Object.keys *] (http://ecma-international.org/ecma-262/5.1/#sec-15.2.3.14)), das obige wird kopieren aufzählbare geerbte Eigenschaften auch. – RobG

+0

danke für die neue info..macht meine Codierung besser .. @RobG – Hawk

+0

@RobG: Aber es ist nicht notwendig für einfache Objekte wie in diesem Beispiel. Nur wenn Sie eine generische Funktion implementiert haben, die einmal dazu verwendet werden könnte, ausgefallene Instanzen zu klonen, würden Sie sich darum kümmern. – Bergi

1
var obj, objTwo; 

obj = { 
    name: "abc", 
    age: 20 
} 
console.log(obj.age); 
objTwo = copy(obj); 
objTwo.age = 10; 
console.log(obj.age); 

function copy (obj) { 
    var key, rtn = Object.create(Object.getPrototypeOf(obj)); 
    for (key in obj) { 
     if (obj.hasOwnProperty(key)) { 
      rtn[key] = obj[key]; 
     } 
    } 
    return rtn; 
} 

In Javascript alles durch Referenz übergeben wird. Der Grund dafür, dass die Änderung in die ursprüngliche Funktionseigenschaft (Variable) "übergeht", liegt darin, dass Sie die Eigenschaft des Objekts und nicht das Objekt (Referenz) selbst ändern. Kopieren Sie das Objekt in eine andere Variable, anstatt es neu zuzuweisen.

Off-Topic:

In Javascript alles durch Referenz übergeben wird. ist anscheinend ein wenig umstritten; Deshalb füge ich diesen Nachtrag hinzu. Fühl mich frei, mich zu korrigieren, wenn ich falsch liege.

Is JavaScript a pass-by-reference or pass-by-value language? Die Top-Antwort heißt es am deutlichsten:

Instead, the situation is that the item passed in is passed by value. But the item that is passed by value is itself a reference.

So war meine Formulierung verwirrend, aber wenn Sie auch in meinem Kopf behalten, dass jeder Betreiber einen Verweis zurückgibt, und bedenken Sie, dass jede Zuordnung und Parameter Durch das Passing werden einfach die Referenzen kopiert, die von den Operatoren (und den Wertliteralen) zurückgegeben werden, dann ist die Art der Übergabe nach Referenz sinnvoll.

Die verknüpfte obere Antwort enthält die vollständige (korrekte) Erklärung.

+6

'Unbehandelter Fehler: Object.defineProperties: Eigenschaftsdeskriptor ist kein Objekt' – Bergi

+0

@Bergi Danke, dass Sie das herausgebracht haben. Dummer Fehler meinerseits. @ Jack_of_All_Trades Könnte stimmen, einige konstruktive Argumente würden geschätzt werden. Ich ignoriere, dass alles, was du gesagt hast, in CAPS ist, ich bin nicht interessiert. –

+1

@KemHeyndels: Ich entschuldige mich dafür. Es war für Humor gedacht. Entschuldigung nochmal dafür. Hier ist der Link, den ich angehängt habe, der helfen könnte, mein ignorantes Verhalten zu korrigieren. JavaScript wird nach Wert für die primitiven Typen und durch Verweis auf Objekttypen weitergegeben. Der Link könnte weitere Informationen liefern. http://snook.ca/archives/javascript/javascript_pass –

0

Okay, das könnte eine seltsame Lösung sein, aber es ist einfach JavaScript. Wenn Sie etwas klonen wollen, würde ich mit dem Wort gehen, „tiefe Kopie“ können Sie JSON wie folgt verwenden:

var obj = { 
    name: "abc", 
    age: 20 
} 


new_object=JSON.parse(JSON.stringify(obj)); 

Nun, ihr Klon des Objekts obj haben.

Eine andere Lösung ist wie folgt:

var new_obj={}; 
for(new_prop in obj){ 
    if (obj.hasOwnProperty(new_prop)){ 
      new_obj[new_prop]=obj[new_prop] 
    } 
} 
+3

Das kann unerwünschte Nebenwirkungen haben, wie Datumsobjekte in Strings umgewandelt werden. – RobG

+0

Downvoters, warum erklären? –

+0

Ich habe nicht abgestimmt, aber ich nehme an, es ist, weil es keine gute Lösung ist. Es ignoriert auch Eigenschaften, deren Werte Funktionen sind und solche mit einem Wert von * undefiniert *. – RobG

1

Ich würde jQuery verwenden, um dies zu tun:

var obj1 = { 
    name: "abc", 
    age: 20  
} 

console.log(obj1); 

var obj2 = $.extend({}, obj1, {}); 
console.log(obj2); 

obj2.age = 1; 
console.log(obj2); 

console.log(obj1); 
1

Wenn Sie nur einfache Objekte klonen müssen, einfach

tun

JSON.parse (JSON.stringify (obj))

würde ausreichen.

Aber das funktioniert offensichtlich nicht in allen Fällen, da JSON.stringify nicht mit zirkulären Referenzen umgehen und Funktionen auslöscht.

Wenn Sie also darüber hinaus gehen wollen, werden die Dinge komplizierter und Sie müssen entweder auf eine Utility-Bibliothek zurückgreifen oder müssen Ihre eigene Deep Clone-Methode implementieren.

Hier ist eine Beispielimplementierung, die ein Niveau der Tiefe zu klonen erwartet.

(function (Object, Array) { 
    function cloneObject(deep, scope, clonedScope) { 
     var type = typeof this, 
      clone = {}, 
      isCR = -1; 

     deep = Number(deep) || 0; 
     scope = scope || []; 
     clonedScope = clonedScope || []; 

     if (!Array.isArray(scope) || !Array.isArray(clonedScope) || clonedScope.length !== scope.length) { 
      throw new TypeError("Unexpected input"); 
     } 
     //If we find a primitive, we reeturn its value. 
     if (type !== "object") { 
      return this.valueOf(); 
     } 

     scope.push(this); 
     clonedScope.push(clone); 

     if (0 === deep) { //If we reached the recursion limit, we can perform a shallow copy 
      for (var prop in this) { 
       clone[prop] = this[prop]; 
      } 
     } else { //Otherwise we need to make some checks first. 
      for (var prop in this) { 
       if ((isCR = scope.indexOf(this[prop])) > -1) { //If we find a circular reference, we want create a new circular reference to the cloned version. 
        clone[prop] = clonedScope[isCR]; 
       } else if (typeof this[prop] !== "undefined" && this[prop] !== null) { //Otherwise continue cloning. 
        clone[prop] = (typeof this[prop] !== "object" ? this[prop] : this[prop].clone(deep - 1, scope, clonedScope)); //If we find a non object, we can directly assign it. Otherwise we need to recursively call the clone function, counting down the limit, and injecting the scopeArrays, to find circular references. 
       } else { //If the property is undefined or null, assign it as such. 
        clone[prop] = this[prop]; 
       } 
      } 
     } 

     scope.pop(); //If we leave a recursion leve, we remove the current object from the list. 
     clonedScope.pop(); 

     return clone; 
    } 


    function cloneArray(deep, scope, clonedScope) { 
     var clone = []; 

     deep = Number(deep) || 0; 

     scope = scope || []; 
     clonedScope = clonedScope || []; 

     if (!Array.isArray(scope) || !Array.isArray(clonedScope) || clonedScope.length !== scope.length) { 
      throw new TypeError("Unexpected input"); 
     } 


     scope.push(this); 
     clonedScope.push(this); 

     if (0 === deep) clone = this.concat(); 
     else this.forEach(function (e) { 
      if ((isCR = scope.indexOf(e)) > -1) { 
       clone.push(clonedScope[isCR]); 
      } else if (typeof e !== "undefined" && e !== null) { 
       clone.push((typeof e !== "object" ? e : e.clone(deep - 1, scope, clonedScope))); 
      } else { 
       clone.push(e); 
      } 
     }); 

     scope.pop(); 
     clonedScope.pop(); 

     return clone; 
    } 

    Object.defineProperty(Object.prototype, "clone", { 
     enumerable: false, 
     value: cloneObject 
    }); 

    Object.defineProperty(Array.prototype, "clone", { 
     enumerable: false, 
     value: cloneArray 
    }); 

})(Object, Array); 

Beachten Sie, dass die die Einbauten Prototypen erstreckt, ist oft verpönt, jedoch habe ich beschlossen, es so zu tun, um eine zusätzliche typecheck zu vermeiden und die Logik für Arrays aufgeteilt und Objekte ein bisschen mehr. Dies kann leicht zu einer normalen Funktion refaktoriert werden

Einige Tests, um zu überprüfen, ob wir tatsächlich neue Referenzen haben.

var first = { 
    a: { 
     b: "b", 
     c: { 

     } 
    }, 
    b: "asd", 
    c: [{}], 
    d: undefined, 
    e: null, 
    f: function a() {} //functions keep their original reference.. 
}; 

first.a.c.a = first.a; //Circular object reference 
first.c.push(first.c); //Circular array reference 

var second = first.clone(Infinity); 

console.log(second, second.a === second.a.c.a, first.a !== second.a.c.a); //..., true, true. 

Es kann eine Menge Platz für Verbesserungen, ich particularily mag nicht, wie der Umfang und clonedScope gets injiziert. Wenn jemand eine bessere Idee hat, kreisförmige Referenzen zu finden und wieder anzubringen, würde ich mich freuen, die Antwort zu aktualisieren

Hier ist ein Fiddle auch.