Was ist der effizienteste Weg, um ein Objekt in JavaScript tief zu klonen?


5180

Was ist der effizienteste Weg, um ein JavaScript-Objekt zu klonen? Ich habe gesehen, obj = eval(uneval(o));dass es verwendet wird, aber das ist nicht Standard und wird nur von Firefox unterstützt .

Ich habe Dinge getan, obj = JSON.parse(JSON.stringify(o));aber die Effizienz in Frage gestellt.

Ich habe auch rekursive Kopierfunktionen mit verschiedenen Fehlern gesehen.
Ich bin überrascht, dass es keine kanonische Lösung gibt.


566
Eval ist nicht böse. Eval schlecht zu benutzen ist. Wenn Sie Angst vor den Nebenwirkungen haben, verwenden Sie es falsch. Die Nebenwirkungen, die Sie befürchten, sind die Gründe, es zu verwenden. Hat übrigens jemand Ihre Frage tatsächlich beantwortet?
James

15
Das Klonen von Objekten ist eine schwierige Angelegenheit, insbesondere bei benutzerdefinierten Objekten beliebiger Sammlungen. Was wahrscheinlich der Grund ist, warum es keinen sofort einsatzbereiten Weg gibt, dies zu tun.
b01

12
eval()Dieseval ist im Allgemeinen eine schlechte Idee, da viele Optimierer der Javascript-Engine deaktiviert werden müssen, wenn sie mit Variablen arbeiten, die über festgelegt werden . Nur eval()in Ihrem Code zu haben, kann zu einer schlechteren Leistung führen.
user56reinstatemonica8


12
Beachten Sie, dass die JSONMethode alle Javascript-Typen verliert, die in JSON keine Entsprechung haben. Zum Beispiel: JSON.parse(JSON.stringify({a:null,b:NaN,c:Infinity,d:undefined,e:function(){},f:Number,g:false}))generiert{a: null, b: null, c: null, g: false}
Oriadam

Antworten:


4731

Native Deep Cloning

Es heißt "strukturiertes Klonen", funktioniert experimentell in Knoten 11 und höher und wird hoffentlich in Browsern landen. Siehe diese Antwort für weitere Details.

Schnelles Klonen mit Datenverlust - JSON.parse / stringify

Wenn Sie nicht verwenden Dates, Funktionen undefined, Infinity, regexps, Karten, Sets, Blobs, Dateilisten, ImageDatas, Sparse - Matrizen, typisiert Arrays oder andere komplexe Typen in Ihrem Objekt, ein sehr einfacher Einzeiler zu tiefer Klon eines Objekt ist:

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'
  re: /.*/,  // lost
}
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()

Benchmarks finden Sie in Corbans Antwort .

Zuverlässiges Klonen mithilfe einer Bibliothek

Da das Klonen von Objekten nicht trivial ist (komplexe Typen, Zirkelverweise, Funktionen usw.), bieten die meisten Hauptbibliotheken Funktionen zum Klonen von Objekten. Erfinden Sie das Rad nicht neu. Wenn Sie bereits eine Bibliothek verwenden, überprüfen Sie, ob diese über eine Funktion zum Klonen von Objekten verfügt. Zum Beispiel,

ES6

Beachten Sie der Vollständigkeit halber, dass ES6 zwei flache Kopiermechanismen bietet: Object.assign()und die Spread-Syntax . Hiermit werden Werte aller aufzählbaren eigenen Eigenschaften von einem Objekt in ein anderes kopiert. Zum Beispiel:

var A1 = {a: "2"};
var A2 = Object.assign({}, A1);
var A3 = {...A1};  // Spread Syntax

7
@ThiefMaster github.com/jquery/jquery/blob/master/src/core.js in Zeile 276 (es gibt ein bisschen Code, der etwas anderes macht, aber der Code für "wie man das in JS macht" ist da :)
Rune FS

7
Hier ist der JS-Code hinter der tiefen Kopie von jQuery für alle Interessierten: github.com/jquery/jquery/blob/master/src/core.js#L265-327
Alex W

194
Woah! Um ganz klar zu sein: Keine Ahnung, warum diese Antwort als die richtige Antwort ausgewählt wurde. Dies war eine Antwort auf die unten angegebenen Antworten: stackoverflow.com/a/122190/6524 (was empfohlen wurde .clone(), was nicht der richtige Code ist Verwendung in diesem Zusammenhang). Leider hat diese Frage so viele Überarbeitungen durchlaufen, dass die ursprüngliche Diskussion nicht mehr sichtbar ist! Befolgen Sie einfach die Anweisungen von Corban und schreiben Sie eine Schleife oder kopieren Sie die Eigenschaften direkt in ein neues Objekt, wenn Sie Wert auf Geschwindigkeit legen. Oder testen Sie es selbst!
John Resig

9
Dies ist eine JavaScript-Frage (keine Erwähnung von jQuery).
Gphilip

60
Wie würde man das ohne jQuery machen?
Awesomeness01

2265

Überprüfen Sie diesen Benchmark: http://jsben.ch/#/bWfk9

In meinen vorherigen Tests, bei denen Geschwindigkeit ein Hauptanliegen war, fand ich

JSON.parse(JSON.stringify(obj))

Dies ist der langsamste Weg, um ein Objekt tief zu klonen (es ist langsamer als jQuery.extend, wobei das deepFlag um 10-20% auf true gesetzt ist).

jQuery.extend ist ziemlich schnell, wenn das deepFlag auf false(flacher Klon) gesetzt ist. Dies ist eine gute Option, da sie eine zusätzliche Logik für die Typüberprüfung enthält und nicht über undefinierte Eigenschaften usw. kopiert. Dies verlangsamt Sie jedoch auch ein wenig.

Wenn Sie die Struktur der Objekte kennen, die Sie klonen for (var i in obj)möchten, oder tief verschachtelte Arrays vermeiden können, können Sie eine einfache Schleife schreiben , um Ihr Objekt zu klonen, während Sie hasOwnProperty überprüfen. Diese ist viel schneller als jQuery.

Wenn Sie versuchen, eine bekannte Objektstruktur in einer Hot-Loop zu klonen, können Sie VIEL MEHR LEISTUNG erzielen, indem Sie einfach die Klonprozedur einbinden und das Objekt manuell erstellen.

JavaScript-Trace-Engines sind nicht in der for..inLage, Schleifen zu optimieren , und die Überprüfung von hasOwnProperty verlangsamt Sie ebenfalls. Manuelles Klonen, wenn Geschwindigkeit ein absolutes Muss ist.

var clonedObject = {
  knownProp: obj.knownProp,
  ..
}

Vorsicht bei der Verwendung der JSON.parse(JSON.stringify(obj))Methode für DateObjekte - JSON.stringify(new Date())Gibt eine Zeichenfolgendarstellung des Datums im ISO-Format zurück, die JSON.parse() nicht zurück in ein DateObjekt konvertiert wird. Siehe diese Antwort für weitere Details .

Beachten Sie außerdem, dass zumindest in Chrome 65 das native Klonen nicht der richtige Weg ist. Laut JSPerf ist das native Klonen durch Erstellen einer neuen Funktion fast 800-mal langsamer als die Verwendung von JSON.stringify, das auf der ganzen Linie unglaublich schnell ist.

Update für ES6

Wenn Sie Javascript ES6 verwenden, versuchen Sie diese native Methode zum Klonen oder für flache Kopien.

Object.assign({}, obj);

4
@trysis Object.create klont das Objekt nicht, verwendet das Prototyp-Objekt ... jsfiddle.net/rahpuser/yufzc1jt/2
rahpuser

105
Diese Methode entfernt auch die keysaus Ihren object, die functionsals Werte haben, da die JSONkeine Funktionen unterstützt.
Karlen Kishmiryan

39
Beachten SieJSON.parse(JSON.stringify(obj)) auch, dass bei Verwendung von On-Date-Objekten das Datum in der Zeichenfolgendarstellung im ISO8601- Format auch wieder in UTC konvertiert wird.
dnlgmzddr

31
Der JSON-Ansatz drosselt auch Zirkelverweise.
Rich Remer

28
@velop, Object.assign ({}, objToClone) scheint jedoch ein flacher Klon zu sein. Beim Herumspielen in der Dev Tools-Konsole zeigte der Objektklon immer noch auf eine Referenz des geklonten Objekts. Ich denke also nicht, dass es hier wirklich anwendbar ist.
Garrett Simpson

473

Angenommen, Sie haben nur Variablen und keine Funktionen in Ihrem Objekt, können Sie einfach Folgendes verwenden:

var newObject = JSON.parse(JSON.stringify(oldObject));

86
Der Nachteil dieses Ansatzes ist, wie ich gerade herausgefunden habe, dass wenn Ihr Objekt irgendwelche Funktionen hat (meins hat interne Getter und Setter), diese beim Stringifizieren verloren gehen. Wenn das alles ist, was Sie brauchen, ist diese Methode in Ordnung.
Markive

31
@ Jason, Der Grund, warum diese Methode langsamer ist als das flache Kopieren (auf einem tiefen Objekt), ist, dass diese Methode per Definition tiefe Kopien ist. Da dies JSONjedoch in nativem Code implementiert ist (in den meisten Browsern), ist dies erheblich schneller als die Verwendung einer anderen Javascript-basierten Deep-Copying-Lösung und manchmal auch schneller als eine Javascript-basierte Flachkopiertechnik (siehe: jsperf.com/cloning) -an-Objekt / 79 ).
MiJyn

35
JSON.stringify({key: undefined}) //=> "{}"
Web_Designer

32
Diese Technik zerstört auch alle DateObjekte, die im Objekt gespeichert sind, und konvertiert sie in eine Zeichenfolgenform.
fstab

13
Es wird nichts kopieren können, was nicht Teil der JSON-Spezifikation ( json.org ) ist
cdmckay

397

Strukturiertes Klonen

Der HTML-Standard enthält einen internen strukturierten Klon- / Serialisierungsalgorithmus , mit dem tiefe Klone von Objekten erstellt werden können. Es ist immer noch auf bestimmte integrierte Typen beschränkt, unterstützt aber zusätzlich zu den wenigen von JSON unterstützten Typen auch Dates, RegExps, Maps, Sets, Blobs, Dateilisten, ImageDatas, spärliche Arrays, typisierte Arrays und wahrscheinlich auch in Zukunft . Außerdem werden Verweise in den geklonten Daten beibehalten, sodass zyklische und rekursive Strukturen unterstützt werden können, die Fehler für JSON verursachen würden.

Unterstützung in Node.js: Experimentell 🙂

Das v8Modul in Node.js stellt derzeit (ab Knoten 11) die strukturierte Serialisierungs-API direkt zur Verfügung . Diese Funktionalität ist jedoch weiterhin als "experimentell" gekennzeichnet und kann in zukünftigen Versionen geändert oder entfernt werden. Wenn Sie eine kompatible Version verwenden, ist das Klonen eines Objekts so einfach wie:

const v8 = require('v8');

const structuredClone = obj => {
  return v8.deserialize(v8.serialize(obj));
};

Direkter Support in Browsern: Vielleicht irgendwann? 😐

Browser bieten derzeit keine direkte Schnittstelle für den strukturierten Klonalgorithmus , aber eine globale structuredClone()Funktion wurde in whatwg / html # 793 auf GitHub erläutert . Wie derzeit vorgeschlagen, wäre die Verwendung für die meisten Zwecke so einfach wie:

const clone = structuredClone(original);

Sofern dies nicht ausgeliefert wird, werden die strukturierten Klonimplementierungen von Browsern nur indirekt verfügbar gemacht.

Asynchrone Problemumgehung: Verwendbar. 😕

Der kostengünstigere Weg, einen strukturierten Klon mit vorhandenen APIs zu erstellen, besteht darin, die Daten über einen Port eines MessageChannels zu senden . Der andere Port gibt ein messageEreignis mit einem strukturierten Klon des angehängten aus .data. Leider ist das Abhören dieser Ereignisse notwendigerweise asynchron, und die synchronen Alternativen sind weniger praktisch.

class StructuredCloner {
  constructor() {
    this.pendingClones_ = new Map();
    this.nextKey_ = 0;

    const channel = new MessageChannel();
    this.inPort_ = channel.port1;
    this.outPort_ = channel.port2;

    this.outPort_.onmessage = ({data: {key, value}}) => {
      const resolve = this.pendingClones_.get(key);
      resolve(value);
      this.pendingClones_.delete(key);
    };
    this.outPort_.start();
  }

  cloneAsync(value) {
    return new Promise(resolve => {
      const key = this.nextKey_++;
      this.pendingClones_.set(key, resolve);
      this.inPort_.postMessage({key, value});
    });
  }
}

const structuredCloneAsync = window.structuredCloneAsync =
    StructuredCloner.prototype.cloneAsync.bind(new StructuredCloner);

Beispiel Verwendung:

const main = async () => {
  const original = { date: new Date(), number: Math.random() };
  original.self = original;

  const clone = await structuredCloneAsync(original);

  // They're different objects:
  console.assert(original !== clone);
  console.assert(original.date !== clone.date);

  // They're cyclical:
  console.assert(original.self === original);
  console.assert(clone.self === clone);

  // They contain equivalent values:
  console.assert(original.number === clone.number);
  console.assert(Number(original.date) === Number(clone.date));

  console.log("Assertions complete.");
};

main();

Synchrone Problemumgehungen: Schrecklich! 🤢

Es gibt keine guten Optionen zum synchronen Erstellen strukturierter Klone. Hier sind stattdessen ein paar unpraktische Hacks.

history.pushState()und history.replaceState()beide erstellen einen strukturierten Klon ihres ersten Arguments und weisen diesen Wert zu history.state. Sie können dies verwenden, um einen strukturierten Klon eines Objekts wie dieses zu erstellen:

const structuredClone = obj => {
  const oldState = history.state;
  history.replaceState(obj, null);
  const clonedObj = history.state;
  history.replaceState(oldState, null);
  return clonedObj;
};

Beispiel Verwendung:

Obwohl synchron, kann dies extrem langsam sein. Es entsteht der gesamte Aufwand für die Bearbeitung des Browserverlaufs. Das wiederholte Aufrufen dieser Methode kann dazu führen, dass Chrome vorübergehend nicht mehr reagiert.

Der NotificationKonstruktor erstellt einen strukturierten Klon der zugehörigen Daten. Es wird auch versucht, dem Benutzer eine Browserbenachrichtigung anzuzeigen. Dies schlägt jedoch unbemerkt fehl, es sei denn, Sie haben eine Benachrichtigungsberechtigung angefordert. Falls Sie die Erlaubnis für andere Zwecke haben, schließen wir die von uns erstellte Benachrichtigung sofort.

const structuredClone = obj => {
  const n = new Notification('', {data: obj, silent: true});
  n.onshow = n.close.bind(n);
  return n.data;
};

Beispiel Verwendung:


3
@rynah Ich habe gerade noch einmal die Spezifikation durchgesehen und Sie haben Recht: Die Methoden history.pushState()und setzen history.replaceState()beide synchron history.stateauf einen strukturierten Klon ihres ersten Arguments. Ein bisschen komisch, aber es funktioniert. Ich aktualisiere jetzt meine Antwort.
Jeremy Banks

40
Das ist einfach so falsch! Diese API soll nicht auf diese Weise verwendet werden.
Fardin K.

209
Als der Typ, der pushState in Firefox implementiert hat, fühle ich eine seltsame Mischung aus Stolz und Abscheu bei diesem Hack. Gut gemacht Jungs.
Justin L.

PushState oder Notification Hack funktioniert nicht für einige Objekttypen wie Function
Shishir Arora

323

Wenn es keine eingebaute gäbe, könnten Sie versuchen:

function clone(obj) {
    if (obj === null || typeof (obj) !== 'object' || 'isActiveClone' in obj)
        return obj;

    if (obj instanceof Date)
        var temp = new obj.constructor(); //or new Date(obj);
    else
        var temp = obj.constructor();

    for (var key in obj) {
        if (Object.prototype.hasOwnProperty.call(obj, key)) {
            obj['isActiveClone'] = null;
            temp[key] = clone(obj[key]);
            delete obj['isActiveClone'];
        }
    }
    return temp;
}

20
Die JQuery-Lösung funktioniert für DOM-Elemente, jedoch nicht für jedes Objekt. Mootools hat das gleiche Limit. Ich wünschte, sie hätten einen generischen "Klon" für nur jedes Objekt ... Die rekursive Lösung sollte für alles funktionieren. Es ist wahrscheinlich der richtige Weg.
Jschrab

5
Diese Funktion wird unterbrochen, wenn das zu klonende Objekt einen Konstruktor hat, für den Parameter erforderlich sind. Es scheint, als könnten wir es in "var temp = new Object ()" ändern und es in jedem Fall funktionieren lassen, nein?
Andrew Arnott

3
Andrew, wenn Sie es in var temp = new Object () ändern, hat Ihr Klon nicht den gleichen Prototyp wie das ursprüngliche Objekt. Versuchen Sie Folgendes: 'var newProto = function () {}; newProto.prototype = obj.constructor; var temp = new newProto (); '
Limscoder

1
Ähnlich wie bei Limscoder, siehe meine Antwort unten, wie dies ohne Aufruf des Konstruktors zu tun ist: stackoverflow.com/a/13333781/560114
Matt Browne

3
Bei Objekten, die Verweise auf Unterteile enthalten (dh Netzwerke von Objekten), funktioniert dies nicht: Wenn zwei Verweise auf dasselbe Unterobjekt verweisen, enthält die Kopie zwei verschiedene Kopien davon. Und wenn es rekursive Referenzen gibt, wird die Funktion niemals beendet (zumindest nicht so, wie Sie es möchten :-) In diesen allgemeinen Fällen müssen Sie ein Wörterbuch mit bereits kopierten Objekten hinzufügen und prüfen, ob Sie es bereits kopiert haben ... Die Programmierung ist komplex, wenn Sie eine einfache Sprache verwenden
virtualnobi

153

Der effiziente Weg, ein Objekt in einer Codezeile zu klonen (nicht tief 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;
    }
  });
}

82
Dies wird nicht rekursiv kopiert und bietet daher keine Lösung für das Problem des Klonens eines Objekts.
Weiß

5
Diese Methode hat funktioniert, obwohl ich einige getestet habe und _.extend ({}, (obj)) bei weitem am schnellsten war: 20x schneller als JSON.parse und 60% schneller als Object.assign. Es kopiert alle Unterobjekte recht gut.
Nico

11
@mwhite gibt es einen Unterschied zwischen Klon und Deep-Clone. Diese Antwort klont zwar, aber sie klont nicht tief.
Meirion Hughes

57
Die Operation bat um einen tiefen Klon. Dies macht keinen tiefen Klon.
user566245

9
Diese Methode erstellt eine SHALLOW-Kopie und keine DEEP-Kopie ! Aus diesem Grund ist es eine völlig falsche Antwort !
Bharata

97

Code:

// extends 'from' object with members from 'to'. If 'to' is null, a deep clone of 'from' is returned
function extend(from, to)
{
    if (from == null || typeof from != "object") return from;
    if (from.constructor != Object && from.constructor != Array) return from;
    if (from.constructor == Date || from.constructor == RegExp || from.constructor == Function ||
        from.constructor == String || from.constructor == Number || from.constructor == Boolean)
        return new from.constructor(from);

    to = to || new from.constructor();

    for (var name in from)
    {
        to[name] = typeof to[name] == "undefined" ? extend(from[name], null) : to[name];
    }

    return to;
}

Prüfung:

var obj =
{
    date: new Date(),
    func: function(q) { return 1 + q; },
    num: 123,
    text: "asdasd",
    array: [1, "asd"],
    regex: new RegExp(/aaa/i),
    subobj:
    {
        num: 234,
        text: "asdsaD"
    }
}

var clone = extend(obj);

3
was ist mit var obj = {}undobj.a = obj
neaumusic

5
Ich verstehe diese Funktion nicht. Angenommen from.constructorist Datezum Beispiel. Wie würde der dritte ifTest erreicht werden, wenn der zweite ifTest erfolgreich wäre und die Funktion (seit Date != Object && Date != Array) zurückkehren würde?
Adam McKee

1
@AdamMcKee Weil das Übergeben von Javascript-Argumenten und die Zuweisung von Variablen schwierig ist . Dieser Ansatz funktioniert hervorragend, einschließlich der Daten (die tatsächlich vom zweiten Test behandelt werden) - Geige zum Testen hier: jsfiddle.net/zqv9q9c6 .
Brichins

1
@ NickSweeting: Versuchen Sie - vielleicht funktioniert es. Wenn nicht - beheben Sie es und aktualisieren Sie die Antwort. So funktioniert es hier in der Community :)
Kamarey

1
Diese Funktion klont den regulären Ausdruck im Test nicht. Die Bedingung "from.constructor! = Object && from.constructor! = Array" gibt für andere Konstruktoren wie Number, Date usw. immer true zurück.
aMarCruz

95

Das benutze ich:

function cloneObject(obj) {
    var clone = {};
    for(var i in obj) {
        if(typeof(obj[i])=="object" && obj[i] != null)
            clone[i] = cloneObject(obj[i]);
        else
            clone[i] = obj[i];
    }
    return clone;
}

8
Das scheint nicht richtig zu sein. cloneObject({ name: null })=>{"name":{}}
Niyaz

13
Dies ist auf eine andere dumme Sache in Javascript zurückzuführen, typeof null > "object"aber Object.keys(null) > TypeError: Requested keys of a value that is not an object.ändern Sie die Bedingung inif(typeof(obj[i])=="object" && obj[i]!=null)
Vitim.us

Dadurch werden dem Klon geerbte aufzählbare Eigenschaften von obj direkt zugewiesen , und es wird davon ausgegangen, dass obj ein einfaches Objekt ist.
RobG

Dies bringt auch Arrays durcheinander, die mit Zifferntasten in Objekte konvertiert werden.
Klinge

Kein Problem, wenn Sie nicht null verwenden.
Jorge Bucaran

78

Tiefe Kopie nach Leistung: Vom Besten zum Schlechtesten

  • Neuzuweisung "=" (String-Arrays, Zahlen-Arrays - nur)
  • Slice (String-Arrays, nur Zahlen-Arrays)
  • Verkettung (String-Arrays, nur Zahlen-Arrays)
  • Benutzerdefinierte Funktion: For-Loop oder rekursive Kopie
  • jQuery's $ .extend
  • JSON.parse (String-Arrays, Zahlen-Arrays, Objekt-Arrays - nur)
  • _.Clone von Underscore.js (nur String-Arrays, Zahlen-Arrays)
  • _.CloneDeep von Lo-Dash

Kopieren Sie ein Array von Zeichenfolgen oder Zahlen tief (eine Ebene - keine Referenzzeiger):

Wenn ein Array Zahlen und Zeichenfolgen enthält - Funktionen wie .slice (), .concat (), .splice (), der Zuweisungsoperator "=" und die Klonfunktion von Underscore.js; erstellt eine tiefe Kopie der Elemente des Arrays.

Wo Neuzuweisung die schnellste Leistung hat:

var arr1 = ['a', 'b', 'c'];
var arr2 = arr1;
arr1 = ['a', 'b', 'c'];

Und .slice () hat eine bessere Leistung als .concat (), http://jsperf.com/duplicate-array-slice-vs-concat/3

var arr1 = ['a', 'b', 'c'];  // Becomes arr1 = ['a', 'b', 'c']
var arr2a = arr1.slice(0);   // Becomes arr2a = ['a', 'b', 'c'] - deep copy
var arr2b = arr1.concat();   // Becomes arr2b = ['a', 'b', 'c'] - deep copy

Kopieren Sie ein Array von Objekten tief (zwei oder mehr Ebenen - Referenzzeiger):

var arr1 = [{object:'a'}, {object:'b'}];

Schreiben Sie eine benutzerdefinierte Funktion (hat eine schnellere Leistung als $ .extend () oder JSON.parse):

function copy(o) {
   var out, v, key;
   out = Array.isArray(o) ? [] : {};
   for (key in o) {
       v = o[key];
       out[key] = (typeof v === "object" && v !== null) ? copy(v) : v;
   }
   return out;
}

copy(arr1);

Verwenden Sie Dienstprogrammfunktionen von Drittanbietern:

$.extend(true, [], arr1); // Jquery Extend
JSON.parse(arr1);
_.cloneDeep(arr1); // Lo-dash

Wo jQuerys $ .extend eine bessere Leistung hat:


Ich habe einige getestet und _.extend ({}, (obj)) war bei weitem am schnellsten: 20x schneller als JSON.parse und 60% schneller als Object.assign zum Beispiel. Es kopiert alle Unterobjekte recht gut.
Nico

4
Alle Ihre Beispiele sind flach, eine Ebene. Dies ist keine gute Antwort. Die Frage betraf das tiefe Klonen, dh mindestens zwei Ebenen.
Karl Morrison

1
Eine tiefe Kopie liegt vor, wenn ein Objekt vollständig ohne Verwendung von Referenzzeigern auf andere Objekte kopiert wird. Die Techniken unter dem Abschnitt "Deep Copy eines Arrays von Objekten", wie z. B. jQuery.extend () und die benutzerdefinierte Funktion (die rekursiv ist), kopieren Objekte mit "mindestens zwei Ebenen". Nein, nicht alle Beispiele sind "einstufige" Kopien.
tfmontague

1
Ich mag Ihre benutzerdefinierte Kopierfunktion, aber Sie sollten Nullwerte ausschließen, sonst werden alle Nullwerte in Objekte konvertiert, dh:out[key] = (typeof v === "object" && v !== null) ? copy(v) : v;
Josi

2
@HossamMourad - Der Fehler wurde von Josi am 1. Februar behoben (im obigen Kommentar), und ich konnte die Antwort nicht korrekt aktualisieren. Entschuldigung, dass dieser Fehler zu einem Refactor Ihrer Codebasis geführt hat.
tfmontague

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

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

Gute Antwort, aber dies schlägt bei Zirkelverweisen fehl.
Luke

59

Tiefes Kopieren von Objekten in JavaScript (ich denke das Beste und das Einfachste)

1. Verwenden von JSON.parse (JSON.stringify (Objekt));

var obj = { 
  a: 1,
  b: { 
    c: 2
  }
}
var newObj = JSON.parse(JSON.stringify(obj));
obj.b.c = 20;
console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 2 } } 

2. Mit der erstellten Methode

function cloneObject(obj) {
    var clone = {};
    for(var i in obj) {
        if(obj[i] != null &&  typeof(obj[i])=="object")
            clone[i] = cloneObject(obj[i]);
        else
            clone[i] = obj[i];
    }
    return clone;
}

var obj = { 
  a: 1,
  b: { 
    c: 2
  }
}
var newObj = cloneObject(obj);
obj.b.c = 20;

console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 2 } } 

3. Verwenden des _.cloneDeep- Link- Lodash von Lo-Dash

var obj = { 
  a: 1,
  b: { 
    c: 2
  }
}

var newObj = _.cloneDeep(obj);
obj.b.c = 20;
console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 2 } } 

4. Verwenden der Object.assign () -Methode

var obj = { 
  a: 1,
  b: 2
}

var newObj = _.clone(obj);
obj.b = 20;
console.log(obj); // { a: 1, b: 20 }
console.log(newObj); // { a: 1, b: 2 }  

ABER FALSCH WANN

var obj = { 
  a: 1,
  b: { 
    c: 2
  }
}

var newObj = Object.assign({}, obj);
obj.b.c = 20;
console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 20 } } --> WRONG
// Note: Properties on the prototype chain and non-enumerable properties cannot be copied.

5.Verwenden von Underscore.js _.clone link Underscore.js

var obj = { 
  a: 1,
  b: 2
}

var newObj = _.clone(obj);
obj.b = 20;
console.log(obj); // { a: 1, b: 20 }
console.log(newObj); // { a: 1, b: 2 }  

ABER FALSCH WANN

var obj = { 
  a: 1,
  b: { 
    c: 2
  }
}

var newObj = _.cloneDeep(obj);
obj.b.c = 20;
console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 20 } } --> WRONG
// (Create a shallow-copied clone of the provided plain object. Any nested objects or arrays will be copied by reference, not duplicated.)

JSBEN.CH Performance Benchmarking Playground 1 ~ 3 http://jsben.ch/KVQLd Leistung Tiefes Kopieren von Objekten in JavaScript


5
Object.assign()führt keine tiefe Kopie durch
Roymunson

1
Sie sollten Benchmarks für diese hinzufügen. das wäre sehr hilfreich
jcollum

Als ich die "erstellte Methode" für ein Objekt verwendet habe, das ein Array enthält, konnte ich nicht pop () oder splice () verwenden. Ich verstehe nicht warum? let data = {title:["one", "two"]}; let tmp = cloneObject(data); tmp.title.pop();es wirft: TypeError: tmp.title.pop is not a function(natürlich pop () funktioniert gut, wenn ich nur do let tmp = data; aber dann kann ich tmp nicht ändern, ohne Daten zu beeinflussen)
hugogogo

Hey, dein letztes Beispiel ist falsch. Meiner Meinung nach müssen Sie _clone und nicht _cloneDeep für das falsche Beispiel verwenden.
Kenanyildiz

Diese erstellte Methode (2.) funktioniert nicht für Arrays, oder?
Toivo Säwén

57

Es gibt eine Bibliothek ("Klon" genannt) , die das ganz gut macht. Es bietet das vollständigste rekursive Klonen / Kopieren von beliebigen Objekten, die mir bekannt sind. Es werden auch Zirkelverweise unterstützt, die in den anderen Antworten noch nicht behandelt werden.

Sie finden es auch auf npm . Es kann sowohl für den Browser als auch für Node.js verwendet werden.

Hier ist ein Beispiel für die Verwendung:

Installieren Sie es mit

npm install clone

oder verpacken Sie es mit Ender .

ender build clone [...]

Sie können den Quellcode auch manuell herunterladen.

Dann können Sie es in Ihrem Quellcode verwenden.

var clone = require('clone');

var a = { foo: { bar: 'baz' } };  // inital value of a
var b = clone(a);                 // clone a -> b
a.foo.bar = 'foo';                // change a

console.log(a);                   // { foo: { bar: 'foo' } }
console.log(b);                   // { foo: { bar: 'baz' } }

(Haftungsausschluss: Ich bin der Autor der Bibliothek.)


3
Der npm-Klon war für mich von unschätzbarem Wert, um willkürlich verschachtelte Objekte zu klonen. Das ist die richtige Antwort.
Andy Ray

Was ist die Leistung Ihrer Bibliothek im Vergleich zu sagen wir JSON.parse(JSON.stringify(obj))?
Pkyeck

Hier ist eine Bibliothek , die angibt, dass es schnellere Optionen gibt. Habe aber nicht getestet.
pvorb

Gute Lösung und dies unterstützt Zirkelverweise (im Gegensatz zu JSON-Analyse)
Luke

55

Cloning Ein Objekt war in JS immer ein Problem, aber es ging um ES6. Ich liste unten verschiedene Möglichkeiten zum Kopieren eines Objekts in JavaScript auf. Stellen Sie sich vor, Sie haben das Objekt unten und möchten eine ausführliche Kopie davon haben:

var obj = {a:1, b:2, c:3, d:4};

Es gibt nur wenige Möglichkeiten, dieses Objekt zu kopieren, ohne den Ursprung zu ändern:

1) ES5 +, Verwenden einer einfachen Funktion, um die Kopie für Sie zu erstellen:

function deepCopyObj(obj) {
    if (null == obj || "object" != typeof obj) return obj;
    if (obj instanceof Date) {
        var copy = new Date();
        copy.setTime(obj.getTime());
        return copy;
    }
    if (obj instanceof Array) {
        var copy = [];
        for (var i = 0, len = obj.length; i < len; i++) {
            copy[i] = cloneSO(obj[i]);
        }
        return copy;
    }
    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 this object.");
}

2) ES5 + mit JSON.parse und JSON.stringify.

var  deepCopyObj = JSON.parse(JSON.stringify(obj));

3) AngularJs:

var  deepCopyObj = angular.copy(obj);

4) jQuery:

var deepCopyObj = jQuery.extend(true, {}, obj);

5) Unterstriche & Loadash:

var deepCopyObj = _.cloneDeep(obj); //latest version UndescoreJs makes shallow copy

Hoffe diese Hilfe ...


2
Klon in Unterstrich ist kein tiefer Klon in der aktuellen Version
Rogelio

Vielen Dank. yes as new doc for Underscore ... clone_.clone (Objekt) Erstellt einen flach kopierten Klon des bereitgestellten einfachen Objekts. Verschachtelte Objekte oder Arrays werden als Referenz kopiert und nicht dupliziert. _.clone ({name: 'moe'}); => {name: 'moe'};
Alireza

59
Object.assignist nicht tief kopieren. Beispiel : var x = { a: { b: "c" } }; var y = Object.assign({}, x); x.a.b = "d". Wenn dies eine tiefe Kopie y.a.bwäre , wäre es immer noch c, aber es ist jetzt d.
kba

8
Object.assign () klont nur die erste Eigenschaftsebene!
Haemse

5
Was ist die Funktion cloneSO ()?
Pastorello

53

Ich weiß, dass dies ein alter Beitrag ist, aber ich dachte, dass dies für die nächste Person, die mit stolpert, hilfreich sein könnte.

Solange Sie nichts einem Objekt zuweisen, behält es keine Referenz im Speicher. Um ein Objekt zu erstellen, das Sie mit anderen Objekten teilen möchten, müssen Sie eine Fabrik wie folgt erstellen:

var a = function(){
    return {
        father:'zacharias'
    };
},
b = a(),
c = a();
c.father = 'johndoe';
alert(b.father);

16
Diese Antwort ist nicht wirklich relevant, da die Frage lautet: gegebene Instanz b wie erstellt man eine Kopie c, während man nicht über Fabrik a Bescheid weiß oder Fabrik a nicht verwenden möchte. Der Grund, warum man die Factory möglicherweise nicht verwenden möchte, ist, dass b nach der Instanziierung möglicherweise mit zusätzlichen Daten (z. B. Benutzereingaben) initialisiert wurde.
Noel Abrahams

12
Es ist wahr, dass dies nicht wirklich eine Antwort auf die Frage ist, aber ich denke, es ist wichtig, dass es hier ist, weil es die Antwort auf die Frage ist, von der ich vermute, dass viele der Leute, die hierher kommen, wirklich vorhaben, sie zu stellen.
Semikolon

8
Sorry Leute, ich verstehe nicht wirklich warum so viele Upvotes. Das Klonen eines Objekts ist ein ziemlich klares Konzept. Sie kegeln ein Objekt aus einem anderen Objekt und es hat nicht viel damit zu tun, ein neues Objekt mit dem Factory-Muster zu erstellen.
öffnet

2
Während dies für vordefinierte Objekte funktioniert, erkennt das "Klonen" auf diese Weise keine neuen Eigenschaften, die dem ursprünglichen Objekt hinzugefügt wurden. Wenn Sie a erstellen, fügen Sie a eine neue Eigenschaft hinzu, und erstellen Sie dann b. b wird die neue Eigenschaft nicht haben. Im Wesentlichen ist das Fabrikmuster für neue Eigenschaften unveränderlich. Dies ist kein paradigmatisches Klonen. Siehe: jsfiddle.net/jzumbrun/42xejnbx
Jon

1
Ich denke, dies ist im Allgemeinen ein guter Rat, da Sie anstatt zu verwenden const defaultFoo = { a: { b: 123 } };gehen können const defaultFoo = () => ({ a: { b: 123 } };und Ihr Problem gelöst ist. Es ist jedoch wirklich keine Antwort auf die Frage. Als Kommentar zu der Frage hätte es vielleicht mehr Sinn gemacht, als als vollständige Antwort.
Josh aus Qaribou

48

Wenn Sie es verwenden, verfügt die Bibliothek Underscore.js über eine Klonmethode .

var newObject = _.clone(oldObject);

24
lodash hat eine cloneDeep-Methode, es unterstützt auch einen anderen Parameter zum Klonen, um es tief zu machen: lodash.com/docs#clone und lodash.com/docs#cloneDeep
öffnet am

12
@opensas stimmte zu. Lodash ist im Allgemeinen dem Unterstrich überlegen
nha

7
Ich empfehle, diese und alle anderen Antworten zu löschen, die nur einzeilige Verweise auf die .clone(...)Methode einer Dienstprogrammbibliothek sind . Jede größere Bibliothek wird sie haben, und die wiederholten kurzen, nicht detaillierten Antworten sind für die meisten Besucher, die diese bestimmte Bibliothek nicht verwenden, nicht nützlich.
Jeremy Banks

41

Hier ist eine Version der obigen Antwort von ConroyP, die auch dann funktioniert, wenn der Konstruktor Parameter benötigt:

//If Object.create isn't already defined, we just do the simple shim,
//without the second argument, since that's all we need here
var object_create = Object.create;
if (typeof object_create !== 'function') {
    object_create = function(o) {
        function F() {}
        F.prototype = o;
        return new F();
    };
}

function deepCopy(obj) {
    if(obj == null || typeof(obj) !== 'object'){
        return obj;
    }
    //make sure the returned object has the same prototype as the original
    var ret = object_create(obj.constructor.prototype);
    for(var key in obj){
        ret[key] = deepCopy(obj[key]);
    }
    return ret;
}

Diese Funktion ist auch in meiner simpleoo- Bibliothek verfügbar .

Bearbeiten:

Hier ist eine robustere Version (dank Justin McCandless unterstützt dies jetzt auch zyklische Referenzen):

/**
 * Deep copy an object (make copies of all its object properties, sub-properties, etc.)
 * An improved version of http://keithdevens.com/weblog/archive/2007/Jun/07/javascript.clone
 * that doesn't break if the constructor has required parameters
 * 
 * It also borrows some code from http://stackoverflow.com/a/11621004/560114
 */ 
function deepCopy(src, /* INTERNAL */ _visited, _copiesVisited) {
    if(src === null || typeof(src) !== 'object'){
        return src;
    }

    //Honor native/custom clone methods
    if(typeof src.clone == 'function'){
        return src.clone(true);
    }

    //Special cases:
    //Date
    if(src instanceof Date){
        return new Date(src.getTime());
    }
    //RegExp
    if(src instanceof RegExp){
        return new RegExp(src);
    }
    //DOM Element
    if(src.nodeType && typeof src.cloneNode == 'function'){
        return src.cloneNode(true);
    }

    // Initialize the visited objects arrays if needed.
    // This is used to detect cyclic references.
    if (_visited === undefined){
        _visited = [];
        _copiesVisited = [];
    }

    // Check if this object has already been visited
    var i, len = _visited.length;
    for (i = 0; i < len; i++) {
        // If so, get the copy we already made
        if (src === _visited[i]) {
            return _copiesVisited[i];
        }
    }

    //Array
    if (Object.prototype.toString.call(src) == '[object Array]') {
        //[].slice() by itself would soft clone
        var ret = src.slice();

        //add it to the visited array
        _visited.push(src);
        _copiesVisited.push(ret);

        var i = ret.length;
        while (i--) {
            ret[i] = deepCopy(ret[i], _visited, _copiesVisited);
        }
        return ret;
    }

    //If we've reached here, we have a regular object

    //make sure the returned object has the same prototype as the original
    var proto = (Object.getPrototypeOf ? Object.getPrototypeOf(src): src.__proto__);
    if (!proto) {
        proto = src.constructor.prototype; //this line would probably only be reached by very old browsers 
    }
    var dest = object_create(proto);

    //add this object to the visited array
    _visited.push(src);
    _copiesVisited.push(dest);

    for (var key in src) {
        //Note: this does NOT preserve ES5 property attributes like 'writable', 'enumerable', etc.
        //For an example of how this could be modified to do so, see the singleMixin() function
        dest[key] = deepCopy(src[key], _visited, _copiesVisited);
    }
    return dest;
}

//If Object.create isn't already defined, we just do the simple shim,
//without the second argument, since that's all we need here
var object_create = Object.create;
if (typeof object_create !== 'function') {
    object_create = function(o) {
        function F() {}
        F.prototype = o;
        return new F();
    };
}

30

Im Folgenden werden zwei Instanzen desselben Objekts erstellt. Ich habe es gefunden und benutze es derzeit. Es ist einfach und leicht zu bedienen.

var objToCreate = JSON.parse(JSON.stringify(cloneThis));

Stimmt etwas mit dieser Antwort nicht? Es ist nützlicher als eine eigenständige Lösung, aber einfach. Die jQuery-Lösung ist jedoch beliebter. Warum ist das so?
Zeremonie

Ja, bitte lass es mich wissen. Es scheint wie beabsichtigt zu funktionieren. Wenn irgendwo ein versteckter Bruch vorliegt, muss ich eine andere Lösung verwenden.
Nathan Rogers

4
Für ein einfaches Objekt ist dies in Chrome etwa sechsmal langsamer als die angegebene Antwort und wird mit zunehmender Komplexität des Objekts viel langsamer. Es skaliert furchtbar und kann Ihre Anwendung sehr schnell zum Engpass bringen.
Tic

1
Sie brauchen keine Daten, nur ein Verständnis dafür, was los ist. Diese Klontechnik serialisiert das gesamte Objekt in eine Zeichenfolge und analysiert dann diese Zeichenfolgenserialisierung, um ein Objekt zu erstellen. Inhärent wird das nur viel langsamer sein, als nur etwas Speicher neu anzuordnen (was die anspruchsvolleren Klone tun). Aber wenn das gesagt ist, wen interessiert es für kleine bis mittlere Projekte (abhängig von Ihrer Definition von "mittelgroß"), ob es sogar 1000x weniger effizient ist? Wenn Ihre Objekte klein sind und Sie sie nicht klonen, ist eine Tonne 1000x von praktisch nichts immer noch praktisch nichts.
Machineghost

3
Außerdem verliert diese Methode Methoden (oder alles, was in JSON nicht erlaubt ist). Außerdem - JSON.stringify konvertiert Date-Objekte in Strings, ... und nicht umgekehrt;) Halten Sie sich von dieser Lösung fern.
Herr MT

22

Crockford schlägt vor (und ich bevorzuge), diese Funktion zu verwenden:

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

var newObject = object(oldObject);

Es ist knapp, funktioniert wie erwartet und Sie brauchen keine Bibliothek.


BEARBEITEN:

Dies ist eine Polyfüllung für Object.create, sodass Sie diese auch verwenden können.

var newObject = Object.create(oldObject);

HINWEIS: Wenn Sie einige davon verwenden, haben Sie möglicherweise Probleme mit einigen Iterationen, die Sie verwenden hasOwnProperty. Denn createneues leeres Objekt erstellen , die erben oldObject. Es ist jedoch immer noch nützlich und praktisch zum Klonen von Objekten.

Zum Beispiel wenn oldObject.a = 5;

newObject.a; // is 5

aber:

oldObject.hasOwnProperty(a); // is true
newObject.hasOwnProperty(a); // is false

9
Korrigieren Sie mich, wenn ich falsch liege, aber ist das nicht Crockfords Funktion für die prototypische Vererbung? Wie trifft es auf das Klonen zu?
Alex Nolasco

3
Ja, ich hatte Angst vor dieser Diskussion: Was ist der praktische Unterschied zwischen Klon-, Kopier- und Prototypvererbung, wann sollten Sie jede verwenden und welche Funktionen auf dieser Seite tun tatsächlich was? Ich habe diese SO-Seite gefunden, indem ich "Javascript-Kopierobjekt" gegoogelt habe. Was ich wirklich suchte, war die obige Funktion, also kam ich zurück, um sie zu teilen. Ich vermute, der Fragesteller hat auch danach gesucht.
Chris Broski

51
Der Unterschied zwischen Klon / Kopie und Vererbung besteht darin, dass - in Ihrem Beispiel, wenn ich eine Eigenschaft von oldObject ändere, die Eigenschaft auch in newObject geändert wird. Wenn Sie eine Kopie erstellen, können Sie mit oldObject tun, was Sie wollen, ohne newObject zu ändern.
Ridcully

13
Dadurch wird die hasOwnProperty-Prüfung unterbrochen, sodass das Klonen eines Objekts ziemlich schwierig ist und unerwartete Ergebnisse erzielt werden.
Corban Brook

var extendObj = function(childObj, parentObj) { var tmpObj = function () {} tmpObj.prototype = parentObj.prototype; childObj.prototype = new tmpObj(); childObj.prototype.constructor = childObj; };... davidshariff.com/blog/javascript-inheritance-patterns
Cody

22

Lodash hat eine schöne _.cloneDeep (value) -Methode:

var objects = [{ 'a': 1 }, { 'b': 2 }];

var deep = _.cloneDeep(objects);
console.log(deep[0] === objects[0]);
// => false

5
Ich empfehle, diese und alle anderen Antworten zu löschen, die nur einzeilige Verweise auf die .clone(...)Methode einer Dienstprogrammbibliothek sind . Jede größere Bibliothek wird sie haben, und die wiederholten kurzen, nicht detaillierten Antworten sind für die meisten Besucher, die diese bestimmte Bibliothek nicht verwenden, nicht nützlich.
Jeremy Banks

Ein einfacher Weg ist die Verwendung _.merge({}, objA). Wenn nur lodash Objekte überhaupt nicht mutieren clonewürde, wäre die Funktion nicht notwendig.
Rebs

7
Auf die Google-Suche zum Klonen von JS-Objekten wird hier verwiesen. Ich benutze Lodash, daher ist diese Antwort für mich relevant. Lass uns nicht alle "Wikipedia Deletionist" auf Antworten gehen.
Rebs

2
In Knoten 9 ist JSON.parse (JSON.stringify (arrayOfAbout5KFlatObjects)) viel schneller als _.deepClone (arrayOfAbout5KFlatObjects).
Dan Dascalescu

21
function clone(obj)
 { var clone = {};
   clone.prototype = obj.prototype;
   for (property in obj) clone[property] = obj[property];
   return clone;
 }

17
Das Problem mit der Methode, dass, wenn Sie Unterobjekte innerhalb des Objekts haben, deren Referenzen geklont werden und nicht die Werte jedes Unterobjekts.
Kamarey

1
Machen Sie es einfach rekursiv, damit die Unterobjekte tief geklont werden.
Fiatjaf

nur neugierig ... wird die Klonvariable nicht die Zeiger auf die Eigenschaften des ursprünglichen Objekts haben? weil es keine neue Speicherzuordnung scheint
Rupesh Patel

3
Ja. Dies ist nur eine flache Kopie, sodass der Klon auf genau dieselben Objekte zeigt, auf die das ursprüngliche Objekt zeigt.
Mark Cidade

Dies ist keine Antwort. Sie füllen buchstäblich nur ein Objekt mit Verweisen auf ein anderes Objekt. Wenn Sie Änderungen am Quellobjekt vornehmen, werden Änderungen am "Klon" vorgenommen.
Shawn Whinnery

19

Einzeiler mit flacher Kopie ( ECMAScript 5. Ausgabe ):

var origin = { foo : {} };
var copy = Object.keys(origin).reduce(function(c,k){c[k]=origin[k];return c;},{});

console.log(origin, copy);
console.log(origin == copy); // false
console.log(origin.foo == copy.foo); // true

Und flacher Einzeiler ( ECMAScript 6. Ausgabe , 2015):

var origin = { foo : {} };
var copy = Object.assign({}, origin);

console.log(origin, copy);
console.log(origin == copy); // false
console.log(origin.foo == copy.foo); // true

6
Dies mag für einfache Objekte in Ordnung sein, kopiert jedoch nur Eigenschaftswerte. Die Prototypenkette wird nicht berührt, und bei ihrer Verwendung werden Object.keysnicht aufzählbare und geerbte Eigenschaften übersprungen. Außerdem verliert es durch direkte Zuweisung Eigenschaftsbeschreibungen.
Matt Bierner

Wenn Sie auch den Prototyp kopieren, fehlen Ihnen nur Nicht-Enumerables und Eigenschaftsbeschreibungen, ja? Ziemlich gut. :)
Sam

Abgesehen von der Leistung ist dies eine sehr bequeme Möglichkeit, ein Objekt flach zu kopieren. Ich verwende dies oft, um gefälschte Resteigenschaften in einer Destrukturierungsaufgabe in meinen React-Komponenten zu sortieren.
mjohnsonengr

17

Nur weil ich AngularJS nicht erwähnt habe und dachte, dass die Leute es wissen wollen ...

angular.copy bietet auch eine Methode zum tiefen Kopieren von Objekten und Arrays.


oder es könnte genauso verwendet werden wie jQiery verlängern:angular.extend({},obj);
Galvani

2
@ Galvani: Es sollte beachtet werden, dass jQuery.extendund angular.extendbeide flache Kopien sind. angular.copyist eine tiefe Kopie.
Dan Atkinson

16

Es scheint noch keinen idealen Deep-Clone-Operator für Array-ähnliche Objekte zu geben. Wie der folgende Code zeigt, wandelt der jQuery-Cloner von John Resig Arrays mit nicht numerischen Eigenschaften in Objekte um, die keine Arrays sind, und der JSON-Cloner von RegDwight löscht die nicht numerischen Eigenschaften. Die folgenden Tests veranschaulichen diese Punkte in mehreren Browsern:

function jQueryClone(obj) {
   return jQuery.extend(true, {}, obj)
}

function JSONClone(obj) {
   return JSON.parse(JSON.stringify(obj))
}

var arrayLikeObj = [[1, "a", "b"], [2, "b", "a"]];
arrayLikeObj.names = ["m", "n", "o"];
var JSONCopy = JSONClone(arrayLikeObj);
var jQueryCopy = jQueryClone(arrayLikeObj);

alert("Is arrayLikeObj an array instance?" + (arrayLikeObj instanceof Array) +
      "\nIs the jQueryClone an array instance? " + (jQueryCopy instanceof Array) +
      "\nWhat are the arrayLikeObj names? " + arrayLikeObj.names +
      "\nAnd what are the JSONClone names? " + JSONCopy.names)

14
Wie andere in Kommentaren zu Resigs Antwort betont haben, ändern Sie beim Klonen eines Array-ähnlichen Objekts das {} in [] im Extend-Aufruf, z. B. jQuery.extend (true, [], obj)
Anentropic

15

Ich habe zwei gute Antworten, je nachdem, ob Ihr Ziel darin besteht, ein "einfaches altes JavaScript-Objekt" zu klonen oder nicht.

Nehmen wir außerdem an, dass Sie beabsichtigen, einen vollständigen Klon ohne Prototypverweise auf das Quellobjekt zu erstellen. Wenn Sie nicht an einem vollständigen Klon interessiert sind, können Sie viele der Object.clone () -Routinen verwenden, die in einigen anderen Antworten (Crockfords Muster) enthalten sind.

Für einfache alte JavaScript-Objekte ist eine bewährte Methode zum Klonen eines Objekts in modernen Laufzeiten ganz einfach:

var clone = JSON.parse(JSON.stringify(obj));

Beachten Sie, dass das Quellobjekt ein reines JSON-Objekt sein muss. Dies bedeutet, dass alle verschachtelten Eigenschaften Skalare sein müssen (wie Boolesche Werte, Zeichenfolgen, Arrays, Objekte usw.). Funktionen oder spezielle Objekte wie RegExp oder Date werden nicht geklont.

Ist es effizient? Oh Ja. Wir haben alle Arten von Klonmethoden ausprobiert und dies funktioniert am besten. Ich bin sicher, ein Ninja könnte eine schnellere Methode zaubern. Aber ich vermute, wir sprechen von geringfügigen Gewinnen.

Dieser Ansatz ist einfach und leicht zu implementieren. Wickeln Sie es in eine Komfortfunktion ein, und wenn Sie wirklich etwas Gewinn herausholen müssen, versuchen Sie es zu einem späteren Zeitpunkt.

Für nicht einfache JavaScript-Objekte gibt es keine wirklich einfache Antwort. In der Tat kann es aufgrund der Dynamik der JavaScript-Funktionen und des inneren Objektzustands keine geben. Um eine JSON-Struktur mit darin enthaltenen Funktionen tief zu klonen, müssen Sie diese Funktionen und ihren inneren Kontext neu erstellen. Und JavaScript hat einfach keine standardisierte Methode, um dies zu tun.

Der richtige Weg, dies erneut zu tun, ist eine bequeme Methode, die Sie in Ihrem Code deklarieren und wiederverwenden. Die Convenience-Methode kann mit einem gewissen Verständnis Ihrer eigenen Objekte ausgestattet werden, sodass Sie sicherstellen können, dass das Diagramm innerhalb des neuen Objekts ordnungsgemäß neu erstellt wird.

Wir haben unsere eigenen geschrieben, aber der beste allgemeine Ansatz, den ich gesehen habe, wird hier behandelt:

http://davidwalsh.name/javascript-clone

Das ist die richtige Idee. Der Autor (David Walsh) hat das Klonen generalisierter Funktionen auskommentiert. Dies können Sie je nach Anwendungsfall tun.

Die Hauptidee ist, dass Sie die Instanziierung Ihrer Funktionen (oder sozusagen prototypischer Klassen) pro Typ speziell behandeln müssen. Hier hat er einige Beispiele für RegExp und Date angegeben.

Dieser Code ist nicht nur kurz, sondern auch sehr gut lesbar. Es ist ziemlich einfach zu erweitern.

Ist das effizient? Oh Ja. Da das Ziel darin besteht, einen echten Deep-Copy-Klon zu erstellen, müssen Sie die Mitglieder des Quellobjektdiagramms durchlaufen. Mit diesem Ansatz können Sie genau festlegen, welche untergeordneten Mitglieder behandelt werden sollen und wie benutzerdefinierte Typen manuell behandelt werden sollen.

Hier bitteschön. Zwei Ansätze. Beide sind meiner Ansicht nach effizient.


13

Dies ist im Allgemeinen nicht die effizienteste Lösung, aber sie macht das, was ich brauche. Einfache Testfälle unten ...

function clone(obj, clones) {
    // Makes a deep copy of 'obj'. Handles cyclic structures by
    // tracking cloned obj's in the 'clones' parameter. Functions 
    // are included, but not cloned. Functions members are cloned.
    var new_obj,
        already_cloned,
        t = typeof obj,
        i = 0,
        l,
        pair; 

    clones = clones || [];

    if (obj === null) {
        return obj;
    }

    if (t === "object" || t === "function") {

        // check to see if we've already cloned obj
        for (i = 0, l = clones.length; i < l; i++) {
            pair = clones[i];
            if (pair[0] === obj) {
                already_cloned = pair[1];
                break;
            }
        }

        if (already_cloned) {
            return already_cloned; 
        } else {
            if (t === "object") { // create new object
                new_obj = new obj.constructor();
            } else { // Just use functions as is
                new_obj = obj;
            }

            clones.push([obj, new_obj]); // keep track of objects we've cloned

            for (key in obj) { // clone object members
                if (obj.hasOwnProperty(key)) {
                    new_obj[key] = clone(obj[key], clones);
                }
            }
        }
    }
    return new_obj || obj;
}

Zyklischer Array-Test ...

a = []
a.push("b", "c", a)
aa = clone(a)
aa === a //=> false
aa[2] === a //=> false
aa[2] === a[2] //=> false
aa[2] === aa //=> true

Funktionstest...

f = new Function
f.a = a
ff = clone(f)
ff === f //=> true
ff.a === a //=> false

11

AngularJS

Wenn Sie Angular verwenden, können Sie dies auch tun

var newObject = angular.copy(oldObject);

11

Ich bin mit der Antwort mit den größten Stimmen hier nicht einverstanden . Ein rekursiver Deep Clone ist viel schneller als der erwähnte Ansatz von JSON.parse (JSON.stringify (obj)) .

Und hier ist die Funktion zum schnellen Nachschlagen:

function cloneDeep (o) {
  let newO
  let i

  if (typeof o !== 'object') return o

  if (!o) return o

  if (Object.prototype.toString.apply(o) === '[object Array]') {
    newO = []
    for (i = 0; i < o.length; i += 1) {
      newO[i] = cloneDeep(o[i])
    }
    return newO
  }

  newO = {}
  for (i in o) {
    if (o.hasOwnProperty(i)) {
      newO[i] = cloneDeep(o[i])
    }
  }
  return newO
}

2
Ich mochte diesen Ansatz, aber er behandelt Daten nicht richtig. Erwägen Sie, etwas hinzuzufügen, if(o instanceof Date) return new Date(o.valueOf());nachdem Sie nach null gesucht haben `
Luis

Abstürze bei Zirkelverweisen.
Harry

Im neuesten stabilen Firefox ist dies um eine Größenordnung oder mehr viel länger als die anderen Strategien bei diesem Jsben.ch-Link. Es schlägt die anderen in die falsche Richtung.
WBT

11
// obj target object, vals source object
var setVals = function (obj, vals) {
    if (obj && vals) {
        for (var x in vals) {
            if (vals.hasOwnProperty(x)) {
                if (obj[x] && typeof vals[x] === 'object') {
                    obj[x] = setVals(obj[x], vals[x]);
                } else {
                    obj[x] = vals[x];
                }
            }
        }
    }
    return obj;
};

10

Nur wenn Sie ECMAScript 6 oder Transpiler verwenden können .

Eigenschaften:

  • Löst beim Kopieren keinen Getter / Setter aus.
  • Erhält Getter / Setter.
  • Bewahrt Prototypinformationen auf.
  • Funktioniert sowohl mit objektliteralen als auch mit funktionalen OO- Schreibstilen.

Code:

function clone(target, source){

    for(let key in source){

        // Use getOwnPropertyDescriptor instead of source[key] to prevent from trigering setter/getter.
        let descriptor = Object.getOwnPropertyDescriptor(source, key);
        if(descriptor.value instanceof String){
            target[key] = new String(descriptor.value);
        }
        else if(descriptor.value instanceof Array){
            target[key] = clone([], descriptor.value);
        }
        else if(descriptor.value instanceof Object){
            let prototype = Reflect.getPrototypeOf(descriptor.value);
            let cloneObject = clone({}, descriptor.value);
            Reflect.setPrototypeOf(cloneObject, prototype);
            target[key] = cloneObject;
        }
        else {
            Object.defineProperty(target, key, descriptor);
        }
    }
    let prototype = Reflect.getPrototypeOf(source);
    Reflect.setPrototypeOf(target, prototype);
    return target;
}

9

Hier ist eine umfassende clone () -Methode, mit der jedes JavaScript-Objekt geklont werden kann. Es behandelt fast alle Fälle:

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

Es konvertiert Grundelemente in Wrapper-Objekte, was in den meisten Fällen keine gute Lösung ist.
Danubian Sailor

@DanubianSailor - Ich glaube nicht, dass dies der Fall ist ... es scheint von Anfang an Primitive zurückzugeben und scheint ihnen nichts anzutun, was sie bei der Rückgabe in Wrapper-Objekte verwandeln würde.
Jimbo Jonny
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.