Quelle est la façon la plus efficace de cloner en profondeur un objet en JavaScript?

5180

Quelle est la façon la plus efficace de cloner un objet JavaScript? J'ai vu obj = eval(uneval(o));être utilisé, mais ce n'est pas standard et n'est pris en charge que par Firefox .

J'ai fait des choses comme obj = JSON.parse(JSON.stringify(o));mais remettre en question l'efficacité.

J'ai également vu des fonctions de copie récursives avec divers défauts.
Je suis surpris qu'aucune solution canonique n'existe.

jschrab
la source
566
Eval n'est pas mal. Utiliser mal eval est. Si vous avez peur de ses effets secondaires, vous l'utilisez mal. Les effets secondaires que vous craignez sont les raisons de l'utiliser. Est-ce que quelqu'un a répondu à votre question?
James
15
Le clonage d'objets est une entreprise délicate, en particulier avec des objets personnalisés de collections arbitraires. C'est probablement la raison pour laquelle il n'y a pas de méthode prête à l'emploi.
b01
12
eval()est généralement une mauvaise idée car de nombreux optimiseurs de moteur Javascript doivent être désactivés lorsqu'ils traitent des variables définies viaeval . Le simple fait d'avoir eval()dans votre code peut conduire à de moins bonnes performances.
user56reinstatemonica8
2
Duplication possible de la manière la plus élégante de cloner un objet JavaScript
John Slegers
12
Notez que la JSONméthode perdra tous les types Javascript qui n'ont pas d'équivalent dans JSON. Par exemple: JSON.parse(JSON.stringify({a:null,b:NaN,c:Infinity,d:undefined,e:function(){},f:Number,g:false}))générera{a: null, b: null, c: null, g: false}
oriadam

Réponses:

4731

Clonage profond natif

Cela s'appelle le "clonage structuré", fonctionne expérimentalement dans Node 11 et versions ultérieures et, espérons-le, atterrira dans les navigateurs. Voir cette réponse pour plus de détails.

Clonage rapide avec perte de données - JSON.parse / stringify

Si vous n'utilisez pas Dates, fonctions undefined, Infinityexpressions rationnelles, Cartes, Ensembles, Blobs, filelists, ImageDatas, tableaux rares, tableaux dactylographié ou d' autres types complexes au sein de votre objet, une doublure très simple clone profond d' un objet est:

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

Voir la réponse de Corban pour les repères.

Clonage fiable à l'aide d'une bibliothèque

Étant donné que le clonage d'objets n'est pas trivial (types complexes, références circulaires, fonction, etc.), la plupart des bibliothèques principales fournissent une fonction pour cloner des objets. Ne réinventez pas la roue - si vous utilisez déjà une bibliothèque, vérifiez si elle a une fonction de clonage d'objet. Par exemple,

ES6

Pour être complet, notez que ES6 propose deux mécanismes de copie superficielle: Object.assign()et la syntaxe de propagation . qui copie les valeurs de toutes les propriétés propres énumérables d'un objet à un autre. Par exemple:

var A1 = {a: "2"};
var A2 = Object.assign({}, A1);
var A3 = {...A1};  // Spread Syntax
Dan Dascalescu
la source
7
@ThiefMaster github.com/jquery/jquery/blob/master/src/core.js à la ligne 276 (il y a un peu de code qui fait autre chose mais le code pour "comment faire cela dans JS" est là :)
Rune FS
7
Voici le code JS derrière la copie complète
Alex W
194
Woah! Juste pour être très clair: aucune idée pourquoi cette réponse a été choisie comme bonne réponse, c'était une réponse aux réponses données ci-dessous: stackoverflow.com/a/122190/6524 (qui recommandait .clone(), ce qui n'est pas le bon code à utiliser) dans ce contexte). Malheureusement, cette question a subi tant de révisions que la discussion originale n'est même plus apparente! Veuillez simplement suivre les conseils de Corban et écrire une boucle ou copier les propriétés directement sur un nouvel objet, si vous vous souciez de la vitesse. Ou testez-le par vous-même!
John Resig
9
Il s'agit d'une question JavaScript (aucune mention de jQuery).
gphilip du
60
Comment procéder sans utiliser jQuery?
Awesomeness01
2266

Découvrez cette référence: http://jsben.ch/#/bWfk9

Lors de mes tests précédents où la vitesse était une préoccupation majeure, j'ai trouvé

JSON.parse(JSON.stringify(obj))

pour être le moyen le plus lent de cloner en profondeur un objet (il est plus lent que jQuery.extend avec un deepindicateur défini sur 10-20%).

jQuery.extend est assez rapide lorsque l' deepindicateur est défini sur false(clone superficiel). C'est une bonne option, car elle inclut une logique supplémentaire pour la validation de type et ne copie pas les propriétés non définies, etc., mais cela vous ralentira également un peu.

Si vous connaissez la structure des objets que vous essayez de cloner ou si vous pouvez éviter les tableaux imbriqués profonds, vous pouvez écrire une for (var i in obj)boucle simple pour cloner votre objet tout en vérifiant hasOwnProperty et ce sera beaucoup plus rapide que jQuery.

Enfin, si vous essayez de cloner une structure d'objet connue dans une boucle chaude, vous pouvez obtenir BEAUCOUP PLUS DE PERFORMANCES en insérant simplement la procédure de clonage et en construisant manuellement l'objet.

Les moteurs de trace JavaScript sont for..inoptimistes pour l'optimisation des boucles et la vérification de hasOwnProperty vous ralentira également. Clonage manuel lorsque la vitesse est un must absolu.

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

Attention à utiliser la JSON.parse(JSON.stringify(obj))méthode sur les Dateobjets - JSON.stringify(new Date())retourne une représentation sous forme de chaîne de la date au format ISO, qui JSON.parse() ne se reconvertit pas en Dateobjet. Voir cette réponse pour plus de détails .

De plus, veuillez noter que, dans Chrome 65 au moins, le clonage natif n'est pas la solution. Selon JSPerf, effectuer un clonage natif en créant une nouvelle fonction est presque 800x plus lent que d'utiliser JSON.stringify qui est incroyablement rapide sur toute la ligne.

Mise à jour pour ES6

Si vous utilisez Javascript ES6, essayez cette méthode native de clonage ou de copie superficielle.

Object.assign({}, obj);
Corban Brook
la source
4
@trysis Object.create ne clone pas l'objet, utilise l'objet prototype ... jsfiddle.net/rahpuser/yufzc1jt/2
rahpuser
105
Cette méthode supprimera également le keysde votre object, qui a functionspour valeur, car le JSONne prend pas en charge les fonctions.
Karlen Kishmiryan
39
Gardez également à l'esprit que l'utilisation JSON.parse(JSON.stringify(obj))d'objets de date convertira également la date en UTC dans la représentation sous forme de chaîne au format ISO8601 .
dnlgmzddr
31
L'approche JSON étouffe également les références circulaires.
remer riche
28
@velop, Object.assign ({}, objToClone) semble cependant faire un clone peu profond - en l'utilisant tout en jouant dans la console des outils de développement, le clone d'objet pointait toujours vers une référence de l'objet cloné. Je ne pense donc pas que ce soit vraiment applicable ici.
Garrett Simpson
473

En supposant que vous n'avez que des variables et pas de fonctions dans votre objet, vous pouvez simplement utiliser:

var newObject = JSON.parse(JSON.stringify(oldObject));
Sultan Shakir
la source
86
le con de cette approche que je viens de découvrir est que si votre objet a des fonctions (le mien a des getters et setters internes) alors celles-ci sont perdues lorsqu'elles sont strictes .. Si c'est tout ce dont vous avez besoin, cette méthode est très bien ..
Markive
31
@ Jason, La raison pour laquelle cette méthode est plus lente que la copie superficielle (sur un objet profond) est que cette méthode, par définition, copie en profondeur. Mais comme il JSONest implémenté en code natif (dans la plupart des navigateurs), cela sera considérablement plus rapide que l'utilisation de toute autre solution de copie profonde basée sur javascript, et peut parfois être plus rapide qu'une technique de copie superficielle basée sur javascript (voir: jsperf.com/cloning -un-objet / 79 ).
MiJyn
35
JSON.stringify({key: undefined}) //=> "{}"
Web_Designer
32
cette technique va également détruire tous les Dateobjets qui sont stockés à l'intérieur de l'objet, en les convertissant sous forme de chaîne.
fstab
13
Il ne parviendra pas à copier tout ce qui ne fait pas partie de la spécification JSON ( json.org )
cdmckay
397

Clonage structuré

La norme HTML comprend un algorithme de clonage / sérialisation structuré interne qui peut créer des clones profonds d'objets. Il est toujours limité à certains types intégrés, mais en plus des quelques types pris en charge par JSON, il prend également en charge les dates, les RegExps, les cartes, les ensembles, les objets blob, les listes de fichiers, les imagesDatas, les tableaux clairsemés, les tableaux typés et probablement plus à l'avenir . Il préserve également les références dans les données clonées, ce qui lui permet de prendre en charge des structures cycliques et récursives qui provoqueraient des erreurs pour JSON.

Prise en charge dans Node.js: expérimental 🙂

Le v8module dans Node.js actuellement (à partir de Node 11) expose directement l'API de sérialisation structurée , mais cette fonctionnalité est toujours marquée comme "expérimentale" et sujette à modification ou suppression dans les futures versions. Si vous utilisez une version compatible, le clonage d'un objet est aussi simple que:

const v8 = require('v8');

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

Prise en charge directe dans les navigateurs: peut-être éventuellement? 😐

Les navigateurs ne fournissent pas actuellement d'interface directe pour l'algorithme de clonage structuré, mais une structuredClone()fonction globale a été discutée dans whatwg / html # 793 sur GitHub . Tel qu'il est actuellement proposé, son utilisation dans la plupart des cas serait aussi simple que:

const clone = structuredClone(original);

Sauf si cela est livré, les implémentations de clones structurés des navigateurs ne sont exposées qu'indirectement.

Solution de contournement asynchrone: utilisable. 😕

La manière la plus simple de créer un clone structuré avec des API existantes consiste à publier les données via un port d'un MessageChannels . L'autre port émettra un messageévénement avec un clone structuré de l'attaché .data. Malheureusement, l'écoute de ces événements est nécessairement asynchrone et les alternatives synchrones sont moins pratiques.

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

Exemple d'utilisation:

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

Solutions de contournement synchrones: horribles! 🤢

Il n'y a pas de bonnes options pour créer des clones structurés de manière synchrone. Voici quelques astuces peu pratiques à la place.

history.pushState()et les history.replaceState()deux créent un clone structuré de leur premier argument et attribuent cette valeur à history.state. Vous pouvez l'utiliser pour créer un clone structuré de n'importe quel objet comme celui-ci:

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

Exemple d'utilisation:

Bien que synchrone, cela peut être extrêmement lent. Il entraîne tous les frais généraux associés à la manipulation de l'historique du navigateur. L'appel répété de cette méthode peut entraîner une absence temporaire de réponse de Chrome.

Le Notificationconstructeur crée un clone structuré de ses données associées. Il tente également d'afficher une notification du navigateur à l'utilisateur, mais cela échouera en silence, sauf si vous avez demandé l'autorisation de notification. Si vous avez l'autorisation à d'autres fins, nous fermerons immédiatement la notification que nous avons créée.

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

Exemple d'utilisation:

Jeremy Banks
la source
3
@rynah Je viens de regarder à nouveau la spécification et vous avez raison: les méthodes history.pushState()et sont history.replaceState()toutes deux synchronisées history.statesur un clone structuré de leur premier argument. Un peu bizarre, mais ça marche. Je mets à jour ma réponse maintenant.
Jeremy Banks
40
C'est tellement faux! Cette API n'est pas destinée à être utilisée de cette façon.
Fardin K.
209
En tant que gars qui a implémenté pushState dans Firefox, je ressens un étrange mélange de fierté et de répulsion à ce hack. Bravo les gars.
Justin L.
pushState ou Notification hack ne fonctionne pas pour certains types d'objets comme Function
Shishir Arora
323

S'il n'y en avait pas, vous pouvez essayer:

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;
}
ConroyP
la source
20
La solution JQuery fonctionnera pour les éléments DOM mais pas pour n'importe quel objet. Mootools a la même limite. J'aimerais qu'ils aient un "clone" générique pour n'importe quel objet ... La solution récursive devrait fonctionner pour n'importe quoi. C'est probablement la voie à suivre.
jschrab
5
Cette fonction est interrompue si l'objet cloné a un constructeur qui nécessite des paramètres. Il semble que nous pouvons le changer en "var temp = new Object ()" et le faire fonctionner dans tous les cas, non?
Andrew Arnott
3
Andrew, si vous le changez en var temp = new Object (), alors votre clone n'aura pas le même prototype que l'objet d'origine. Essayez d'utiliser: 'var newProto = function () {}; newProto.prototype = obj.constructor; var temp = new newProto (); '
limscoder
1
Semblable à la réponse de limscoder, voir ma réponse ci-dessous sur la façon de faire cela sans appeler le constructeur: stackoverflow.com/a/13333781/560114
Matt Browne
3
Pour les objets qui contiennent des références à des sous-parties (c'est-à-dire des réseaux d'objets), cela ne fonctionne pas: si deux références pointent vers le même sous-objet, la copie en contient deux copies différentes. Et s'il y a des références récursives, la fonction ne se terminera jamais (enfin, du moins pas comme vous le souhaitez :-) Pour ces cas généraux, vous devez ajouter un dictionnaire des objets déjà copiés, et vérifier si vous l'avez déjà copié ... La programmation est complexe lorsque vous utilisez un langage simple
virtualnobi
153

Le moyen efficace de cloner (pas de cloner en profondeur) un objet sur une seule ligne de code

Une Object.assignméthode fait partie de la norme ECMAScript 2015 (ES6) et fait exactement ce dont vous avez besoin.

var clone = Object.assign({}, obj);

La méthode Object.assign () est utilisée pour copier les valeurs de toutes les propriétés propres énumérables d'un ou plusieurs objets source vers un objet cible.

Lire la suite...

Le polyfill pour supporter les anciens navigateurs:

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;
    }
  });
}
Eugene Tiurin
la source
82
Cela ne copie pas récursivement et n'offre donc pas vraiment de solution au problème de clonage d'un objet.
mwhite
5
Cette méthode a fonctionné, même si j'en ai testé quelques-uns et que _.extend ({}, (obj)) était DE loin le plus rapide: 20 fois plus rapide que JSON.parse et 60% plus rapide que Object.assign, par exemple. Il copie assez bien tous les sous-objets.
Nico
11
@mwhite il y a une différence entre clone et deep-clone. Cette réponse est en fait clonée, mais pas en profondeur.
Meirion Hughes
57
l'op a demandé un clone profond. cela ne fait pas de clone profond.
user566245
9
Cette méthode fait une copie SHALLOW , et non une copie DEEP ! Pour cette raison, c'est une mauvaise réponse !
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;
}

Tester:

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);
Kamarey
la source
3
var obj = {}obj.a = obj
Qu'en est-
5
Je ne comprends pas cette fonction. Supposons que from.constructorc'est Datepar exemple. Comment le troisième iftest serait-il atteint lorsque le deuxième iftest réussirait et entraînerait le retour de la fonction (depuis Date != Object && Date != Array)?
Adam McKee
1
@AdamMcKee Parce que le passage d'arguments javascript et l'affectation de variables est délicat . Cette approche fonctionne très bien, y compris les dates (qui sont en effet gérées par le deuxième test) - violon à tester ici: jsfiddle.net/zqv9q9c6 .
brichins
1
@NickSweeting: Essayez - peut-être que cela fonctionne. Sinon - corrigez-le et mettez à jour la réponse. Voilà comment cela fonctionne ici dans la communauté :)
Kamarey
1
Cette fonction ne clone pas l'expression régulière dans le test, la condition "from.constructor! = Object && from.constructor! = Array" renvoie toujours true pour les autres constructeurs comme Number, Date, etc.
aMarCruz
95

Voici ce que j'utilise:

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;
}
Alan
la source
8
Cela ne semble pas correct. cloneObject({ name: null })=>{"name":{}}
Niyaz
13
Cela est dû à une autre chose stupide en javascript, typeof null > "object"mais Object.keys(null) > TypeError: Requested keys of a value that is not an object.changez la condition enif(typeof(obj[i])=="object" && obj[i]!=null)
Vitim.us
Cela affectera les propriétés énumérables héritées d' obj directement au clone et suppose que obj est un objet simple.
RobG
Cela perturbe également les tableaux, qui sont convertis en objets avec des touches numériques.
lame
Pas de problème si vous n'utilisez pas null.
Jorge Bucaran
78

Copie approfondie par performance: classée du meilleur au pire

  • Réaffectation "=" (tableaux de chaînes, tableaux de nombres - uniquement)
  • Slice (tableaux de chaînes, tableaux de nombres - uniquement)
  • Concaténation (tableaux de chaînes, tableaux de nombres - uniquement)
  • Fonction personnalisée: copie en boucle ou récursive
  • $ .extend de jQuery
  • JSON.parse (tableaux de chaînes, tableaux de nombres, tableaux d'objets - uniquement)
  • Underscore.js de _.clone l »(des tableaux de chaînes, tableaux de numéros - uniquement)
  • _.CloneDeep de Lo-Dash

Copie complète d'un tableau de chaînes ou de nombres (un niveau - pas de pointeurs de référence):

Lorsqu'un tableau contient des nombres et des chaînes - des fonctions comme .slice (), .concat (), .splice (), l'opérateur d'affectation "=" et la fonction clone d'Underscore.js; fera une copie complète des éléments du tableau.

Là où la réaffectation a les performances les plus rapides:

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

Et .slice () a de meilleures performances que .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

Copie en profondeur d'un tableau d'objets (deux niveaux ou plus - pointeurs de référence):

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

Écrivez une fonction personnalisée (a des performances plus rapides que $ .extend () ou 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);

Utilisez des fonctions utilitaires tierces:

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

Où $ .extend de jQuery a de meilleures performances:

tfmontague
la source
J'en ai testé quelques-uns et _.extend ({}, (obj)) était DE loin le plus rapide: 20 fois plus rapide que JSON.parse et 60% plus rapide que Object.assign, par exemple. Il copie assez bien tous les sous-objets.
Nico
4
Tous vos exemples sont superficiels, un niveau. Ce n'est pas une bonne réponse. La question concernait le clonage profond, c'est-à-dire au moins deux niveaux.
Karl Morrison
1
Une copie profonde est lorsqu'un objet est copié dans son intégralité sans utiliser de pointeurs de référence vers d'autres objets. Les techniques de la section "Copie en profondeur d'un tableau d'objets", telles que jQuery.extend () et la fonction personnalisée (qui est récursive) copient des objets avec "au moins deux niveaux". Donc, tous les exemples ne sont pas des copies "à un niveau".
tfmontague
1
J'aime votre fonction de copie personnalisée, mais vous devez exclure les valeurs nulles, sinon toutes les valeurs nulles sont converties en objets, c'est-à-dire:out[key] = (typeof v === "object" && v !== null) ? copy(v) : v;
josi
2
@HossamMourad - Le bug a été corrigé par Josi le 1er février (dans le commentaire ci-dessus), et je n'ai pas correctement mis à jour la réponse. Désolé, ce bogue a entraîné la refonte de votre base de code.
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});
Zibri
la source
Bonne réponse mais cela échoue pour les références circulaires.
Luke
59

Copie en profondeur d'objets en JavaScript (je pense que le meilleur et le plus simple)

1. Utilisation de JSON.parse (JSON.stringify (object));

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.Utilisation de la méthode créée

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. Utilisation du lodash de lien _.cloneDeep de 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. Utilisation de la méthode Object.assign ()

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 }  

MAIS MAUVAIS QUAND

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.Utilisation du lien Underscore.js _.clone 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 }  

MAIS MAUVAIS QUAND

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 Performance Copie profonde d'objets en JavaScript

Tính Ngô Quang
la source
5
Object.assign()n'effectue pas de copie
complète
1
vous devez ajouter des repères pour ceux-ci; ce serait très utile
jcollum
quand j'ai utilisé la "méthode créée" sur un objet contenant un tableau je n'ai pas pu utiliser pop () ou splice () dessus, je ne comprends pas pourquoi? let data = {title:["one", "two"]}; let tmp = cloneObject(data); tmp.title.pop();il jette: TypeError: tmp.title.pop is not a function(bien sûr pop () fonctionne bien si je viens do let tmp = data; mais alors je ne peux pas modifier tmp sans affecter les données)
hugogogo
Hé, votre dernier exemple est faux. À mon avis, vous devez utiliser _clone et non _cloneDeep pour le mauvais exemple.
kenanyildiz
Cette méthode créée (2.) ne fonctionnera pas pour les tableaux, n'est-ce pas?
Toivo Säwén
57

Il y a une bibliothèque (appelée "clone") , qui fait cela très bien. Il fournit le clonage / copie récursif le plus complet d'objets arbitraires que je connaisse. Il prend également en charge les références circulaires, qui ne sont pas encore couvertes par les autres réponses.

Vous pouvez également le trouver sur npm . Il peut être utilisé pour le navigateur ainsi que Node.js.

Voici un exemple sur la façon de l'utiliser:

Installez-le avec

npm install clone

ou emballez-le avec Ender .

ender build clone [...]

Vous pouvez également télécharger le code source manuellement.

Ensuite, vous pouvez l'utiliser dans votre code source.

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

(Avertissement: je suis l'auteur de la bibliothèque.)

pvorb
la source
3
Le clone npm m'a été précieux pour cloner des objets imbriqués arbitrairement. C'est la bonne réponse.
Andy Ray
quelle est la performance de votre lib par rapport à disons JSON.parse(JSON.stringify(obj))?
pkyeck
Voici une bibliothèque qui indique qu'il existe des options plus rapides. N'ont cependant pas été testés.
pvorb
Bonne solution et cela prend en charge les références circulaires (contrairement à l'analyse JSON)
Luke
55

Cloning un objet a toujours été un problème dans JS, mais c'était avant ES6, je liste ci-dessous différentes façons de copier un objet en JavaScript, imaginez que vous avez l'objet ci-dessous et que vous souhaitez en avoir une copie complète:

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

Il existe plusieurs façons de copier cet objet sans modifier son origine:

1) ES5 +, en utilisant une fonction simple pour faire la copie pour vous:

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 +, en utilisant JSON.parse et 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) UnderscoreJs & Loadash:

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

J'espère que ces aides ...

Alireza
la source
2
clone dans le soulignement n'est pas un clone profond dans la version actuelle
Rogelio
Merci. oui comme nouveau document pour Underscore ... clone_.clone (object) Créez un clone copié peu profond de l'objet ordinaire fourni. Tous les objets ou tableaux imbriqués seront copiés par référence et non dupliqués. _.clone ({nom: 'moe'}); => {nom: 'moe'};
Alireza
59
Object.assignne copie pas en profondeur. Exemple: var x = { a: { b: "c" } }; var y = Object.assign({}, x); x.a.b = "d". Si c'était une copie complète, ce y.a.bserait toujours le cas c, mais c'est maintenant d.
kba
8
Object.assign () clone uniquement le premier niveau de propriétés!
haemse
5
qu'est-ce que la fonction cloneSO ()?
pastorello
53

Je sais que c'est un vieux post, mais je pensais que cela pourrait être utile à la prochaine personne qui trébucherait.

Tant que vous n'assignez aucun objet à quelque chose, il ne conserve aucune référence en mémoire. Donc, pour créer un objet que vous souhaitez partager entre d'autres objets, vous devrez créer une usine comme ceci:

var a = function(){
    return {
        father:'zacharias'
    };
},
b = a(),
c = a();
c.father = 'johndoe';
alert(b.father);
Joe
la source
16
Cette réponse n'est pas vraiment pertinente car la question est la suivante: étant donné l'instance b comment créer une copie c TANDIS de ne pas connaître l'usine a ou de ne pas vouloir utiliser l'usine a. La raison pour laquelle on peut ne pas vouloir utiliser l'usine est qu'après l'instanciation, b peut avoir été initialisé avec des données supplémentaires (par exemple, entrée utilisateur).
Noel Abrahams
12
Il est vrai que ce n'est pas vraiment une réponse à la question, mais je pense qu'il est important que ce soit ici parce que c'est la réponse à la question que je soupçonne que beaucoup de gens qui viennent ici ont vraiment l'intention de poser.
Point
8
Désolé les gars, je ne comprends pas vraiment pourquoi tant de votes positifs. Le clonage d'un objet est un concept assez clair, vous cônez un objet à partir d'un AUTRE objet, et cela n'a pas grand-chose à voir avec la création d'un nouveau avec le modèle d'usine.
opensas
2
Bien que cela fonctionne pour les objets prédéfinis, le "clonage" de cette manière ne reconnaîtra pas les nouvelles propriétés ajoutées à l'objet d'origine. Si vous créez un, ajoutez une nouvelle propriété à a, puis créez b. b n'aura pas la nouvelle propriété. Le modèle d'usine est essentiellement immuable aux nouvelles propriétés. Ce n'est pas un clonage paradigmatique. Voir: jsfiddle.net/jzumbrun/42xejnbx
Jon
1
Je pense que c'est un bon conseil, généralement, car au lieu de l'utiliser, const defaultFoo = { a: { b: 123 } };vous pouvez y aller const defaultFoo = () => ({ a: { b: 123 } };et votre problème est résolu. Cependant, ce n'est vraiment pas une réponse à la question. Cela aurait peut-être été plus logique en tant que commentaire sur la question, et non en tant que réponse complète.
Josh de Qaribou
48

Si vous l'utilisez, la bibliothèque Underscore.js possède une méthode de clonage .

var newObject = _.clone(oldObject);
itsadok
la source
24
lodash a une méthode cloneDeep, il prend également en charge un autre paramètre à cloner pour le rendre plus profond: lodash.com/docs#clone et lodash.com/docs#cloneDeep
opensas
12
@opensas a accepté. Lodash est généralement supérieur au trait de soulignement
nha
7
Je préconise de supprimer cela et toutes les autres réponses qui ne sont que des références d'une ligne à la .clone(...)méthode d'une bibliothèque d'utilitaires . Chaque grande bibliothèque en aura, et les brèves réponses non détaillées répétées ne sont pas utiles à la plupart des visiteurs, qui n'utiliseront pas cette bibliothèque particulière.
Jeremy Banks
41

Voici une version de la réponse de ConroyP ci-dessus qui fonctionne même si le constructeur a des paramètres requis:

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

Cette fonction est également disponible dans ma bibliothèque simpleoo .

Éditer:

Voici une version plus robuste (grâce à Justin McCandless, elle prend désormais également en charge les références cycliques):

/**
 * 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();
    };
}
Matt Browne
la source
30

Ce qui suit crée deux instances du même objet. Je l'ai trouvé et je l'utilise actuellement. C'est simple et facile à utiliser.

var objToCreate = JSON.parse(JSON.stringify(cloneThis));
nathan rogers
la source
Y a-t-il quelque chose de mal avec cette réponse? C'est plus utile car c'est une solution autonome, mais simple; mais la solution jQuery est plus populaire. Pourquoi donc?
ceremcem
Oui, faites-le moi savoir. Il semble fonctionner comme prévu, s'il y a une rupture cachée quelque part, je dois utiliser une solution différente.
nathan rogers
4
Pour un objet simple, cela est environ 6 fois plus lent dans Chrome que la réponse donnée, et devient beaucoup plus lent à mesure que la complexité de l'objet augmente. Il évolue terriblement et peut goulot d'étranglement votre application très rapidement.
tic
1
Vous n'avez pas besoin de données, juste une compréhension de ce qui se passe. Cette technique de clonage sérialise l'objet entier dans une chaîne, puis analyse cette sérialisation de chaîne pour créer un objet. En soi, cela va être beaucoup plus lent que de simplement réorganiser de la mémoire (ce que font les clones les plus sophistiqués). Mais cela étant dit, pour les projets de petite à moyenne taille (en fonction de votre définition de «moyenne»), peu importe si c'est 1000 fois moins efficace? Si vos objets sont petits et que vous ne les clonez pas, une tonne 1000x de pratiquement rien n'est encore pratiquement rien.
machineghost
3
De plus, cette méthode perd des méthodes (ou tout ce qui n'est pas autorisé dans JSON), plus - JSON.stringify convertira les objets Date en chaînes, ... et non l'inverse;) Restez à l'écart de cette solution.
Mr MT
22

Crockford suggère (et je préfère) d'utiliser cette fonction:

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

var newObject = object(oldObject);

C'est concis, fonctionne comme prévu et vous n'avez pas besoin d'une bibliothèque.


ÉDITER:

Il s'agit d'un polyfill pour Object.create, vous pouvez donc également l'utiliser.

var newObject = Object.create(oldObject);

Remarque: si vous utilisez une partie de cela, vous pouvez avoir des problèmes avec certaines itérations qui utilisent hasOwnProperty. Parce que, createcréez un nouvel objet vide qui hérite oldObject. Mais il reste utile et pratique pour cloner des objets.

Par exemple si oldObject.a = 5;

newObject.a; // is 5

mais:

oldObject.hasOwnProperty(a); // is true
newObject.hasOwnProperty(a); // is false
protonfish
la source
9
corrigez-moi si je me trompe, mais n'est-ce pas la fonction de génération de Crockford pour l'héritage prototypique? Comment s'applique-t-il au clone?
Alex Nolasco
3
Oui, j'avais peur de cette discussion: quelle est la différence pratique entre l'héritage clone, copie et prototypique, quand devriez-vous utiliser chacun et quelles fonctions sur cette page font réellement quoi? J'ai trouvé cette page SO en recherchant «objet de copie javascript» sur Google. Ce que je cherchais vraiment, c'était la fonction ci-dessus, alors je suis revenu pour partager. Je suppose que le demandeur cherchait aussi cela.
Chris Broski
51
La différence entre clone / copy et héritage est que, en utilisant votre exemple, lorsque je change une propriété de oldObject, la propriété est également modifiée dans newObject. Si vous faites une copie, vous pouvez faire ce que vous voulez avec oldObject sans changer newObject.
Ridcully
13
Cela interrompra la vérification de hasOwnProperty, c'est donc un moyen assez hacky de cloner un objet et vous donnera des résultats inattendus.
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 a une belle méthode _.cloneDeep (value) :

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

var deep = _.cloneDeep(objects);
console.log(deep[0] === objects[0]);
// => false
opensas
la source
5
Je préconise de supprimer cela et toutes les autres réponses qui ne sont que des références d'une ligne à la .clone(...)méthode d'une bibliothèque d'utilitaires . Chaque grande bibliothèque en aura, et les brèves réponses non détaillées répétées ne sont pas utiles à la plupart des visiteurs, qui n'utiliseront pas cette bibliothèque particulière.
Jeremy Banks
Un moyen plus simple consiste à utiliser _.merge({}, objA). Si seulement lodash n'a pas muté les objets en premier lieu, alors la clonefonction ne serait pas nécessaire.
Rebs
7
Les recherches Google pour le clonage d'objets JS se réfèrent ici. J'utilise Lodash donc cette réponse est pertinente pour moi. Permet de ne pas aller tous "suppression de wikipedia" sur les réponses s'il vous plaît.
Rebs
2
Dans le nœud 9, JSON.parse (JSON.stringify (arrayOfAbout5KFlatObjects)) est beaucoup plus rapide que _.deepClone (arrayOfAbout5KFlatObjects).
Dan Dascalescu
21
function clone(obj)
 { var clone = {};
   clone.prototype = obj.prototype;
   for (property in obj) clone[property] = obj[property];
   return clone;
 }
Mark Cidade
la source
17
Le problème avec la méthode, c'est que si vous avez des sous-objets dans l'obj, leurs références seront clonées, et non les valeurs de chaque sous-objet.
Kamarey
1
il suffit de le rendre récursif pour que les sous-objets soient clonés en profondeur.
fiatjaf
juste curieux ... ne sera pas la variable clone aura les pointeurs vers les propriétés de l'objet d'origine? car il ne semble pas y avoir de nouvelle allocation de mémoire
Rupesh Patel
3
Oui. Ceci est juste une copie superficielle, donc le clone pointera vers les mêmes objets pointés par l'objet d'origine.
Mark Cidade
Ce n'est pas une réponse. Vous bourrez littéralement un objet avec des références à un autre objet. Apporter des modifications à l'objet source apportera des modifications au "clone".
Shawn Whinnery
20

Copie simple à une ligne ( ECMAScript 5e édition ):

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

Et une copie peu profonde ( ECMAScript 6e édition , 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
Maël Nison
la source
6
Cela peut convenir pour des objets simples, mais il copie uniquement les valeurs des propriétés. Il ne touche pas la chaîne prototype et en l'utilisant Object.keysignore les propriétés non énumérables et héritées. En outre, il perd les descripteurs de propriété en effectuant une affectation directe.
Matt Bierner
Si vous copiez aussi le prototype, il ne vous manquerait que les non énumérables et les descripteurs de propriété, oui? Assez bien. :)
sam
Mis à part les performances, il s'agit d'un moyen très pratique de copier un objet en profondeur. Je l'utilise souvent pour trier de fausses propriétés de repos dans une affectation de déstructuration dans mes composants React.
mjohnsonengr
17

Juste parce que je n'ai pas vu AngularJS mentionné et j'ai pensé que les gens pourraient vouloir savoir ...

angular.copy fournit également une méthode de copie en profondeur d'objets et de tableaux.

Dan Atkinson
la source
ou il pourrait être utilisé de la même manière que jQiery extend:angular.extend({},obj);
Galvani
2
@Galvani: Il convient de noter que jQuery.extendet angular.extendsont à la fois des copies peu profondes. angular.copyest une copie profonde.
Dan Atkinson
16

Il ne semble pas encore y avoir d'opérateur de clone profond idéal pour les objets de type tableau. Comme l'illustre le code ci-dessous, le cloneur jQuery de John Resig transforme les tableaux avec des propriétés non numériques en objets qui ne sont pas des tableaux, et le cloner JSON de RegDwight supprime les propriétés non numériques. Les tests suivants illustrent ces points sur plusieurs navigateurs:

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)
Page Notes
la source
14
comme d'autres l'ont souligné dans les commentaires de la réponse de Resig, si vous voulez cloner un objet de type tableau, vous changez le {} en [] dans l'appel étendu, par exemple jQuery.extend (true, [], obj)
Anentropic
15

J'ai deux bonnes réponses selon que votre objectif est de cloner un "ancien objet JavaScript" ou non.

Supposons également que votre intention est de créer un clone complet sans référence de prototype à l'objet source. Si vous n'êtes pas intéressé par un clone complet, vous pouvez utiliser la plupart des routines Object.clone () fournies dans certaines des autres réponses (modèle de Crockford).

Pour les anciens objets JavaScript simples, une bonne façon éprouvée de cloner un objet dans les runtimes modernes est tout simplement:

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

Notez que l'objet source doit être un objet JSON pur. Cela signifie que toutes ses propriétés imbriquées doivent être scalaires (comme booléen, chaîne, tableau, objet, etc.). Aucune fonction ou objet spécial comme RegExp ou Date ne sera cloné.

Est-ce efficace? Zut oui. Nous avons essayé toutes sortes de méthodes de clonage et cela fonctionne mieux. Je suis sûr que certains ninja pourraient évoquer une méthode plus rapide. Mais je pense que nous parlons de gains marginaux.

Cette approche est simplement simple et facile à mettre en œuvre. Enveloppez-le dans une fonction de commodité et si vous avez vraiment besoin d'extraire du gain, optez pour plus tard.

Maintenant, pour les objets JavaScript non simples, il n'y a pas de réponse vraiment simple. En fait, cela ne peut pas être dû à la nature dynamique des fonctions JavaScript et à l'état de l'objet interne. Le clonage en profondeur d'une structure JSON avec des fonctions à l'intérieur nécessite de recréer ces fonctions et leur contexte interne. Et JavaScript n'a tout simplement pas de façon standardisée de le faire.

La bonne façon de le faire, encore une fois, est via une méthode pratique que vous déclarez et réutilisez dans votre code. La méthode pratique peut être dotée d'une certaine compréhension de vos propres objets afin que vous puissiez vous assurer de recréer correctement le graphique dans le nouvel objet.

Nous sommes nos propres écrits, mais la meilleure approche générale que j'ai vue est couverte ici:

http://davidwalsh.name/javascript-clone

C'est la bonne idée. L'auteur (David Walsh) a commenté le clonage de fonctions généralisées. C'est quelque chose que vous pourriez choisir de faire, selon votre cas d'utilisation.

L'idée principale est que vous devez gérer spécifiquement l'instanciation de vos fonctions (ou classes prototypiques, pour ainsi dire) par type. Ici, il a fourni quelques exemples pour RegExp et Date.

Non seulement ce code est bref, mais il est également très lisible. C'est assez facile à étendre.

Est-ce efficace? Zut oui. Étant donné que le but est de produire un véritable clone de copie profonde, vous devrez parcourir les membres du graphique d'objet source. Avec cette approche, vous pouvez modifier exactement les membres enfants à traiter et comment gérer manuellement les types personnalisés.

Alors voilà. Deux approches. Les deux sont efficaces à mon avis.

Michael Uzquiano
la source
13

Ce n'est généralement pas la solution la plus efficace, mais elle fait ce dont j'ai besoin. Cas de test simples ci-dessous ...

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

Test de réseau cyclique ...

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

Test de fonctionnalité...

f = new Function
f.a = a
ff = clone(f)
ff === f //=> true
ff.a === a //=> false
neatonk
la source
11

AngularJS

Eh bien, si vous utilisez angulaire, vous pouvez aussi le faire

var newObject = angular.copy(oldObject);
azerafati
la source
11

Je ne suis pas d'accord avec la réponse avec les plus grands votes ici . Un clone profond récursif est beaucoup plus rapide que l' approche JSON.parse (JSON.stringify (obj)) mentionnée.

Et voici la fonction de référence rapide:

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
}
prograhammer
la source
2
J'ai aimé cette approche mais elle ne gère pas correctement les dates; pensez à ajouter quelque chose comme if(o instanceof Date) return new Date(o.valueOf());après avoir vérifié la valeur null `
Luis
Crashes sur les références circulaires.
Harry
Dans le dernier Firefox stable, c'est bien plus long que les autres stratégies de ce lien Jsben.ch, par ordre de grandeur ou plus. Il bat les autres dans la mauvaise direction.
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;
};
Dima
la source
10

Uniquement lorsque vous pouvez utiliser ECMAScript 6 ou des transpilers .

Fonctionnalités:

  • Ne déclenchera pas getter / setter pendant la copie.
  • Préserve le getter / setter.
  • Préserve les informations du prototype.
  • Fonctionne avec les styles d'écriture OO littéraux et fonctionnels .

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;
}
andrew
la source
9

Voici une méthode clone () complète qui peut cloner n'importe quel objet JavaScript. Il gère presque tous les cas:

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;
};
user1547016
la source
Il convertit les primitives en objets wrapper, ce qui n'est pas une bonne solution dans la plupart des cas.
Danubian Sailor
@DanubianSailor - Je ne pense pas que ce soit le cas ... il semble renvoyer les primitives tout de suite et ne semble pas leur faire quoi que ce soit qui les transformerait en objets wrapper au fur et à mesure de leur retour.
Jimbo Jonny