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


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

 Ich habe Dinge wie gemacht obj = JSON.parse(JSON.stringify(o)); aber frag die Effizienz.

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


4538
2018-06-06 14:59


Ursprung


Antworten:


Hinweis: Dies ist eine Antwort auf eine andere Antwort, keine richtige Antwort auf diese Frage. Wenn Sie ein schnelles Objektklonen wünschen, folgen Sie bitte Corbans Ratschlag in ihrer Antwort auf diese Frage.


Ich möchte darauf hinweisen, dass die .clone() Methode in jQuery klont nur DOM-Elemente. Um JavaScript-Objekte zu klonen, würden Sie Folgendes tun:

// Shallow copy
var newObject = jQuery.extend({}, oldObject);

// Deep copy
var newObject = jQuery.extend(true, {}, oldObject);

Weitere Informationen finden Sie in der jQuery-Dokumentation.

Ich möchte auch anmerken, dass die tiefe Kopie tatsächlich viel schlauer ist als das, was oben gezeigt wird - sie kann viele Fallen vermeiden (zum Beispiel, um ein DOM-Element tief zu erweitern). Es wird häufig in jQuery Core und in Plugins mit großer Wirkung verwendet.


4062



Check diesen Benchmark: http://jsben.ch/#/bWfk9

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

JSON.parse(JSON.stringify(obj))

um der schnellste Weg zu sein, ein Objekt tief zu klonen (es schlägt aus jQuery.extend mit tiefer Flagge, die 10-20% wahr ist).

jQuery.extend ist ziemlich schnell, wenn das tiefe Flag auf false gesetzt ist (oberflächlicher Klon). Es ist eine gute Option, da es einige zusätzliche Logik für die Typprüfung enthält und nicht über nicht definierte Eigenschaften usw. kopiert wird, aber dies wird Sie auch etwas verlangsamen.

Wenn Sie die Struktur der Objekte kennen, die Sie versuchen zu klonen, oder tief verschachtelte Arrays vermeiden können, können Sie ein einfaches schreiben for (var i in obj) Schleife, um Ihr Objekt zu klonen, während Sie hasOwnProperty überprüfen, und es wird viel schneller sein als jQuery.

Schließlich, wenn Sie versuchen, eine bekannte Objektstruktur in einer Hot-Loop zu klonen, können Sie VIEL VIEL MEHR LEISTUNG erzielen, indem Sie einfach den Klon-Vorgang eingrenzen und das Objekt manuell konstruieren.

JavaScript-Trace-Engines saugen bei der Optimierung for..in Schleifen und das Überprüfen von hasOwnProperty werden Sie ebenfalls verlangsamen. Manueller Klon wenn Geschwindigkeit ein absolutes Muss ist.

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

Vorsicht mit dem JSON.parse(JSON.stringify(obj)) Methode an Date Objekte - JSON.stringify(new Date()) gibt eine String-Darstellung des Datums im ISO-Format zurück JSON.parse()  nicht zurück in a konvertieren Date Objekt. Siehe diese Antwort für weitere Details.

Beachten Sie außerdem, dass natives Klonen in Chrome 65 nicht der richtige Weg ist. Gemäß dieser JSPerf, natives Klonen durch Erstellen einer neuen Funktion ist fast 800x langsamer als mit JSON.stringify, das auf der ganzen Linie unglaublich schnell ist.


1868



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

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

402



Strukturiertes Klonen

HTML5 definiert ein interner "strukturierter" Klonierungsalgorithmus das kann tiefe Klone von Objekten erzeugen. Es ist immer noch auf bestimmte integrierte Typen beschränkt, aber zusätzlich zu den wenigen Typen, die von JSON unterstützt werden, unterstützt es auch Datumsangaben, RegExps, Maps, Sets, Blobs, FileLists, ImageDatas, Sparse Arrays, Typisierte Arraysund wahrscheinlich mehr in der Zukunft. Außerdem werden Referenzen in den geklonten Daten beibehalten, sodass zyklische und rekursive Strukturen unterstützt werden, die JSON-Fehler verursachen.

Direkter Support in Browsern: Bald verfügbar?

Browser bieten derzeit keine direkte Schnittstelle für den strukturierten Klonierungsalgorithmus, sondern eine globale structuredClone() Funktion wird aktiv diskutiert in whatwg / html # 793 auf GitHub und könnte bald kommen! Wie derzeit vorgeschlagen, wird die Verwendung für die meisten Zwecke so einfach sein wie:

const clone = structuredClone(original);

Bis diese ausgeliefert werden, werden strukturierte Clone-Implementierungen von Browsern nur indirekt verfügbar gemacht.

Asynchrone Problemumgehung: Verwendbar.

Die Methode mit dem geringeren Overhead zum Erstellen eines strukturierten Clones mit vorhandenen APIs besteht darin, die Daten über einen Port von a zu posten Nachrichtenkanäle. Der andere Port sendet ein message Ereignis mit einem strukturierten Klon des angehängten .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 Möglichkeiten, synchron strukturierte Clones zu erstellen. Hier sind ein paar unpraktische Hacks statt.

history.pushState() und history.replaceState() beide erstellen einen strukturierten Klon ihres ersten Arguments und weisen diesen Wert zu history.state. Sie können damit einen strukturierten Klon eines beliebigen Objekts erstellen:

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

Beispiel Verwendung:

'use strict';

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

  const clone = structuredClone(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.");
};

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

main();

Obwohl synchron, kann dies extrem langsam sein. Es verursacht den gesamten Overhead, der mit der Manipulation des Browserverlaufs verbunden ist. Wenn Sie diese Methode wiederholt aufrufen, reagiert Chrome möglicherweise nicht mehr.

Das Notification Konstrukteur erstellt einen strukturierten Klon seiner zugehörigen Daten. Es wird außerdem versucht, dem Benutzer eine Browserbenachrichtigung anzuzeigen. Dies wird jedoch im Hintergrund fehlschlagen, sofern Sie keine Benachrichtigungsberechtigung angefordert haben. Falls Sie die Erlaubnis für andere Zwecke haben, schließen wir die Benachrichtigung, die wir erstellt haben, sofort.

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

Beispiel Verwendung:

'use strict';

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

  const clone = structuredClone(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.");
};

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

main();


274



Wenn es kein eingebautes gab, 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;
    }


273



Die effiziente Möglichkeit, ein Objekt in einer Codezeile zu klonen (nicht zu deep-klonen)

Ein Object.assign Methode 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...

Das Polyfill um ältere Browser zu unterstützen:

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

132



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

91



Das ist was ich benutze:

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

81



Tiefe Kopie nach Leistung: Rangliste von den besten bis zu den schlechtesten

  • Reassignment "=" (String-Arrays, nur Zahlen-Arrays)
  • Slice (String-Arrays, nur Zahlen-Arrays)
  • Verkettung (String-Arrays, nur Zahlen-Arrays)
  • Benutzerdefinierte Funktion: for-loop oder rekursive Kopie
  • jQuerys $ .Extend
  • JSON.parse (String-Arrays, Zahlen-Arrays, nur Objekt-Arrays)
  • Underscore.js's _.clone (string arrays, number arrays - nur)
  • Lo-Dash's _.cloneDeep

Tiefes Kopieren eines Arrays von Strings oder Zahlen (eine Ebene - keine Referenzzeiger):

Wenn ein Array Zahlen und Strings enthält - Funktionen wie .slice (), .concat (), .splice (), der Zuweisungsoperator "=" und die Klonfunktion von Underscore.js; macht 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

Tieferes Kopieren eines Arrays von Objekten (zwei oder mehr Ebenen - Referenzzeiger):

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

Schreiben Sie eine benutzerdefinierte Funktion (hat eine höhere 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 Dienstfunktionen von Drittanbietern:

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

Wo jQuery $ .extend eine bessere Leistung hat:


64