Underscore: Extension approfondie et copie approfondie

Créé le 25 mars 2011  ·  28Commentaires  ·  Source: jashkenas/underscore

Demande de fonctionnalité :
Serait-il possible d'avoir un paramètre booléen sur _.extend() et _.copy() pour les rendre profonds, ou d'avoir des méthodes profondes séparées ?

enhancement wontfix

Commentaire le plus utile

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

Tous les 28 commentaires

Je crains qu'il n'y ait pas de très bonne sémantique pour une opération de copie en profondeur en JavaScript. Les implémentations existantes présentent divers bogues, comme la mutation des tableaux imbriqués et l'échec de la copie des objets Date imbriqués. Si vous souhaitez proposer cette fonctionnalité, vous devrez l'accompagner d'une implémentation à toute épreuve.

Certes, les copies profondes sont définitivement désordonnées en javascript. Il doit y avoir un moment où vous dites que les avantages l'emportent sur les inconvénients, n'est-ce pas ? Avoir des gens qui écrivent constamment des méthodes de copie en profondeur minables qui ne fonctionnent pas comme prévu est pire que d'utiliser une méthode de copie en profondeur qui a été soigneusement codée, mais qui a des limites bien documentées. Par exemple, la solution de ce type est excellente : http://stackoverflow.com/questions/728360/copying-an-object-in-javascript/728694#728694

Il couvre la plupart des bases et documente ce qu'il ne gérera pas correctement. Il suppose que l'objet ne contient que ces types : objet, tableau, date, chaîne, nombre et booléen, et il suppose que tous les objets ou tableaux ne contiendront que le même.

Oui - et même la solution de ce type n'est vraiment pas assez bonne. Si nous ne pouvons pas l'implémenter correctement, nous ne devrions pas l'implémenter.

Mais est-il vraiment préférable que les utilisateurs lancent leur propre implémentation, probablement encore plus cassée ?

Non - il est préférable que les utilisateurs ne copient pas en profondeur en JavaScript. Vous pouvez généralement trouver un moyen d'atteindre le même objectif sans avoir à disposer d'une fonction de copie en profondeur robuste ... en connaissant à l'avance la structure de l'objet que vous souhaitez copier.

Ah, vous suggérez donc que l'utilisateur hydrate une nouvelle instance de l'objet avec les valeurs nécessaires ? Je peux voir ça.

Jquery.extend a une option profonde.

+1 pour l'option profonde, quelle que soit la difficulté de sa mise en œuvre.

+2 pour cela - ce n'est pas difficile à mettre en œuvre, c'est une question de principe (c'est-à-dire ne pas mettre en œuvre une solution moins que parfaite). Cependant, comme mentionné ci-dessus, il est utilisé dans la bibliothèque jQuery et très utile dans tous les cas extrêmes, sauf quelques-uns. Ce serait bien de l'avoir disponible dans une bibliothèque légère comme celle-ci.

@kmalakoff a écrit une implémentation , vous devriez lui faire part de vos commentaires ! :)

Vous pouvez même fusionner mon _.cloneToDepth d'origine avec _clone et simplement ajouter un paramètre de profondeur...

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

De plus, j'ai écrit _.own et _.disown qui introduisent une convention de propriété (soit des paires de conserver/libérer ou de cloner/détruire). Il ne récurse qu'un niveau plus bas, mais je suppose qu'il pourrait y avoir une option ajoutée pour la récursivité totale (je voudrais voir un cas d'utilisation !).

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

Et si vous voulez un clone extensible à usage général :

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

Je ne suis pas convaincu que ce soit une bonne idée jusqu'à ce qu'un cas d'utilisation soit donné où une copie en profondeur est en fait la meilleure solution. Je pense que ça va être dur d'en trouver un.

Je n'étais pas entièrement satisfait de ma réponse d'hier (je ne devrais pas écrire de code après minuit) ... J'ai proposé deux versions ( _.cloneToDepth ne fait que cloner le conteneur, en conservant les références aux objets d'origine et _.deepClone qui copie les instances) :

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

Comme le souligne @michaelficarra , les cas d'utilisation peuvent ne pas être clairs. Personnellement, j'utilise :

1) _.own / _.disown lorsque je veux partager des objets qui ont des cycles de vie complexes et/ou des modèles de propriété (comme le comptage de références pour gérer correctement le nettoyage)
2) J'ai utilisé _.cloneToDepth / _.containerClone (rarement !) lorsque j'avais des options complexes et imbriquées pour une fonction.
3) Je n'ai jamais eu besoin d'un _.deepClone , mais supposons qu'il pourrait être utile comme méthode à usage général _.clone si vous écrivez une fonction générique qui prend en charge les types de manière flexible (par exemple, je ne attention à ce que vous me passez, mais je vais le modifier et je ne veux pas avoir d'effets secondaires sur l'original - bien que les chaînes soient un cas spécial immuable).

J'ai soumis le code et les tests ici : https://github.com/kmalakoff/underscore-awesomer/commit/0cf6008f16ad6e6daf60caf456021693ef33fec5

Remarque : ceux qui recherchent un clone profond incomplet d'une ligne pour des objets simples avec une sémantique prévisible peuvent simplement JSON.parse(JSON.stringify(object)) .

@adamhooper Cette méthode a-t-elle tendance à perdre des propriétés ou quelque chose ? Pourquoi est-il incomplet ?

@diversario Il ne copiera pas le prototype d'objet et il ne copiera pas les fonctions. Cela s'applique de manière récursive, de sorte qu'il ne copie pas correctement les prototypes ou les fonctions des objets imbriqués non plus.

En particulier : il ne copiera correctement aucune date dans votre arbre d'objets. Et si vous voulez le réparer pour qu'il fonctionne avec Dates, eh bien, vous traitez simplement un petit symptôme d'un problème beaucoup plus important.

Ah, c'est vrai. Je l'utilise principalement pour casser la référence à des choses comme des objets "modèles", donc je n'ai rien rencontré de tel. Mais je vois le besoin d'une véritable copie profonde.

J'ai transformé le mixin deepExtend de Kurt Milam en un package npm.

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

@michaelficarra , veuillez expliquer en quoi la copie en profondeur n'est-elle pas une bonne solution pour cloner une structure arborescente générique ?

Pour tous ceux d'entre vous qui essaient d'utiliser @adamhooper one-liner deep copy, sachez que cela ne fonctionne pas pour les dates
JSON.parse(JSON.stringify(objet))
en fait, il convertit n'importe quel objet Date en chaîne

-1 pour la copie en profondeur. L'utilisation d'une chaîne prototype pour les objets de configuration conviendra toujours mieux à votre API que les options imbriquées.

En fait, il y a une place pour la copie en profondeur, mais elle devrait coïncider avec la vérification de type, c'est donc un cas d'utilisation très spécifique à mon avis, et non un usage général. Mieux adapté aux bibliothèques de schémas JSON ou aux chargeurs de configuration. Pas une ceinture à outils javascript.

Dans la plupart des cas, _.extend({}, obj1, { prop1: 1, prop2: 2 }) est ce que j'ai vraiment besoin de faire :

  • me donne un nouvel objet
  • ne modifie pas mon objet source et
  • n'est pas une copie profonde
_.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)});
  };

Nous rencontrons souvent un cas d'utilisation dans notre environnement de travail qui bénéficierait du clonage en profondeur.

Parfois, vous devez effectuer une migration de base de données, lorsque les fonctionnalités d'une application Web complexe sont mises à jour et étendues à un nouveau paradigme. Une copie profonde de nos données est très utile lorsque vous devez effectuer des opérations telles que la génération de hachages à partir des données lorsque la référence d'origine est préférée pour ne pas être manipulée. Je ne pense pas qu'un seul document de notre énorme base de données soit "plat". Les propriétés imbriquées existent partout.

De plus, vous souhaitez parfois envoyer une copie d'un objet à un service différent au sein de votre application Web, pour qu'il soit exploité avant de décider quoi en faire. Avoir les propriétés imbriquées par référence va à l'encontre de l'objectif d'avoir un objet cloné. Le point ENTIER, imo, d'un objet cloné est de laisser la référence d'origine intacte. Dès que vous introduisez des références pour les niveaux plus profonds, tout le but est discutable.

Je suis sûr qu'il existe une manière décente d'y parvenir où un objet peut être navigué jusqu'à ses profondeurs, puis reconstruit à partir de la couche la plus profonde jusqu'à la racine.

Cette page vous a été utile?
0 / 5 - 0 notes

Questions connexes

jezen picture jezen  ·  8Commentaires

afranioce picture afranioce  ·  8Commentaires

arieljake picture arieljake  ·  4Commentaires

xiaoliwang picture xiaoliwang  ·  3Commentaires

jdalton picture jdalton  ·  6Commentaires