Underscore: Deep Extend und Deep Copy

Erstellt am 25. März 2011  ·  28Kommentare  ·  Quelle: jashkenas/underscore

Featureanfrage:
Wäre es möglich, entweder einen booleschen Parameter für _.extend() und _.copy() zu haben, um sie tief zu machen, oder separate tiefe Methoden zu haben?

enhancement wontfix

Hilfreichster Kommentar

_.deepClone = function(obj) {
      return (!obj || (typeof obj !== 'object'))?obj:
          (_.isString(obj))?String.prototype.slice.call(obj):
          (_.isDate(obj))?new Date(obj.valueOf()):
          (_.isFunction(obj.clone))?obj.clone():
          (_.isArray(obj)) ? _.map(obj, function(t){return _.deepClone(t)}):
          _.mapObject(obj, function(val, key) {return _.deepClone(val)});
  };

Alle 28 Kommentare

Ich befürchte, dass es in JavaScript keine wirklich gute Semantik für einen Deep-Copy-Vorgang gibt. Vorhandene Implementierungen haben verschiedene Fehler, wie z. B. das Mutieren verschachtelter Arrays und das Nichtkopieren verschachtelter Date-Objekte. Wenn Sie dieses Feature vorschlagen möchten, müssen Sie es mit einer kugelsicheren Implementierung begleiten.

Richtig, tiefe Kopien sind in Javascript definitiv chaotisch. Irgendwann muss man doch sagen, dass die Vorteile die Nachteile überwiegen, oder? Es ist schlimmer, Leute ständig miese Deep-Copy-Methoden schreiben zu lassen, die nicht wie erwartet funktionieren, als eine Deep-Copy-Methode zu verwenden, die sorgfältig codiert wurde, aber gut dokumentierte Einschränkungen hat. Zum Beispiel ist die Lösung dieses Typen großartig: http://stackoverflow.com/questions/728360/copying-an-object-in-javascript/728694#728694

Er deckt die meisten Basen ab und dokumentiert, was nicht richtig gehandhabt wird. Er geht davon aus, dass das Objekt nur diese Typen enthält: Objekt, Array, Datum, Zeichenfolge, Zahl und Boolesch, und er geht davon aus, dass alle Objekte oder Arrays nur dieselben Typen enthalten.

Ja – und selbst die Lösung dieses Typen ist wirklich nicht gut genug. Wenn wir es nicht richtig implementieren können, sollten wir es nicht implementieren.

Aber ist es wirklich besser für Benutzer, ihre eigene, wahrscheinlich noch kaputtere Implementierung zu erstellen?

Nein – es ist besser für Benutzer, nicht tief in JavaScript zu kopieren. Sie können normalerweise einen Weg finden, dasselbe Ziel zu erreichen, ohne eine robuste Deep-Copy-Funktion haben zu müssen ... indem Sie die Struktur des Objekts, das Sie kopieren möchten, im Voraus kennen.

Ah, Sie schlagen also vor, dass der Benutzer eine neue Instanz des Objekts mit den erforderlichen Werten hydratisiert? Ich kann sehen, dass.

Jquery.extend hat eine tiefe Option.

+1 für die tiefe Option, unabhängig davon, wie schwierig die Implementierung ist.

+2 dafür - es ist nicht schwer zu implementieren, es ist eine Frage des Prinzips (dh keine weniger als perfekte Lösung zu implementieren). Wie oben erwähnt, wird es jedoch in der jQuery-Bibliothek verwendet und ist bis auf wenige Randfälle in allen Fällen sehr nützlich. Es wäre schön, es in einer leichten Bibliothek wie dieser verfügbar zu haben.

@kmalakoff hat eine Implementierung geschrieben , du solltest ihm Feedback geben! :)

Sie könnten sogar mein ursprüngliches _.cloneToDepth mit _clone zusammenführen und einfach einen Tiefenparameter hinzufügen ....

  // Create a duplicate of a container of objects to any zero-indexed depth.
  _.cloneToDepth = _.clone = function(obj, depth) {
    if (typeof obj !== 'object') return obj;
    var clone = _.isArray(obj) ? obj.slice() : _.extend({}, obj);
    if (!_.isUndefined(depth) && (depth > 0)) {
      for (var key in clone) {
        clone[key] = _.clone(clone[key], depth-1);
      }
    }
    return clone;
  };

Außerdem habe ich _.own und _.disown geschrieben, die eine Konvention für den Besitz einführen (entweder Paare von Beibehalten/Freigeben oder Klonen/Zerstören). Es rekursiert nur eine Ebene nach unten, aber ich nehme an, es könnte eine Option für die vollständige Rekursion hinzugefügt werden (ich möchte einen Anwendungsfall sehen!).

  _.own = function(obj, options) {
    if (!obj || (typeof(obj)!='object')) return obj;
    options || (options = {});
    if (_.isArray(obj)) {
      if (options.share_collection) { _.each(obj, function(value) { _.own(value, {prefer_clone: options.prefer_clone}); }); return obj; }
      else { var a_clone =  []; _.each(obj, function(value) { a_clone.push(_.own(value, {prefer_clone: options.prefer_clone})); }); return a_clone; }
    }
    else if (options.properties) {
      if (options.share_collection) { _.each(obj, function(value, key) { _.own(value, {prefer_clone: options.prefer_clone}); }); return obj; }
      else { var o_clone = {}; _.each(obj, function(value, key) { o_clone[key] = _.own(value, {prefer_clone: options.prefer_clone}); }); return o_clone; }
    }
    else if (obj.retain) {
      if (options.prefer_clone && obj.clone) return obj.clone();
      else obj.retain();
    }
    else if (obj.clone) return obj.clone();
    return obj;
  };

  _.disown = function(obj, options) {
    if (!obj || (typeof(obj)!='object')) return obj;
    options || (options = {});
    if (_.isArray(obj)) {
      if (options.clear_values) { _.each(obj, function(value, index) { _.disown(value); obj[index]=null; }); return obj; }
      else {
        _.each(obj, function(value) { _.disown(value); });
        obj.length=0; return obj;
      }
    }
    else if (options.properties) {
      if (options.clear_values) { _.each(obj, function(value, key) { _.disown(value); obj[key]=null; }); return obj; }
      else {
        _.each(obj, function(value) { _.disown(value); });
        for(key in obj) { delete obj[key]; }
        return obj;
      }
    }
    else if (obj.release) obj.release();
    else if (obj.destroy) obj.destroy();
    return obj;
  };

Und wenn Sie einen universellen, erweiterbaren Klon wollen:

  // Create a duplicate of all objects to any zero-indexed depth.
  _.deepClone = function(obj, depth) {
    if (typeof obj !== 'object') return obj;
    if (_.isString(obj)) return obj.splice();
    if (_.isDate(obj)) return new Date(obj.getTime());
    if (_.isFunction(obj.clone)) return obj.clone();
    var clone = _.isArray(obj) ? obj.slice() : _.extend({}, obj);
    if (!_.isUndefined(depth) && (depth > 0)) {
      for (var key in clone) {
        clone[key] = _.deepClone(clone[key], depth-1);
      }
    }
    return clone;
  };

Ich bin nicht davon überzeugt, dass dies eine gute Idee ist, bis ein Anwendungsfall gegeben ist, bei dem eine tiefe Kopie tatsächlich die beste Lösung ist. Ich denke, es wird schwierig sein, einen zu finden.

Ich war gestern mit meiner Antwort nicht ganz zufrieden (sollte nach Mitternacht keinen Code schreiben) ... Ich habe mir zwei Versionen ausgedacht ( _.cloneToDepth klont im Grunde nur den Container, wobei Verweise auf ursprüngliche Objekte und _.deepClone was die Instanzen kopiert):

  // Create a duplicate of a container of objects to any zero-indexed depth.
  _.cloneToDepth = _.containerClone = _.clone = function(obj, depth) {
    if (!obj || (typeof obj !== 'object')) return obj;  // by value
    var clone;
    if (_.isArray(obj)) clone = Array.prototype.slice.call(obj);
    else if (obj.constructor!=={}.constructor) return obj; // by reference
    else clone = _.extend({}, obj);
    if (!_.isUndefined(depth) && (depth > 0)) {
      for (var key in clone) {
        clone[key] = _.clone(clone[key], depth-1);
      }
    }
    return clone;
  };

  // Create a duplicate of all objects to any zero-indexed depth.
  _.deepClone = function(obj, depth) {
    if (!obj || (typeof obj !== 'object')) return obj;  // by value
    else if (_.isString(obj)) return String.prototype.slice.call(obj);
    else if (_.isDate(obj)) return new Date(obj.valueOf());
    else if (_.isFunction(obj.clone)) return obj.clone();
    var clone;
    if (_.isArray(obj)) clone = Array.prototype.slice.call(obj);
    else if (obj.constructor!=={}.constructor) return obj; // by reference
    else clone = _.extend({}, obj);
    if (!_.isUndefined(depth) && (depth > 0)) {
      for (var key in clone) {
        clone[key] = _.deepClone(clone[key], depth-1);
      }
    }
    return clone;
  };

Wie @michaelficarra betont , können die Anwendungsfälle unklar sein. Ich persönlich verwende:

1) _.own / _.disown wenn ich Objekte mit komplexen Lebenszyklen und/oder Eigentumsmodellen teilen möchte (z. B. Referenzzählung zur ordnungsgemäßen Bereinigung)
2) Ich habe _.cloneToDepth / _.containerClone (selten!) verwendet, wenn ich komplexe, verschachtelte Optionen für eine Funktion hatte.
3) Ich habe noch nie eine _.deepClone , aber nehme an, sie könnte als allgemeine _.clone Methode nützlich sein, wenn Sie eine generische Funktion schreiben, die Typen flexibel unterstützt (z. B. ich nicht kümmere dich darum, was du mir übergibst, aber ich werde es modifizieren und möchte keine Nebenwirkungen auf das Original haben - obwohl Zeichenfolgen ein unveränderlicher Sonderfall sind).

Ich habe den Code und die Tests hier eingereicht: https://github.com/kmalakoff/underscore-awesomer/commit/0cf6008f16ad6e6daf60caf456021693ef33fec5

Hinweis: Wer auf der Suche nach einem einzeiligen, unvollständigen Deep-Clone für einfache Objekte mit vorhersagbarer Semantik feststeckt, kann nur JSON.parse(JSON.stringify(object)) .

@adamhooper Neigt diese Methode dazu, Eigenschaften oder so etwas zu verlieren? Warum ist es unvollständig?

@diversario Es wird weder der Objektprototyp noch Funktionen kopiert. Das gilt rekursiv – es kopiert also auch nicht die Prototypen verschachtelter Objekte oder funktioniert richtig.

Insbesondere: Es wird kein Datum in Ihrem Objektbaum richtig kopieren. Und wenn Sie es so reparieren möchten, dass es mit Dates funktioniert, adressieren Sie einfach ein kleines Symptom eines viel größeren Problems.

Oh, richtig. Ich benutze es hauptsächlich, um Verweise auf Dinge wie "Vorlagen" -Objekte zu unterbrechen, also bin ich auf so etwas nicht gestoßen. Aber ich sehe die Notwendigkeit einer wirklich tiefen Kopie.

Ich habe Kurt Milams deepExtend-Mixin in ein npm-Paket umgewandelt.

https://github.com/pygy/undescoreDeepExtend/

@michaelficarra , bitte erklären Sie, warum Deep-Copying keine gute Lösung zum Klonen einer generischen Baumstruktur ist.

Für alle von Ihnen, die versuchen, @adamhooper One-Liner Deep Copy zu verwenden, sollten Sie sich bewusst sein, dass es nicht für Daten funktioniert
JSON.parse (JSON.stringify (Objekt))
Tatsächlich konvertiert es jedes Objekt Date in eine Zeichenfolge

-1 für tiefe Kopie. Die Verwendung einer prototypischen Kette für Konfigurationsobjekte passt immer besser zu Ihrer API als verschachtelte Optionen.

Eigentlich gibt es einen Platz für Deep Copy, aber es sollte mit der Typprüfung zusammenfallen, also ist es meiner Meinung nach ein sehr spezifischer Anwendungsfall und kein allgemeiner Zweck. Besser geeignet für JSON-Schemabibliotheken oder Konfigurationsladeprogramme. Kein Javascript-Werkzeuggürtel.

In den meisten Fällen ist _.extend({}, obj1, { prop1: 1, prop2: 2 }) das, was ich wirklich tun muss:

  • gibt mir ein neues Objekt
  • ändert mein Quellobjekt nicht und
  • ist keine tiefe Kopie
_.deepClone = function(obj) {
      return (!obj || (typeof obj !== 'object'))?obj:
          (_.isString(obj))?String.prototype.slice.call(obj):
          (_.isDate(obj))?new Date(obj.valueOf()):
          (_.isFunction(obj.clone))?obj.clone():
          (_.isArray(obj)) ? _.map(obj, function(t){return _.deepClone(t)}):
          _.mapObject(obj, function(val, key) {return _.deepClone(val)});
  };

Wir stoßen in unserer Arbeitsumgebung häufig auf einen Anwendungsfall, der von Deep Cloning profitieren würde.

Manchmal müssen Sie eine Datenbankmigration durchführen, wenn Funktionen einer komplizierten Webanwendung aktualisiert und auf ein neues Paradigma erweitert werden. Eine tiefe Kopie unserer Daten ist sehr hilfreich, wenn Operationen wie das Generieren von Hashes aus den Daten durchgeführt werden müssen, wenn die ursprüngliche Referenz nicht manipuliert werden soll. Ich glaube nicht, dass ein einziges Dokument in unserer riesigen Datenbank „flach“ ist. Verschachtelte Eigenschaften gibt es überall.

Manchmal möchten Sie auch eine Kopie eines Objekts an einen anderen Dienst innerhalb Ihrer Webanwendung senden, damit es bearbeitet wird, bevor Sie entscheiden, was damit geschehen soll. Das Besitzen der verschachtelten Eigenschaften als Referenz macht den Zweck eines geklonten Objekts zunichte. Der GANZE Punkt, imo, eines geklonten Objekts besteht darin, die ursprüngliche Referenz intakt zu lassen. Sobald Sie Referenzen für die tieferen Ebenen einführen, ist der ganze Zweck hinfällig.

Ich bin sicher, es gibt einen anständigen Weg, um dies zu erreichen, bei dem ein Objekt in seine Tiefen navigiert und dann von der tiefsten Schicht bis zur Wurzel rekonstruiert werden kann.

War diese Seite hilfreich?
0 / 5 - 0 Bewertungen

Verwandte Themen

umarfarooq125 picture umarfarooq125  ·  8Kommentare

haggholm picture haggholm  ·  8Kommentare

arypbatista picture arypbatista  ·  3Kommentare

jdalton picture jdalton  ·  4Kommentare

githublyp picture githublyp  ·  3Kommentare