Nodejs: comment cloner un objet

90

Si je clone un tableau, j'utilise cloneArr = arr.slice()

Je veux savoir comment cloner un objet dans nodejs.

guilin 桂林
la source

Réponses:

175

Pour les utilitaires et les classes où il n'est pas nécessaire de réduire chaque goutte de performance, je triche souvent et j'utilise simplement JSON pour effectuer une copie complète:

function clone(a) {
   return JSON.parse(JSON.stringify(a));
}

Ce n'est pas la seule réponse ou la réponse la plus élégante; toutes les autres réponses doivent être prises en compte pour les goulots d'étranglement de la production. Cependant, c'est une solution rapide et sale, assez efficace et utile dans la plupart des situations où je clonerais un simple hachage de propriétés.

Kato
la source
2
@djechlin Bien sûr que oui. Essayez-le: jsfiddle.net/katowulf/E5jC3 (testé avec le nœud 0.10.11) Il ne sera pas en mesure de reconstituer des fonctions ou des données prototypiques, mais il obtient les valeurs très bien.
Kato
11
Cela convertira les dates en chaînes
backus
4
@Backus Ainsi que des objets et des fonctions.
Kato
2
@Kato, Pourquoi des références circulaires impliqueraient-elles qu'il n'est pas nécessaire de faire des copies complètes? Ce sont deux choses sans rapport. Il est certainement possible d'écrire une bonne méthode de clonage pour NodeJS qui prend en charge les références circulaires et ne retombe pas sur l'utilisation de JSON pour cloner. github.com/pvorb/node-clone
Samuel Neff
2
Déjà noté dans les commentaires et dans la description. Vous ne pouvez pas cloner une fonction, un prototype d'objet ou d'autres choses que vous ne devriez pas essayer de faire avec une méthode de clonage générique. Vous aurez besoin de plus d'une ligne de code pour cela, et vous ne devriez probablement pas cloner ce courrier indésirable de toute façon - mettez une méthode de clonage sur votre classe qui sait comment gérer les internes et le fairenewObj = obj.clone(args...);
Kato
34

Object.assign n'a été mentionné dans aucune des réponses ci-dessus.

let cloned = Object.assign({}, source);

Si vous êtes sur ES6, vous pouvez utiliser l'opérateur de diffusion:

let cloned = { ... source };

Référence: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/assign

Sam G
la source
28
Notez que les deux sont des clones superficiels et que l'opérateur de diffusion sur les objets nécessite node.js 8+.
jlh
1
Comme @jlh l'a mentionné, cela ne fonctionnera pas pour les objets imbriqués. Vous pouvez en savoir plus sur les problèmes de clonage profond de cette méthode ici: developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
Shnd
Comme l'a dit Shnd, cette réponse est incorrecte. Pour le clonage profond, nous devons utiliser d'autres alternatives car Object.assign () copie les valeurs des propriétés. Si la valeur source est une référence à un objet, elle copie uniquement cette valeur de référence.
troy
1
Cela ne clone pas, cela fait simplement référence à l'objet d'origine, ce qui signifie que si vous changez quelque chose, il ira au maître
Aidan Welch
@AidanWelch Je ne pense pas que ce soit correct. let a = {x:1}; let b = Object.assign({}, a); a.x = 2; a.y = 1; console.log(a, b);
Miguel Pynto le
20

Il y a quelques modules Node là-bas si vous ne voulez pas "rouler vous-même". Celui-ci a l'air bien: https://www.npmjs.com/package/clone

On dirait qu'il gère toutes sortes de choses, y compris les références circulaires. Depuis la page github :

clone maîtrise les objets de clonage, les tableaux, les objets Date et les objets RegEx. Tout est cloné de manière récursive, de sorte que vous pouvez cloner des dates dans des tableaux dans des objets, par exemple. [...] Références circulaires? Oui!

Clint Harris
la source
10

Il est difficile de faire une opération de clonage générique mais utile car ce qui doit être cloné récursivement et ce qui doit être simplement copié dépend de la façon dont l'objet spécifique est censé fonctionner.

Quelque chose qui peut être utile est

function clone(x)
{
    if (x === null || x === undefined)
        return x;
    if (typeof x.clone === "function")
        return x.clone();
    if (x.constructor == Array)
    {
        var r = [];
        for (var i=0,n=x.length; i<n; i++)
            r.push(clone(x[i]));
        return r;
    }
    return x;
}

Dans ce code, la logique est

  • dans le cas nullou undefinedretournez simplement le même (le cas particulier est nécessaire car c'est une erreur d'essayer de voir si unclone méthode est présente)
  • l'objet a-t-il un clone méthode? alors utilise ça
  • est l'objet un tableau? puis effectuez une opération de clonage récursif
  • sinon renvoyez simplement la même valeur

Cette fonction de clonage devrait permettre d'implémenter facilement des méthodes de clonage personnalisées ... par exemple

function Point(x, y)
{
    this.x = x;
    this.y = y;

    ...
}

Point.prototype.clone = function()
{
    return new Point(this.x, this.y);
};



function Polygon(points, style)
{
    this.points = points;
    this.style = style;

    ...
}

Polygon.prototype.clone = function()
{
    return new Polygon(clone(this.points),
                       this.style);
};

Lorsque dans l'objet vous savez qu'une opération de clonage correcte pour un tableau spécifique n'est qu'une copie superficielle, vous pouvez appeler values.slice()au lieu declone(values) .

Par exemple, dans le code ci-dessus, je demande explicitement qu'un clonage d'un objet polygonal clonera les points, mais partagera le même objet de style. Si je veux aussi cloner l'objet de style à la place, je peux simplement passer clone(this.style).

6502
la source
1
+1 pour avoir mentionné que les objets doivent implémenter la .cloneméthode eux-mêmes. C'est la meilleure façon de gérer le clonage d'objets.
Raynos
3
if (x.clone)devrait êtreif (typeof x.clone === 'function')
Yanick Rochon
@YanickRochon: Merci, corrigé. Désolé, je n'ai pas remarqué ce commentaire avant ...
6502
9

Il n'existe pas de méthode native pour cloner des objets. Underscore implements _.clonequi est un clone superficiel.

_.clone = function(obj) {
  return _.isArray(obj) ? obj.slice() : _.extend({}, obj);
};

Il le tranche ou le prolonge.

Voici _.extend

// extend the obj (first parameter)
_.extend = function(obj) {
  // for each other parameter
  each(slice.call(arguments, 1), function(source) {
    // loop through all properties of the other objects
    for (var prop in source) {
      // if the property is not undefined then add it to the object.
      if (source[prop] !== void 0) obj[prop] = source[prop];
    }
  });
  // return the object (first parameter)
  return obj;
};

Extend itère simplement à travers tous les éléments et crée un nouvel objet avec les éléments qu'il contient.

Vous pouvez déployer votre propre implémentation naïve si vous le souhaitez

function clone(o) {
  var ret = {};
  Object.keys(o).forEach(function (val) {
    ret[val] = o[val];
  });
  return ret;
}

Il y a de bonnes raisons d'éviter le clonage profond car les fermetures ne peuvent pas être clonées.

J'ai personnellement posé une question deep cloning objects beforeet la conclusion à laquelle je suis arrivé est que vous ne le faites tout simplement pas.

Ma recommandation est l'utilisation underscoreet sa _.cloneméthode pour les clones peu profonds

Raynos
la source
9

Pour une copie superficielle, j'aime utiliser le modèle de réduction (généralement dans un module ou autre), comme ceci:

var newObject = Object.keys(original).reduce(function (obj, item) {
    obj[item] = original[item];
    return obj;
},{});

Voici un jsperf pour quelques options: http://jsperf.com/shallow-copying

Olli K
la source
simple, élégant, brillant. J'attends avec impatience vos autres contributions SO ^ _ ^
Merci
7

Ancienne question, mais il y a une réponse plus élégante que ce qui a été suggéré jusqu'à présent; utilisez les utils._extend intégrés:

var extend = require("util")._extend;

var varToCopy = { test: 12345, nested: { val: 6789 } };

var copiedObject = extend({}, varToCopy);

console.log(copiedObject);

// outputs:
// { test: 12345, nested: { val: 6789 } }

Notez l'utilisation du premier paramètre avec un objet vide {} - cela indique à extend que le ou les objets copiés doivent être copiés dans un nouvel objet. Si vous utilisez un objet existant comme premier paramètre, le second (et tous les suivants) paramètres seront copiés en profondeur sur la première variable de paramètre.

En utilisant les exemples de variables ci-dessus, vous pouvez également faire ceci:

var anotherMergeVar = { foo: "bar" };

extend(copiedObject, { anotherParam: 'value' }, anotherMergeVar);

console.log(copiedObject);

// outputs:
// { test: 12345, nested: { val: 6789 }, anotherParam: 'value', foo: 'bar' }

Utilitaire très pratique, en particulier là où je suis habitué à étendre AngularJS et jQuery.

J'espère que ceci aide quelqu'un d'autre; les écrasements de références d'objets sont une misère, et cela le résout à chaque fois!

Scott Byers
la source
1
L'utilisation de _extend est désormais dépréciée. Utilisez plutôt Object.assign (): developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/…
MacroMan
7

Vous pouvez également utiliser lodash . Il a une méthode clone et cloneDeep .

var _= require('lodash');

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

var shallow = _.clone(objects);
console.log(shallow[0] === objects[0]);
// => true

var deep = _.cloneDeep(objects);
console.log(deep[0] === objects[0]);
Michal
la source
1

En fonction de ce que vous voulez faire avec votre objet cloné, vous pouvez utiliser le mécanisme d'héritage prototypique de javascript et obtenir un objet quelque peu cloné via:

var clonedObject = Object.create(originalObject);

N'oubliez pas que ce n'est pas un clone complet - pour le meilleur ou pour le pire.

Une bonne chose à ce sujet est que vous n'avez en fait pas dupliqué l'objet, donc l'empreinte mémoire sera faible.

Certaines choses difficiles à retenir à propos de cette méthode sont que l'itération des propriétés définies dans la chaîne de prototypes fonctionne parfois un peu différemment et le fait que toute modification apportée à l'objet d'origine affectera également l'objet cloné à moins que cette propriété n'ait également été définie sur elle-même. .

VoxPelli
la source
3
C'est tellement ridicule dangereux et différent du tout d'un clone. var a = {foo: "bar"}, b = Object.create(a); a.foo = "broken"; console.log(b.foo); // "broken";
Merci
@naomik: b.foo = "working"; console.log(a.foo); // still "broken";Il faut bien sûr être conscient que les modifications de l'objet d'origine seront reflétées dans le "clone" et que les modifications du "clone" ne seront pas reflétées dans l'objet d'origine - mais je n'appellerais pas cela dangereux - tout dépend de ce que vous voulez faire avec votre clone
VoxPelli
@naomik: Je suis assez nouveau dans JS, donc j'aimerais quelques précisions. Le clone peu profond proposé par olli-k et que vous avez aimé semble (à mon humble avis, mais je me trompe peut-être) avoir les mêmes limites que la solution proposée par voxpelli. L'original et la fermeture peu profonde partageront également les mêmes «éléments», la mise à jour d'un élément dans l'original aura également un impact sur le clone peu profond. Ou?
bmorin
@bmornin, si vous utilisez la technique d'Olli K, la modification de l'objet d'origine ne provoquera pas de changement dans le nouvel objet.
Merci
1
@VoxPelli Le but d'un "clone" est d'isoler les changements de l'objet original, donc cette méthode est plus dangereuse.
Doug
1

J'ai implémenté une copie complète complète. Je pense que c'est le meilleur choix pour une méthode de clonage générique, mais elle ne gère pas les références cycliques.

Exemple d'utilisation:

parent = {'prop_chain':3}
obj = Object.create(parent)
obj.a=0; obj.b=1; obj.c=2;

obj2 = copy(obj)

console.log(obj, obj.prop_chain)
// '{'a':0, 'b':1, 'c':2} 3
console.log(obj2, obj2.prop_chain)
// '{'a':0, 'b':1, 'c':2} 3

parent.prop_chain=4
obj2.a = 15

console.log(obj, obj.prop_chain)
// '{'a':0, 'b':1, 'c':2} 4
console.log(obj2, obj2.prop_chain)
// '{'a':15, 'b':1, 'c':2} 4

Le code lui-même:

Ce code copie les objets avec leurs prototypes, il copie également des fonctions (peut être utile pour quelqu'un).

function copy(obj) {
  // (F.prototype will hold the object prototype chain)
  function F() {}
  var newObj;

  if(typeof obj.clone === 'function')
    return obj.clone()

  // To copy something that is not an object, just return it:
  if(typeof obj !== 'object' && typeof obj !== 'function' || obj == null)
    return obj;

  if(typeof obj === 'object') {    
    // Copy the prototype:
    newObj = {}
    var proto = Object.getPrototypeOf(obj)
    Object.setPrototypeOf(newObj, proto)
  } else {
    // If the object is a function the function evaluate it:
    var aux
    newObj = eval('aux='+obj.toString())
    // And copy the prototype:
    newObj.prototype = obj.prototype
  }

  // Copy the object normal properties with a deep copy:
  for(var i in obj) {
    if(obj.hasOwnProperty(i)) {
      if(typeof obj[i] !== 'object')
        newObj[i] = obj[i]
      else
        newObj[i] = copy(obj[i])
    }
  }

  return newObj;
}

Avec cette copie, je ne trouve aucune différence entre l'original et le copié, sauf si l'original a utilisé des fermetures sur sa construction, donc je pense que c'est une bonne mise en œuvre.

J'espère que ça aide

VinGarcia
la source
1

pour le tableau, on peut utiliser

var arr = [1,2,3]; 
var arr_2 = arr ; 

print ( arr_2 ); 

arr = arr.slice (0);

print ( arr ); 

arr[1]=9999; 

print ( arr_2 ); 
tlqtangok
la source
1

Les objets et les tableaux en JavaScript utilisent l'appel par référence, si vous mettez à jour la valeur copiée, cela peut se refléter sur l'objet d'origine. Pour éviter cela, vous pouvez cloner l'objet en profondeur, pour empêcher la transmission de la référence, à l'aide de la commande d' exécution de la méthode cloneDeep de la bibliothèque lodash

npm installer lodash

const ld = require('lodash')
const objectToCopy = {name: "john", age: 24}
const clonedObject = ld.cloneDeep(objectToCopy)
vinay dagar
la source