Wie klone ich ein JavaScript-Objekt richtig?


3078

Ich habe ein Objekt x. Ich möchte es als Objekt kopieren y, so dass Änderungen ynicht geändert werden x. Ich habe festgestellt, dass das Kopieren von Objekten, die von integrierten JavaScript-Objekten abgeleitet sind, zu zusätzlichen, unerwünschten Eigenschaften führt. Dies ist kein Problem, da ich eines meiner wörtlich konstruierten Objekte kopiere.

Wie klone ich ein JavaScript-Objekt richtig?



256
Für JSON verwende ichmObj=JSON.parse(JSON.stringify(jsonObject));
Lord Loh.

67
Ich verstehe wirklich nicht, warum niemand vorschlägt Object.create(o), es macht alles, was der Autor verlangt?
Froginvasion

44
var x = { deep: { key: 1 } }; var y = Object.create(x); x.deep.key = 2; Danach y.deep.keywird auch 2 sein, daher kann Object.create NICHT zum Klonen verwendet werden ...
Ruben Stolk

17
@ r3wt das wird nicht funktionieren ... Bitte erst nach dem grundlegenden Test der Lösung
posten

Antworten:


1561

Dies für ein Objekt in JavaScript zu tun, ist nicht einfach oder unkompliziert. Sie werden auf das Problem stoßen, fälschlicherweise Attribute aus dem Prototyp des Objekts zu übernehmen, die im Prototyp verbleiben und nicht in die neue Instanz kopiert werden sollten. Wenn Sie beispielsweise eine cloneMethode hinzufügen Object.prototype, wie einige Antworten zeigen, müssen Sie dieses Attribut explizit überspringen. Aber was ist, wenn andere zusätzliche Methoden Object.prototypeoder andere Zwischenprototypen hinzugefügt werden , von denen Sie nichts wissen? In diesem Fall kopieren Sie Attribute, die Sie nicht sollten, sodass Sie unvorhergesehene, nicht lokale Attribute mit der hasOwnPropertyMethode erkennen müssen.

Zusätzlich zu nicht aufzählbaren Attributen tritt ein schwierigeres Problem auf, wenn Sie versuchen, Objekte mit versteckten Eigenschaften zu kopieren. Ist beispielsweise prototypeeine versteckte Eigenschaft einer Funktion. Außerdem wird auf den Prototyp eines Objekts mit dem Attribut verwiesen __proto__, das ebenfalls ausgeblendet ist, und nicht von einer for / in-Schleife kopiert, die über die Attribute des Quellobjekts iteriert. Ich denke, es __proto__könnte spezifisch für den JavaScript-Interpreter von Firefox sein und in anderen Browsern etwas anderes, aber Sie erhalten das Bild. Nicht alles ist aufzählbar. Sie können ein verstecktes Attribut kopieren, wenn Sie seinen Namen kennen, aber ich kenne keine Möglichkeit, es automatisch zu erkennen.

Ein weiterer Haken bei der Suche nach einer eleganten Lösung ist das Problem, die Vererbung des Prototyps korrekt einzurichten. Wenn der Prototyp Ihres Quellobjekts so ist Object, {}funktioniert das einfache Erstellen eines neuen allgemeinen Objekts mit. Wenn der Prototyp der Quelle jedoch ein Nachkomme von ist Object, fehlen Ihnen die zusätzlichen Elemente des Prototyps, den Sie mithilfe des hasOwnPropertyFilters übersprungen haben oder den waren im Prototyp, aber überhaupt nicht aufzählbar. Eine Lösung könnte darin bestehen, die constructorEigenschaft des Quellobjekts aufzurufen , um das ursprüngliche Kopierobjekt abzurufen und dann über die Attribute zu kopieren. Dann erhalten Sie jedoch immer noch keine nicht aufzählbaren Attribute. Ein DateObjekt speichert beispielsweise seine Daten als verstecktes Element:

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);

Die Datumszeichenfolge für d1liegt 5 Sekunden hinter der von d2. Eine Möglichkeit, eine mit Dateder anderen zu vergleichen, besteht darin, die setTimeMethode aufzurufen. Dies ist jedoch spezifisch für die DateKlasse. Ich glaube nicht, dass es eine kugelsichere allgemeine Lösung für dieses Problem gibt, obwohl ich gerne falsch liegen würde!

Als ich hatte General tief zu implementieren Kopiere ich durch die Annahme zu gefährden endete , dass ich nur eine Ebene zu kopieren brauchen würde Object, Array, Date, String, Number, oder Boolean. Die letzten 3 Typen sind unveränderlich, so dass ich eine flache Kopie erstellen und mir keine Sorgen machen könnte, dass sie sich ändert. Ich ging weiter davon aus, dass alle Elemente, die in einer der 6 einfachen Typen in dieser Liste enthalten sind Objectoder Arrayauch sein würden. Dies kann mit Code wie dem folgenden erreicht werden:

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.");
}

Die obige Funktion funktioniert für die 6 einfachen Typen, die ich erwähnt habe, angemessen, solange die Daten in den Objekten und Arrays eine Baumstruktur bilden. Das heißt, es gibt nicht mehr als einen Verweis auf dieselben Daten im Objekt. Zum Beispiel:

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

Es kann kein JavaScript-Objekt verarbeiten, kann jedoch für viele Zwecke ausreichen, solange Sie nicht davon ausgehen, dass es nur für alles funktioniert, was Sie darauf werfen.


5
funktionierte fast gut in einem Nodejs - musste nur die Zeile für (var i = 0, var len = obj.length; i <len; ++ i) {in for (var i = 0; i <obj.length; ++ i) {
Trindaz

5
Für zukünftige Googler: Dieselbe tiefe Kopie, die Referenzen rekursiv weitergibt, anstatt ' return'
Trindaz

7
Wäre heutzutage JSON.parse(JSON.stringify([some object]),[some revirer function])eine Lösung?
KooiInc

5
Sind Sie sich im ersten Ausschnitt sicher, dass dies nicht der Fall sein sollte var cpy = new obj.constructor()?
Cyon

8
Die Objektzuweisung scheint in Chrome keine echte Kopie zu sein, behält den Verweis auf das ursprüngliche Objekt bei - endete mit JSON.stringify und JSON.parse zum Klonen - funktionierte perfekt
1owk3y

974

Wenn Sie Datein Ihrem Objekt keine s, Funktionen, undefined, regExp oder Infinity verwenden, ist ein sehr einfacher Einzeiler 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()

Dies funktioniert für alle Arten von Objekten, die Objekte, Arrays, Zeichenfolgen, Boolesche Werte und Zahlen enthalten.

Siehe auch diesen Artikel über den strukturierten Klonalgorithmus von Browsern, der beim Posten von Nachrichten an und von einem Worker verwendet wird. Es enthält auch eine Funktion zum tiefen Klonen.


42
Beachten Sie, dass dies nur zum Testen verwendet werden kann. Erstens ist es in Bezug auf Zeit und Speicherverbrauch alles andere als optimal. Zweitens haben nicht alle Browser diese Methoden.
Nux

2
Sie können jederzeit JSON2.js oder JSON3.js einschließen. Sie würden sie sowieso für Ihre App benötigen. Ich stimme jedoch zu, dass dies möglicherweise nicht die beste Lösung ist, da JSON.stringify keine geerbten Eigenschaften enthält.
Tim Hong

78
@Nux, warum nicht optimal in Bezug auf Zeit und Speicher? MiJyn sagt: "Der Grund, warum diese Methode langsamer ist als das flache Kopieren (auf einem tiefen Objekt), ist, dass diese Methode per Definition tiefe Kopien enthält. Da JSON jedoch in nativem Code (in den meisten Browsern) implementiert ist, ist dies erheblich schneller als bei Verwendung einer anderen Javascript-basierten Deep-Copying-Lösung und manchmal schneller als eine Javascript-basierte Flachkopiertechnik (siehe: jsperf.com/cloning-an-object/79). " stackoverflow.com/questions/122102/…
BeauCielBleu

16
Ich möchte nur ein Update für Oktober 2014 hinzufügen. Chrome 37+ ist mit JSON.parse (JSON.stringify (oldObject)) schneller. Der Vorteil dieser Verwendung ist, dass es für eine Javascript-Engine sehr einfach ist, etwas Besseres zu erkennen und zu optimieren, wenn sie dies wünscht.
Mirhagk

17
Update 2016: Dies sollte jetzt in nahezu jedem weit verbreiteten Browser funktionieren. (siehe Kann ich verwenden ... ) Die Hauptfrage wäre jetzt, ob es ausreichend performant ist.
James Foster

783

Mit jQuery können Sie flache Kopien mit verlängern :

var copiedObject = jQuery.extend({}, originalObject)

Nachträgliche Änderungen am copiedObjectwerden sich nicht auf das auswirken originalObjectund umgekehrt.

Oder um eine tiefe Kopie zu machen :

var copiedObject = jQuery.extend(true, {}, originalObject)

164
oder sogar:var copiedObject = jQuery.extend({},originalObject);
Grant McLean

82
Auch nützlich, um true als ersten Parameter für Deep Copy anzugeben:jQuery.extend(true, {}, originalObject);
Will Shaver

6
Ja, ich fand diesen Link hilfreich (gleiche Lösung wie Pascal) stackoverflow.com/questions/122102/…
Garry English

3
Nur eine Anmerkung, dies kopiert nicht den Protokonstruktor des Originalobjekts
Sam Jones

1
Nach stackoverflow.com/questions/184710/... , so scheint es , dass „flache Kopie“ kopieren Sie einfach die Referenz von originalObject, also warum das hier sagt ...subsequent changes to the copiedObject will not affect the originalObject, and vice versa.... Entschuldigung, dass ich wirklich verwirrt war.
Carr

687

In ECMAScript 6 gibt es die Object.assign- Methode, mit der Werte aller aufzählbaren eigenen Eigenschaften von einem Objekt in ein anderes kopiert werden. Zum Beispiel:

var x = {myProp: "value"};
var y = Object.assign({}, x); 

Beachten Sie jedoch, dass verschachtelte Objekte weiterhin als Referenz kopiert werden.


Ja, ich glaube, das Object.assignist der richtige Weg. Es ist auch einfach, es zu füllen

22
Beachten Sie auch, dass dies über „Methoden“ definiert über Objektliterale kopieren (da diese sind zählbare) aber nicht Methoden über die „Klasse“ Mechanismus trotzte (da diese nicht zählbare).
Marcus Junius Brutus

16
Ich denke sollte erwähnt werden, dass dies vom IE außer Edge nicht unterstützt wird. Einige Leute benutzen das immer noch.
Saulius

1
Dies ist das gleiche wie @EugeneTiurin seine Antwort.
Wilt

5
Ich spreche aus Erfahrung hier ... Verwenden Sie dies NICHT. Objekte sind hierarchisch angelegt und Sie kopieren nur die erste Ebene, sodass Sie das gesamte kopierte Objekt zuweisen können. Vertrauen Sie mir, es kann Ihnen Tage des Kopfkratzens ersparen.
Ow3n

232

Pro MDN :

  • Wenn Sie eine flache Kopie wünschen, verwenden Sie Object.assign({}, a)
  • Verwenden Sie für "tiefe" Kopie JSON.parse(JSON.stringify(a))

Es sind keine externen Bibliotheken erforderlich, Sie müssen jedoch zuerst die Browserkompatibilität überprüfen .


8
JSON.parse (JSON.stringify (a)) sieht wunderschön aus, aber bevor Sie es verwenden, empfehle ich, die Zeit zu messen, die für Ihre gewünschte Sammlung benötigt wird. Abhängig von der Objektgröße ist dies möglicherweise nicht die schnellste Option.
Edza

3
JSON-Methode Ich habe festgestellt, dass Datumsobjekte in Zeichenfolgen konvertiert werden, jedoch nicht zurück in Datumsangaben. Sie müssen sich mit dem Spaß an Zeitzonen in Javascript auseinandersetzen und alle Daten manuell festlegen. Kann ähnliche Fälle für andere Typen außer Daten sein
Steve Seeger

Für Date würde ich moment.js verwenden, da es cloneFunktionalität hat. Weitere Informationen finden Sie
Tareq

Dies ist gut, aber seien Sie
vorsichtig,

Ein weiteres Problem beim Parsen von JSON sind Zirkelverweise. Wenn sich innerhalb des Objekts
Zirkelverweise befinden

133

Es gibt viele Antworten, aber keine, die Object.create erwähnt aus ECMAScript 5 erwähnt, das Ihnen zwar keine exakte Kopie gibt, aber die Quelle als Prototyp des neuen Objekts festlegt.

Dies ist also keine genaue Antwort auf die Frage, aber es ist eine einzeilige Lösung und somit elegant. Und es funktioniert am besten für 2 Fälle:

  1. Wo eine solche Vererbung nützlich ist (duh!)
  2. Wenn das Quellobjekt nicht geändert wird, ist die Beziehung zwischen den beiden Objekten kein Problem.

Beispiel:

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

Warum halte ich diese Lösung für überlegen? Es ist nativ, also keine Schleife, keine Rekursion. Ältere Browser benötigen jedoch eine Polyfüllung.


Hinweis: Object.create ist keine tiefe Kopie (itpastorn erwähnte keine Rekursion). Beweis:var a = {b:'hello',c:{d:'world'}}, b = Object.create(a); a == b /* false */; a.c == b.c /* true */;
Zamnüsse

103
Dies ist eine prototypische Vererbung, kein Klonen. Das sind ganz andere Dinge. Das neue Objekt hat keine eigenen Eigenschaften, sondern zeigt nur auf die Eigenschaften des Prototyps. Beim Klonen wird ein neues Objekt erstellt, das keine Eigenschaften in einem anderen Objekt referenziert.
d13

7
Ich stimme dir vollkommen zu. Ich stimme auch zu, dass dies nicht das Klonen ist, wie es "beabsichtigt" sein könnte. Aber komm schon, Leute, nimm die Natur von JavaScript an, anstatt zu versuchen, obskure Lösungen zu finden, die nicht standardisiert sind. Sicher, Sie mögen keine Prototypen und sie sind alle "bla" für Sie, aber sie sind in der Tat sehr nützlich, wenn Sie wissen, was Sie tun.
Froginvasion

4
@RobG: Dieser Artikel erklärt den Unterschied zwischen Referenzieren und Klonen: en.wikipedia.org/wiki/Cloning_(programming) . Object.createverweist durch Verweise auf die Eigenschaften des Elternteils. Wenn sich die Eigenschaftswerte des Elternteils ändern, ändert sich auch das des Kindes. Dies hat einige überraschende Nebenwirkungen bei verschachtelten Arrays und Objekten, die zu schwer zu findenden Fehlern in Ihrem Code führen können, wenn Sie diese nicht kennen: jsbin.com/EKivInO/2 . Ein geklontes Objekt ist ein völlig neues, unabhängiges Objekt, das dieselben Eigenschaften und Werte wie das übergeordnete Objekt hat, jedoch nicht mit dem übergeordneten Objekt verbunden ist.
13.

1
Dies führt Menschen in die Irre ... Object.create () kann als Mittel zur Vererbung verwendet werden, aber das Klonen ist weit davon entfernt.
Prajnavantha

128

Eine elegante Möglichkeit, ein Javascript-Objekt in einer Codezeile zu klonen

Eine Object.assignMethode ist Teil des ECMAScript 2015 (ES6) -Standards und macht genau das, was Sie brauchen.

var clone = Object.assign({}, obj);

Die Object.assign () -Methode wird verwendet, um die Werte aller aufzählbaren eigenen Eigenschaften von einem oder mehreren Quellobjekten in ein Zielobjekt zu kopieren.

Weiterlesen...

Die Polyfüllung zur Unterstützung älterer Browser:

if (!Object.assign) {
  Object.defineProperty(Object, 'assign', {
    enumerable: false,
    configurable: true,
    writable: true,
    value: function(target) {
      'use strict';
      if (target === undefined || target === null) {
        throw new TypeError('Cannot convert first argument to object');
      }

      var to = Object(target);
      for (var i = 1; i < arguments.length; i++) {
        var nextSource = arguments[i];
        if (nextSource === undefined || nextSource === null) {
          continue;
        }
        nextSource = Object(nextSource);

        var keysArray = Object.keys(nextSource);
        for (var nextIndex = 0, len = keysArray.length; nextIndex < len; nextIndex++) {
          var nextKey = keysArray[nextIndex];
          var desc = Object.getOwnPropertyDescriptor(nextSource, nextKey);
          if (desc !== undefined && desc.enumerable) {
            to[nextKey] = nextSource[nextKey];
          }
        }
      }
      return to;
    }
  });
}

Entschuldigung für die dumme Frage, aber warum werden Object.assignzwei Parameter verwendet, wenn die valueFunktion in der Polyfüllung nur einen Parameter akzeptiert?
Qwertie

@ Qwertie gestern Alle Argumente werden iteriert und zu einem Objekt zusammengeführt, wobei die Eigenschaften des zuletzt übergebenen Arguments priorisiert werden
Eugene Tiurin

Oh, ich verstehe, danke (ich war argumentsvorher nicht mit dem Objekt vertraut .) Ich habe Probleme, es Object()über Google zu finden ... es ist eine Typografie, nicht wahr?
Qwertie

44
Dies wird nur ein flaches "Klonen" durchführen
Marcus Junius Brutus

1
Diese Antwort ist genau die gleiche wie diese: stackoverflow.com/questions/122102/… Ich weiß, dass sie von derselben Person stammt, aber Sie sollten darauf verweisen, anstatt nur die Antwort zu kopieren.
Lesolorzanov

87

Bei den meisten Lösungen im Internet gibt es mehrere Probleme. Deshalb habe ich mich für ein Follow-up entschieden, das beinhaltet, warum die akzeptierte Antwort nicht akzeptiert werden sollte.

Ausgangssituation

Ich möchte ein Javascript mit all seinen Kindern und ihren Kindern und so weiter tief kopierenObject . Da ich aber nicht Art eines normalen Entwickler bin, meine Objecthat normale properties , circular structuresund sogarnested objects .

Also lasst uns ein circular structureund ein nested objecterstes erstellen .

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

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

Lassen Sie uns alles in einem ObjectNamen zusammenbringen a.

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

Als nächstes wollen wir ain eine Variable mit dem Namen kopieren bund sie mutieren.

var b = a;

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

Sie wissen, was hier passiert ist, denn wenn nicht, würden Sie nicht einmal auf diese großartige Frage stoßen.

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"
    }
}

Lassen Sie uns nun eine Lösung finden.

JSON

Der erste Versuch, den ich versuchte, war mit JSON.

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

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

Verschwenden Sie nicht zu viel Zeit damit TypeError: Converting circular structure to JSON .

Rekursive Kopie (die akzeptierte "Antwort")

Werfen wir einen Blick auf die akzeptierte Antwort.

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.");
}

Sieht gut aus, oder? Es ist eine rekursive Kopie des Objekts und behandelt auch andere Typen Date, aber das war keine Voraussetzung.

var b = cloneSO(a);

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

Rekursion und circular structuresfunktioniert nicht gut zusammen ...RangeError: Maximum call stack size exceeded

native Lösung

Nachdem ich mit meinem Kollegen gestritten hatte, fragte mich mein Chef, was passiert sei, und er fand nach einigem googeln eine einfache Lösung . Es heißt Object.create.

var b = Object.create(a);

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

Diese Lösung wurde vor einiger Zeit zu Javascript hinzugefügt und funktioniert sogar circular structure.

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"
    }
}

... und Sie sehen, es hat mit der verschachtelten Struktur im Inneren nicht funktioniert.

Polyfill für die native Lösung

Es gibt eine Polyfüllung für Object.createden älteren Browser, genau wie für den IE 8. Sie wird von Mozilla empfohlen, ist natürlich nicht perfekt und führt zum gleichen Problem wie die native Lösung .

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

var b = clonePF(a);

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

Ich habe den Bereich Faußerhalb des Bereichs platziert, damit wir einen Blick darauf werfen können, was instanceofuns sagt.

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

Gleiches Problem wie bei der nativen Lösung , jedoch etwas schlechtere Ausgabe.

die bessere (aber nicht perfekte) Lösung

Beim Stöbern fand ich eine ähnliche Frage ( in Javascript, wenn ich eine tiefe Kopie durchführe, wie vermeide ich einen Zyklus, weil eine Eigenschaft "dies" ist? ) Zu dieser Frage , aber mit einer viel besseren Lösung.

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';

Und schauen wir uns die Ausgabe an ...

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

Die Anforderungen stimmen überein, aber es gibt noch einige kleinere Probleme, einschließlich des Änderns instancevon nestedund circzu Object.

Die Struktur von Bäumen, die sich ein Blatt teilen, wird nicht kopiert, sondern zu zwei unabhängigen Blättern:

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

Fazit

Die letzte Lösung mit Rekursion und Cache ist möglicherweise nicht die beste, aber es handelt sich um eine echte Kopie des Objekts. Es behandelt einfach properties, circular structuresund nested object, aber es wird vermasseln die Instanz von ihnen während klonen.

jsfiddle


12
Die Schlussfolgerung ist also, dieses Problem zu vermeiden :)
Mikus

@mikus, bis es eine echte Spezifikation gibt, die mehr als nur die grundlegenden Anwendungsfälle abdeckt, ja.
Fabio Poloni

2
Eine gute Analyse der oben angegebenen Lösungen, aber die Schlussfolgerung des Autors zeigt, dass es keine Lösung für diese Frage gibt.
Amir Mog

2
Es ist schade, dass JS keine native Klonfunktion enthält.
100k

1
Unter all den Top-Antworten denke ich, dass dies nahe an der richtigen liegt.
KTU

77

Wenn Sie mit einer flachen Kopie einverstanden sind, verfügt die Bibliothek underscore.js über eine Klonmethode .

y = _.clone(x);

oder Sie können es wie erweitern

copiedObject = _.extend({},originalObject);

2
Vielen Dank. Verwenden dieser Technik auf einem Meteor-Server.
Turbo

Um schnell mit lodash zu beginnen, würde ich empfehlen, npm, Browserify sowie lodash zu lernen. Ich habe einen Klon für die Arbeit mit 'npm i --save lodash.clone' und dann 'var clone = require (' lodash.clone ');' Um arbeiten zu müssen, benötigen Sie so etwas wie browserify. Sobald Sie es installiert und erfahren haben, wie es funktioniert, verwenden Sie bei jeder Ausführung Ihres Codes "browserify yourfile.js> bundle.js; Start von chrome index.html" (anstatt direkt in Chrome zu wechseln). Dadurch werden Ihre Datei und alle vom npm-Modul benötigten Dateien in bundle.js zusammengefasst. Sie können wahrscheinlich Zeit sparen und diesen Schritt mit Gulp automatisieren.
Aaron Bell

65

OK, stellen Sie sich vor, Sie haben dieses Objekt unten und möchten es klonen:

let obj = {a:1, b:2, c:3}; //ES6

oder

var obj = {a:1, b:2, c:3}; //ES5

Die Antwort hängt hauptsächlich davon ab, welches ECMA-Skript Sie verwenden. In ES6+können Sie einfach Object.assignden Klon erstellen :

let cloned = Object.assign({}, obj); //new {a:1, b:2, c:3};

oder mit einem Spread-Operator wie folgt:

let cloned = {...obj}; //new {a:1, b:2, c:3};

Wenn Sie jedoch verwenden ES5, können Sie nur wenige Methoden verwenden. JSON.stringifyStellen Sie jedoch sicher, dass Sie nicht einen großen Datenblock zum Kopieren verwenden. In vielen Fällen kann dies jedoch eine praktische Zeile sein:

let cloned = JSON.parse(JSON.stringify(obj)); 
//new {a:1, b:2, c:3};, can be handy, but avoid using on big chunk of data over and over

Können Sie bitte ein Beispiel geben, was big chunk of datagleichbedeutend wäre? 100kb? 100 MB? Vielen Dank!
user1063287

Ja, @ user1063287, dass im Grunde genommen die größeren Daten, die Leistung schlechter sind ... es kommt also wirklich darauf an, nicht ein KB, MB oder GB, es geht mehr darum, wie oft Sie das auch tun möchten ... Auch es wird nicht funktionieren für Funktionen und andere Sachen ...
Alireza

3
Object.assignmacht eine flache Kopie (genau wie die Verbreitung, @Alizera)
Bogdan D

Sie können let in es5 nicht verwenden: ^) @Alireza
SensationSama

40

Eine besonders unelegante Lösung ist die Verwendung der JSON-Codierung, um tiefe Kopien von Objekten zu erstellen, die keine Mitgliedsmethoden haben. Die Methode besteht darin, Ihr Zielobjekt mit JSON zu codieren. Durch Decodieren erhalten Sie dann die gesuchte Kopie. Sie können so oft dekodieren, wie Sie möchten, und so viele Kopien erstellen, wie Sie benötigen.

Natürlich gehören Funktionen nicht zu JSON, daher funktioniert dies nur für Objekte ohne Member-Methoden.

Diese Methode war perfekt für meinen Anwendungsfall, da ich JSON-Blobs in einem Schlüsselwertspeicher speichere und wenn sie als Objekte in einer JavaScript-API verfügbar gemacht werden, enthält jedes Objekt tatsächlich eine Kopie des ursprünglichen Status des Objekts kann das Delta berechnen, nachdem der Anrufer das exponierte Objekt mutiert hat.

var object1 = {key:"value"};
var object2 = object1;

object2 = JSON.stringify(object1);
object2 = JSON.parse(object2);

object2.key = "a change";
console.log(object1);// returns value

Warum gehören Funktionen nicht zu JSON? Ich habe gesehen, dass sie mehr als einmal als JSON übertragen wurden ...
the_drow

5
Funktionen sind nicht Teil der JSON-Spezifikation, da sie keine sichere (oder intelligente) Methode zum Übertragen von Daten darstellen, wofür JSON entwickelt wurde. Ich weiß, dass der native JSON-Encoder in Firefox einfach Funktionen ignoriert, die an ihn übergeben wurden, aber ich bin mir nicht sicher, wie sich andere verhalten.
Kris Walker

1
@mark: { 'foo': function() { return 1; } }ist ein wörtlich erstelltes Objekt.
Abarnert

@ abarnert-Funktionen sind keine Daten. "Funktionsliterale" sind eine Fehlbezeichnung, da Funktionen beliebigen Code enthalten können, einschließlich Zuweisungen und allerlei "nicht serialisierbarer" Dinge.
vemv

35

Sie können einfach eine Spread-Eigenschaft verwenden, um ein Objekt ohne Referenzen zu kopieren. Aber seien Sie vorsichtig (siehe Kommentare), die 'Kopie' befindet sich nur auf der untersten Objekt- / Array-Ebene. Verschachtelte Eigenschaften sind immer noch Referenzen!


Kompletter Klon:

let x = {a: 'value1'}
let x2 = {...x}

// => mutate without references:

x2.a = 'value2'
console.log(x.a)    // => 'value1'

Klonen mit Referenzen auf der zweiten Ebene:

const y = {a: {b: 'value3'}}
const y2 = {...y}

// => nested object is still a references:

y2.a.b = 'value4'
console.log(y.a.b)    // => 'value4'

JavaScript unterstützt Deep Clones nativ nicht. Verwenden Sie eine Utility-Funktion. Zum Beispiel Ramda:

http://ramdajs.com/docs/#clone


1
Dies funktioniert nicht ... es würde wahrscheinlich funktionieren, wenn x ein Array sein wird, zum Beispiel x = ['ab', 'cd', ...]
Kamil Kiełczewski

3
Dies funktioniert, aber denken Sie daran, dass dies eine SHALLOW-Kopie ist. Daher bleiben alle tiefen Verweise auf andere Objekte Verweise!
Bugs Bunny

Ein const first = {a: 'foo', b: 'bar'}; const second = {...{a} = first}
Teilklon

25

Für Benutzer von AngularJS gibt es auch eine direkte Methode zum Klonen oder Erweitern der Objekte in dieser Bibliothek.

var destination = angular.copy(source);

oder

angular.copy(source, destination);

Mehr in der Dokumentation zu angle.copy ...


2
Dies ist eine tiefe Kopie FYI.
Zamnuts

22

Hier ist eine Funktion, die Sie verwenden können.

function clone(obj) {
    if(obj == null || typeof(obj) != 'object')
        return obj;    
    var temp = new obj.constructor(); 
    for(var key in obj)
        temp[key] = clone(obj[key]);    
    return temp;
}

10
Diese Antwort ist ziemlich nah, aber nicht ganz richtig. Wenn Sie versuchen, ein Datumsobjekt zu klonen, erhalten Sie nicht dasselbe Datum, da der Aufruf der Datumskonstruktorfunktion das neue Datum mit dem aktuellen Datum / der aktuellen Uhrzeit initialisiert. Dieser Wert ist nicht aufzählbar und wird von der for / in-Schleife nicht kopiert.
A. Levy

Nicht perfekt, aber schön für diese grundlegenden Fälle. ZB einfaches Klonen eines Arguments zulassen, das ein grundlegendes Objekt, Array oder String sein kann.
James_womack

Upvoted für den korrekten Aufruf des Konstruktors mit new. Die akzeptierte Antwort nicht.
GetFree

funktioniert auf knoten alles andere! noch Links Referenz Links
user956584

Der rekursive Gedanke ist großartig. Aber wenn der Wert Array ist, wird es funktionieren?
Q10Viking

22

A. Levys Antwort ist fast vollständig, hier ist mein kleiner Beitrag: Es gibt eine Möglichkeit, mit rekursiven Referenzen umzugehen , siehe diese Zeile

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

Wenn das Objekt ein XML-DOM-Element ist, müssen wir stattdessen cloneNode verwenden

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

Inspiriert von A. Levys umfassender Studie und Calvins Prototyping-Ansatz biete ich folgende Lösung an:

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;
}

Siehe auch den Hinweis von Andy Burke in den Antworten.


3
Date.prototype.clone = function() {return new Date(+this)};
RobG

22

Aus diesem Artikel: So kopieren Sie Arrays und Objekte in Javascript von Brian Huisman:

Object.prototype.clone = function() {
  var newObj = (this instanceof Array) ? [] : {};
  for (var i in this) {
    if (i == 'clone') continue;
    if (this[i] && typeof this[i] == "object") {
      newObj[i] = this[i].clone();
    } else newObj[i] = this[i]
  } return newObj;
};

4
Dies ist nah, funktioniert aber für kein Objekt. Versuchen Sie damit ein Date-Objekt zu klonen. Nicht alle Eigenschaften sind aufzählbar, daher werden nicht alle in der for / in-Schleife angezeigt.
A. Levy

Das Hinzufügen zu diesem Objektprototyp hat jQuery für mich kaputt gemacht. Auch wenn ich in clone2 umbenannt habe.
iPadDeveloper2011

3
@ iPadDeveloper2011 Der obige Code hatte einen Fehler, bei dem eine globale Variable namens 'i' '(für i in diesem)' anstelle von '(für var i in diesem)' erstellt wurde. Ich habe genug Karma, um es zu bearbeiten und zu reparieren, also habe ich es getan.
Mikemaccana

1
@Calvin: Dies sollte eine nicht aufzählbare Eigenschaft sein, andernfalls wird 'clone' in 'for'-Schleifen angezeigt.
Mikemaccana

2
Warum ist das nicht auch var copiedObj = Object.create(obj);ein guter Weg?
Dan P.

19

In ES-6 können Sie einfach Object.assign (...) verwenden. Ex:

let obj = {person: 'Thor Odinson'};
let clone = Object.assign({}, obj);

Eine gute Referenz finden Sie hier: https://googlechrome.github.io/samples/object-assign-es6/


12
Das Objekt wird nicht tief geklont.
August

Das ist eine Aufgabe, keine Kopie. clone.Title = "nur ein Klon" bedeutet, dass obj.Title = "nur ein Klon".
HoldOffHunger

@HoldOffHunger Sie irren sich. Prüfen Sie es in Ihrem Browser JS - Konsole ( let obj = {person: 'Thor Odinson'}; let clone = Object.assign({}, obj); clone.title = "Whazzup";)
Kollapsar

@collapsar: Genau das habe ich überprüft, dann wird console.log (Person) "Whazzup" sein, nicht "Thor Odinson". Siehe Augusts Kommentar.
HoldOffHunger

1
@HoldOffHunger Kommt weder in Chrome 60.0.3112.113 noch in Edge 14.14393 vor; Der Kommentar von August gilt nicht, da die Werte der objEigenschaften primitiver Typen tatsächlich geklont werden. Eigenschaftswerte, die selbst Objekte sind, werden nicht geklont.
Kollapsar

18

In ECMAScript 2018

let objClone = { ...obj };

Beachten Sie, dass verschachtelte Objekte weiterhin als Referenz kopiert werden .


1
Vielen Dank für den Hinweis, dass verschachtelte Objekte immer noch als Referenz kopiert werden! Beim Debuggen meines Codes wurde ich fast verrückt, weil ich verschachtelte Eigenschaften des "Klons" geändert habe, aber das Original wurde geändert.
Benny Neugebauer

Dies ist ES2016, nicht 2018, und diese Antwort wurde zwei Jahre zuvor gegeben .
Dan Dascalescu

Was ist, wenn ich auch eine Kopie des verschachtelten Eigentums haben möchte
Sunil Garg

1
@ SunilGarg Um auch verschachtelte Eigenschaften zu kopieren, können Sie verwenden const objDeepClone = JSON.parse(JSON.stringify(obj));
Pavan Garre

16

Verwenden von Lodash:

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

5
Oh mein Gott, es wäre verrückt, das Klonen neu zu erfinden. Dies ist die einzig vernünftige Antwort.
Dan Ross

5
Ich bevorzuge, _.cloneDeep(x)da es im Wesentlichen das gleiche wie oben ist, aber besser liest.
Garbanzio


13

Sie können ein Objekt klonen und alle Verweise mit einer einzigen Codezeile aus dem vorherigen entfernen. Einfach machen:

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'}

Für Browser / Engines, die Object.create derzeit nicht unterstützen, können Sie diese Polyfüllung verwenden:

// 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
+1 Object.create(...)scheint definitiv der richtige Weg zu sein.
René Nyffenegger

Perfekte Antwort. Vielleicht könnten Sie eine Erklärung für hinzufügen Object.hasOwnProperty? Auf diese Weise wissen die Benutzer, wie sie das Durchsuchen des Prototyp-Links verhindern können.
Froginvasion

Funktioniert gut, aber in welchen Browsern funktioniert die Polyfüllung?
Ian Lunn

11
Dadurch wird obj2 mit einem obj1 als Prototyp erstellt. Es funktioniert nur, weil Sie das textMitglied in obj2 beschatten. Sie erstellen keine Kopie, sondern verschieben sich nur auf die Prototypenkette, wenn ein Mitglied auf obj2 nicht gefunden wird.
Nick Desaulniers

2
Dadurch wird es NICHT "ohne Referenzen" erstellt, sondern nur die Referenz auf den Prototyp verschoben. Es ist immer noch eine Referenz. Wenn sich eine Eigenschaft im Original ändert, ändert sich auch die Prototyp-Eigenschaft im "Klon". Es ist überhaupt kein Klon.
Jimbo Jonny

13

Neue Antwort auf eine alte Frage! Wenn Sie das Vergnügen haben, ECMAScript 2016 (ES6) mit Spread Syntax zu verwenden , ist dies ganz einfach.

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

Dies bietet eine saubere Methode für eine flache Kopie eines Objekts. Das Erstellen einer tiefen Kopie, dh das Erstellen einer neuen Kopie jedes Werts in jedem rekursiv verschachtelten Objekt, erfordert eine der oben genannten schwereren Lösungen.

JavaScript entwickelt sich weiter.


2
Es funktioniert nicht, wenn Sie Funktionen für Objekte definiert haben
Petr Marek

Soweit ich sehe, funktioniert der Spread-Operator nur mit iterables - developer.mozilla.org sagt: var obj = {'key1': 'value1'}; var array = [...obj]; // TypeError: obj is not iterable
Oleh

@Oleh also benutze `{... obj} anstelle von [... obj];`
manikant gautam

@manikantgautam Ich habe zuvor Object.assign () verwendet, aber jetzt wird die Objektverbreitungssyntax in Chrome, Firefox (immer noch nicht in Edge und Safari) unterstützt. Sein ECMAScript-Vorschlag ... aber Babel unterstützt ihn, soweit ich sehen kann, und ist daher wahrscheinlich sicher zu verwenden.
Oleh

12
let clone = Object.assign( Object.create( Object.getPrototypeOf(obj)), obj)

ES6-Lösung, wenn Sie eine Klasseninstanz und nicht nur ein Eigenschaftsobjekt (flach) klonen möchten .


Wie unterscheidet sich das von let cloned = Object.assign({}, obj)?
Ceztko

10

Ich denke, es gibt eine einfache und funktionierende Antwort. Beim tiefen Kopieren gibt es zwei Bedenken:

  1. Halten Sie Eigenschaften unabhängig voneinander.
  2. Und halten Sie die Methoden für geklonte Objekte am Leben.

Daher denke ich, dass eine einfache Lösung darin bestehen wird, zuerst zu serialisieren und zu deserialisieren und dann eine Zuweisung vorzunehmen, um auch Funktionen zu kopieren.

let deepCloned = JSON.parse(JSON.stringify(source));
let merged = Object.assign({}, source);
Object.assign(merged, deepCloned);

Obwohl diese Frage viele Antworten hat, hoffe ich, dass auch diese hilft.


Wenn ich Lodash importieren darf, bevorzuge ich die Verwendung von Lodash cloneDeep.
ConductedClever

2
Ich verwende JSON.parse (JSON.stringify (Quelle)). Funktioniert immer.
Mischa

2
@ Mischa, auf diese Weise werden Sie die Funktionen vermissen. Der Begriff "Werke" hat viele Bedeutungen.
ConductedClever

Und denken Sie daran, dass, wie ich bereits erwähnt habe, nur die Funktionen der ersten Ebene kopiert werden. Wenn wir also einige Objekte ineinander haben, besteht die einzige Möglichkeit darin, Feld für Feld rekursiv zu kopieren.
ConductedClever

9

Für eine tiefe Kopie und einen Klon JSON.stringify und dann JSON.parse das Objekt:

obj = { a: 0 , b: { c: 0}};
let deepClone = JSON.parse(JSON.stringify(obj));
obj.a = 5;
obj.b.c = 5;
console.log(JSON.stringify(deepClone)); // { a: 0, b: { c: 0}}

ziemlich klug ... irgendwelche Nachteile dieses Ansatzes?
Aleks

8

Dies ist eine Anpassung des Codes von A. Levy, um auch das Klonen von Funktionen und mehrere / zyklische Referenzen zu handhaben. Dies bedeutet, dass der geklonte Objektbaum diese hat, wenn zwei Eigenschaften im geklonten Baum Referenzen desselben Objekts sind Eigenschaften zeigen auf ein und denselben Klon des referenzierten Objekts. Dies löst auch den Fall von zyklischen Abhängigkeiten, die, wenn sie nicht behandelt werden, zu einer Endlosschleife führen. Die Komplexität des Algorithmus ist 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;
}

Einige schnelle Tests

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));

1
Ab September 2016 ist dies die einzig richtige Lösung für die Frage.
DomQ

6

Ich wollte nur zu allen Object.createLösungen in diesem Beitrag hinzufügen , dass dies mit nodejs nicht in der gewünschten Weise funktioniert.

In Firefox das Ergebnis von

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

ist

{test:"test"}.

In nodejs ist es

{}

Dies ist eine prototypische Vererbung, kein Klonen.
13.

1
@ d13 Beachten Sie, dass es in JavaScript keine standardisierte Möglichkeit gibt, ein Objekt zu klonen, solange Ihr Argument gültig ist. Dies ist eine prototypische Vererbung, die jedoch als Klone verwendet werden kann, wenn Sie die Konzepte verstehen.
Froginvasion

@froginvasion. Das einzige Problem bei der Verwendung von Object.create besteht darin, dass verschachtelte Objekte und Arrays nur Zeigerreferenzen auf die verschachtelten Objekte und Arrays des Prototyps sind. jsbin.com/EKivInO/2/edit?js,console . Technisch gesehen sollte ein "geklontes" Objekt seine eigenen eindeutigen Eigenschaften haben, die keine gemeinsamen Verweise auf Eigenschaften anderer Objekte sind.
d13

@ d13 okay, ich verstehe deinen Standpunkt jetzt. Ich meinte aber, dass zu viele Menschen mit dem Konzept der prototypischen Vererbung entfremdet sind und für mich nicht lernen, wie es funktioniert. Wenn ich mich nicht irre, kann Ihr Beispiel durch einfaches Aufrufen behoben werden, um Object.hasOwnPropertyzu überprüfen, ob Sie das Array besitzen oder nicht. Ja, dies erhöht die Komplexität bei der prototypischen Vererbung.
Froginvasion

6
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;
};

2
if(!src && typeof src != "object"){. Ich denke das sollte ||nicht sein &&.
MikeM

6

Da mindeavour angegeben hat, dass das zu klonende Objekt ein "wörtlich konstruiertes" Objekt ist, könnte eine Lösung darin bestehen, das Objekt einfach mehrmals zu generieren, anstatt eine Instanz des Objekts zu klonen:

function createMyObject()
{
    var myObject =
    {
        ...
    };
    return myObject;
}

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

6

Ich habe meine eigene Implementierung geschrieben. Ich bin mir nicht sicher, ob es als bessere Lösung gilt:

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

Es folgt die Implementierung:

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;
}

Ich arbeite nicht für mein Objekt, obwohl mein Fall etwas komplex ist.
Sajuuk
Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.