Comment cloner correctement un objet JavaScript?

3080

J'ai un objet x. Je voudrais le copier comme objet y, de telle sorte que les changements à yne pas modifierx . J'ai réalisé que la copie d'objets dérivés d'objets JavaScript intégrés entraînerait des propriétés supplémentaires et indésirables. Ce n'est pas un problème, car je copie un de mes propres objets construits littéralement.

Comment cloner correctement un objet JavaScript?

soundly_typed
la source
31
Voir cette question: stackoverflow.com/questions/122102/…
Niyaz
257
Pour JSON, j'utilisemObj=JSON.parse(JSON.stringify(jsonObject));
Lord Loh.
68
Je ne comprends vraiment pas pourquoi personne ne le suggère Object.create(o), il fait tout ce que l'auteur demande?
froginvasion
45
var x = { deep: { key: 1 } }; var y = Object.create(x); x.deep.key = 2; Après cela, y.deep.keysera également 2, donc Object.create NE PEUT PAS ÊTRE UTILISÉ pour le clonage ...
Ruben Stolk
18
@ r3wt qui ne fonctionnera pas ... Veuillez poster uniquement après avoir fait un test de base de la solution ..
akshay

Réponses:

1562

Faire cela pour n'importe quel objet en JavaScript ne sera ni simple ni direct. Vous rencontrerez le problème de la récupération par erreur d'attributs du prototype de l'objet qui doivent être laissés dans le prototype et non copiés dans la nouvelle instance. Si, par exemple, vous ajoutez une cloneméthode Object.prototype, comme le montrent certaines réponses, vous devrez ignorer explicitement cet attribut. Mais que se passe-t-il s'il y a d'autres méthodes supplémentaires ajoutées Object.prototypeou d'autres prototypes intermédiaires que vous ne connaissez pas? Dans ce cas, vous copiez les attributs que vous ne devriez pas, vous devez donc détecter les attributs non locaux imprévus avec la hasOwnPropertyméthode.

En plus des attributs non énumérables, vous rencontrerez un problème plus difficile lorsque vous essayez de copier des objets qui ont des propriétés masquées. Par exemple, prototypeest une propriété cachée d'une fonction. De plus, le prototype d'un objet est référencé avec l'attribut __proto__, qui est également masqué, et ne sera pas copié par une boucle for / in itérant sur les attributs de l'objet source. Je pense que cela __proto__peut être spécifique à l'interpréteur JavaScript de Firefox et que cela peut être quelque chose de différent dans d'autres navigateurs, mais vous obtenez l'image. Tout n'est pas énumérable. Vous pouvez copier un attribut caché si vous connaissez son nom, mais je ne connais aucun moyen de le découvrir automatiquement.

Un autre problème dans la recherche d'une solution élégante est le problème de la configuration correcte de l'héritage du prototype. Si le prototype de votre objet source est Object, alors créer simplement un nouvel objet général avec {}fonctionnera, mais si le prototype de la source est un descendant de Object, alors vous allez manquer les membres supplémentaires de ce prototype que vous avez sauté en utilisant le hasOwnPropertyfiltre, ou qui étaient dans le prototype, mais n'étaient pas énumérables en premier lieu. Une solution peut être d'appeler la constructorpropriété de l'objet source pour obtenir l'objet de copie initial, puis de copier les attributs, mais vous n'obtiendrez toujours pas d'attributs non énumérables. Par exemple, un Dateobjet stocke ses données en tant que membre masqué:

function clone(obj) {
    if (null == obj || "object" != typeof obj) return obj;
    var copy = obj.constructor();
    for (var attr in obj) {
        if (obj.hasOwnProperty(attr)) copy[attr] = obj[attr];
    }
    return copy;
}

var d1 = new Date();

/* Executes function after 5 seconds. */
setTimeout(function(){
    var d2 = clone(d1);
    alert("d1 = " + d1.toString() + "\nd2 = " + d2.toString());
}, 5000);

La chaîne de date pour d1aura 5 secondes de retard sur celle de d2. Un moyen de rendre l'un Dateidentique à l'autre consiste à appeler la setTimeméthode, mais cela est spécifique à la Dateclasse. Je ne pense pas qu'il existe une solution générale pare-balles à ce problème, même si je serais heureux de me tromper!

Quand je devais mettre en œuvre profonde générale copie j'ai fini par compromettre en supposant que je seulement besoin de copier une plaine Object, Array, Date, String, Numberou Boolean. Les 3 derniers types sont immuables, donc je pourrais effectuer une copie superficielle et ne pas m'inquiéter de son changement. J'ai en outre supposé que tous les éléments contenus dans Objectou Arrayseraient également l'un des 6 types simples de cette liste. Cela peut être accompli avec du code comme celui-ci:

function clone(obj) {
    var copy;

    // Handle the 3 simple types, and null or undefined
    if (null == obj || "object" != typeof obj) return obj;

    // Handle Date
    if (obj instanceof Date) {
        copy = new Date();
        copy.setTime(obj.getTime());
        return copy;
    }

    // Handle Array
    if (obj instanceof Array) {
        copy = [];
        for (var i = 0, len = obj.length; i < len; i++) {
            copy[i] = clone(obj[i]);
        }
        return copy;
    }

    // Handle Object
    if (obj instanceof Object) {
        copy = {};
        for (var attr in obj) {
            if (obj.hasOwnProperty(attr)) copy[attr] = clone(obj[attr]);
        }
        return copy;
    }

    throw new Error("Unable to copy obj! Its type isn't supported.");
}

La fonction ci-dessus fonctionnera correctement pour les 6 types simples que j'ai mentionnés, tant que les données dans les objets et les tableaux forment une structure arborescente. Autrement dit, il n'y a pas plus d'une référence aux mêmes données dans l'objet. Par exemple:

// This would be cloneable:
var tree = {
    "left"  : { "left" : null, "right" : null, "data" : 3 },
    "right" : null,
    "data"  : 8
};

// This would kind-of work, but you would get 2 copies of the 
// inner node instead of 2 references to the same copy
var directedAcylicGraph = {
    "left"  : { "left" : null, "right" : null, "data" : 3 },
    "data"  : 8
};
directedAcyclicGraph["right"] = directedAcyclicGraph["left"];

// Cloning this would cause a stack overflow due to infinite recursion:
var cyclicGraph = {
    "left"  : { "left" : null, "right" : null, "data" : 3 },
    "data"  : 8
};
cyclicGraph["right"] = cyclicGraph;

Il ne sera pas en mesure de gérer n'importe quel objet JavaScript, mais il peut être suffisant à de nombreuses fins tant que vous ne supposez pas qu'il fonctionnera uniquement pour tout ce que vous lui lancerez.

A. Levy
la source
5
a presque bien fonctionné dans un nodejs - a juste dû changer la ligne pour (var i = 0, var len = obj.length; i <len; ++ i) {to for (var i = 0; i <obj.length; ++ i) {
Trindaz

5
Pour les futurs googleurs: même copie complète, passant les références de manière récursive au lieu d'utiliser des instructions 'return' sur gist.github.com/2234277
Trindaz

8
Serait-ce aujourd'hui JSON.parse(JSON.stringify([some object]),[some revirer function])une solution?
KooiInc

5
Dans le premier extrait, êtes-vous sûr que cela ne devrait pas l'être var cpy = new obj.constructor()?
cyon

8
L'attribution d'objet ne semble pas faire une véritable copie dans Chrome, maintient la référence à l'objet d'origine - a fini par utiliser JSON.stringify et JSON.parse pour cloner - a parfaitement fonctionné
1owk3y

975

Si vous n'utilisez pas Dates, les fonctions, undefined, regExp ou Infinity au sein de votre objet, un simple liner 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'
}
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()

Cela fonctionne pour toutes sortes d'objets contenant des objets, des tableaux, des chaînes, des booléens et des nombres.

Voir également cet article sur l'algorithme de clonage structuré des navigateurs utilisé lors de la publication de messages vers et depuis un travailleur. Il contient également une fonction de clonage en profondeur.


43
Notez que cela ne peut être utilisé que pour les tests. Tout d'abord, il est loin d'être optimal en termes de consommation de temps et de mémoire. Deuxièmement, tous les navigateurs n'ont pas cette méthode.
Nux

2
Vous pouvez toujours inclure JSON2.js ou JSON3.js. Vous en auriez besoin de toute façon. Mais je suis d'accord que ce n'est peut-être pas la meilleure solution, car JSON.stringify n'inclut pas les propriétés héritées.
Tim Hong

79
@Nux, pourquoi pas optimal en termes de temps et de mémoire? MiJyn dit: "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, des copies profondes. Mais puisque JSON est implémenté en code natif (dans la plupart des navigateurs), cela sera considérablement plus rapide que d'utiliser 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-an-object/79). " stackoverflow.com/questions/122102/…
BeauCielBleu

17
Je veux juste ajouter une mise à jour pour cela pour octobre 2014. Chrome 37+ est plus rapide avec JSON.parse (JSON.stringify (oldObject)); L'avantage d'utiliser ceci est qu'il est très facile pour un moteur javascript de voir et d'optimiser quelque chose de mieux s'il le souhaite.
mirhagk

17
Mise à jour 2016: Cela devrait maintenant fonctionner dans à peu près tous les navigateurs largement utilisés. (voir Puis-je utiliser ... ) La principale question est maintenant de savoir s'il est suffisamment performant.
James Foster

784

Avec jQuery, vous pouvez copier superficiellement avec extend :

var copiedObject = jQuery.extend({}, originalObject)

les modifications ultérieures de la copiedObjectn'affecteront pas la originalObjectet vice versa.

Ou pour faire une copie complète :

var copiedObject = jQuery.extend(true, {}, originalObject)

164
ou même:var copiedObject = jQuery.extend({},originalObject);
Grant McLean

82
Également utile pour spécifier true comme premier paramètre pour la copie jQuery.extend(true, {}, originalObject);
Will Shaver

6
Oui, j'ai trouvé ce lien utile (même solution que Pascal) stackoverflow.com/questions/122102/…
Garry Anglais

3
Juste une note, cela ne copie pas le constructeur du proto de l'objet d'origine
Sam Jones

1
Selon stackoverflow.com/questions/184710/… , il semble que "copie superficielle" copie simplement la référence de originalObject, alors pourquoi ici dit cela ...subsequent changes to the copiedObject will not affect the originalObject, and vice versa.... Désolé d'avoir été vraiment confus.
Carr

687

Dans ECMAScript 6, il existe une méthode Object.assign , qui copie les valeurs de toutes les propriétés propres énumérables d'un objet à un autre. Par exemple:

var x = {myProp: "value"};
var y = Object.assign({}, x); 

Mais sachez que les objets imbriqués sont toujours copiés comme référence.


Ouais, je crois que Object.assignc'est la voie à suivre. Il est également facile de le remplir

22
Sachez également que cela copiera les "méthodes" définies via les littéraux d'objet (car elles sont énumérables) mais pas les méthodes défiées via le mécanisme "classe" (car elles ne sont pas énumérables).
Marcus Junius Brutus

16
Je pense qu'il convient de mentionner que ce n'est pas pris en charge par IE, sauf Edge. Certaines personnes l'utilisent encore.
Saulius

1
C'est la même chose que @EugeneTiurin sa réponse.
Wilt

5
Parlant d'expérience ici ... N'utilisez pas cela. Les objets sont conçus pour être hiérarchiques et vous ne copiez que le premier niveau, ce qui vous amène à attribuer l'intégralité de l'objet copié. Croyez-moi, cela peut vous faire économiser des jours de grattage de tête.
ow3n

234

Par MDN :

  • Si vous voulez une copie superficielle, utilisez Object.assign({}, a)
  • Pour une copie "en profondeur", utilisez JSON.parse(JSON.stringify(a))

Il n'y a pas besoin de bibliothèques externes mais vous devez d'abord vérifier la compatibilité du navigateur .


8
JSON.parse (JSON.stringify (a)) est magnifique, mais avant de l'utiliser, je recommande de comparer le temps qu'il faut pour la collection souhaitée. Selon la taille de l'objet, ce n'est peut-être pas l'option la plus rapide du tout.
Edza

3
La méthode JSON que j'ai remarquée convertit les objets de date en chaînes mais pas en dates. Vous devez gérer le plaisir des fuseaux horaires en Javascript et fixer manuellement toutes les dates. Il peut y avoir des cas similaires pour d'autres types en plus des dates
Steve Seeger

pour Date, j'utiliserais moment.js car il a des clonefonctionnalités. voir plus ici momentjs.com/docs/#/parsing/moment-clone
Tareq

C'est bien mais attention si l'objet a des fonctions à l'intérieur
lesolorzanov

Un autre problème avec l'analyse json est les références circulaires. S'il y a des références circulaires à l'intérieur de l'objet, cela se cassera
philoj

134

Il existe de nombreuses réponses, mais aucune ne mentionne Object.create d'ECMAScript 5, qui ne vous donne certes pas une copie exacte, mais définit la source comme le prototype du nouvel objet.

Ainsi, ce n'est pas une réponse exacte à la question, mais c'est une solution monoligne et donc élégante. Et cela fonctionne mieux pour 2 cas:

  1. Où un tel héritage est utile (duh!)
  2. Où l'objet source ne sera pas modifié, rendant ainsi la relation entre les 2 objets non problématique.

Exemple:

var foo = { a : 1 };
var bar = Object.create(foo);
foo.a; // 1
bar.a; // 1
foo.a = 2;
bar.a; // 2 - prototype changed
bar.a = 3;
foo.a; // Still 2, since setting bar.a makes it an "own" property

Pourquoi est-ce que je considère cette solution comme supérieure? C'est natif, donc pas de boucle, pas de récursivité. Cependant, les anciens navigateurs auront besoin d'un polyfill.


Remarque: Object.create n'est pas une copie complète (itpastorn n'a mentionné aucune récursivité). Preuve:var a = {b:'hello',c:{d:'world'}}, b = Object.create(a); a == b /* false */; a.c == b.c /* true */;
zamnuts

103
C'est l'héritage prototypique, pas le clonage. Ce sont des choses complètement différentes. Le nouvel objet n'a aucune de ses propres propriétés, il pointe simplement vers les propriétés du prototype. Le but du clonage est de créer un nouvel objet frais qui ne référence aucune propriété dans un autre objet.
d13

7
Je suis complètement d'accord avec vous. Je conviens également que ce n'est pas du clonage comme cela pourrait être «prévu». Mais allez sur les gens, adoptez la nature de JavaScript au lieu d'essayer de trouver des solutions obscures qui ne sont pas standardisées. Bien sûr, vous n'aimez pas les prototypes et ils sont tous "bla" pour vous, mais ils sont en fait très utiles si vous savez ce que vous faites.
froginvasion

4
@RobG: Cet article explique la différence entre le référencement et le clonage: en.wikipedia.org/wiki/Cloning_(programming) . Object.createpointe vers les propriétés du parent par le biais de références. Cela signifie que si les valeurs des propriétés du parent changent, l'enfant changera également. Cela a des effets secondaires surprenants avec des tableaux et des objets imbriqués qui pourraient entraîner des bogues difficiles à trouver dans votre code si vous ne les connaissez pas: jsbin.com/EKivInO/2 . Un objet cloné est un tout nouvel objet indépendant qui a les mêmes propriétés et valeurs que le parent, mais n'est pas connecté au parent.
d13

1
Cela induit les gens en erreur ... Object.create () peut être utilisé comme moyen d'héritage mais le clonage est loin d'être le cas.
prajnavantha

129

Une façon élégante de cloner un objet Javascript dans une 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;
    }
  });
}

Désolé pour la question stupide, mais pourquoi Object.assignprend deux paramètres alors que la valuefonction dans le polyfill ne prend qu'un seul paramètre?
Qwertie

@Qwertie hier Tous les arguments sont itérés et fusionnés en un seul objet, priorisant les propriétés du dernier argument passé
Eugene Tiurin

Oh je vois, merci (je ne connaissais pas l' argumentsobjet auparavant.) J'ai du mal à trouver Object()via Google ... c'est un transtypage, n'est-ce pas?
Qwertie

45
cela ne fera qu'un "clonage" superficiel
Marcus Junius Brutus

1
Cette réponse est exactement la même que celle-ci: stackoverflow.com/questions/122102/… Je sais que c'est par la même personne mais vous devriez faire référence au lieu de simplement copier la réponse.
lesolorzanov

88

Il existe plusieurs problèmes avec la plupart des solutions sur Internet. J'ai donc décidé de faire un suivi, qui comprend, pourquoi la réponse acceptée ne devrait pas être acceptée.

situation de départ

Je veux copier en profondeur un Javascript Objectavec tous ses enfants et leurs enfants et ainsi de suite. Mais puisque je ne suis pas une sorte de développeur normale, mon Objecta la normale properties , circular structureset même nested objects.

Créons donc un circular structureet un nested objectpremier.

function Circ() {
    this.me = this;
}

function Nested(y) {
    this.y = y;
}

Rassemblons tout dans un Objectnom a.

var a = {
    x: 'a',
    circ: new Circ(),
    nested: new Nested('a')
};

Ensuite, nous voulons copier adans une variable nommée bet la muter.

var b = a;

b.x = 'b';
b.nested.y = 'b';

Vous savez ce qui s'est passé ici parce que sinon vous n'atterririez même pas sur cette grande question.

console.log(a, b);

a --> Object {
    x: "b",
    circ: Circ {
        me: Circ { ... }
    },
    nested: Nested {
        y: "b"
    }
}

b --> Object {
    x: "b",
    circ: Circ {
        me: Circ { ... }
    },
    nested: Nested {
        y: "b"
    }
}

Maintenant, trouvons une solution.

JSON

La première tentative que j'ai essayée a été d'utiliser JSON.

var b = JSON.parse( JSON.stringify( a ) );

b.x = 'b';
b.nested.y = 'b';

Ne perdez pas trop de temps dessus, vous en aurez TypeError: Converting circular structure to JSON.

Copie récursive (la "réponse" acceptée)

Jetons un œil à la réponse acceptée.

function cloneSO(obj) {
    // Handle the 3 simple types, and null or undefined
    if (null == obj || "object" != typeof obj) return obj;

    // Handle Date
    if (obj instanceof Date) {
        var copy = new Date();
        copy.setTime(obj.getTime());
        return copy;
    }

    // Handle Array
    if (obj instanceof Array) {
        var copy = [];
        for (var i = 0, len = obj.length; i < len; i++) {
            copy[i] = cloneSO(obj[i]);
        }
        return copy;
    }

    // Handle Object
    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! Its type isn't supported.");
}

Ça a l'air bien, hein? C'est une copie récursive de l'objet et gère également d'autres types Date, mais ce n'était pas une exigence.

var b = cloneSO(a);

b.x = 'b';
b.nested.y = 'b';

Récursivité et circular structuresne fonctionne pas bien ensemble ...RangeError: Maximum call stack size exceeded

solution native

Après avoir discuté avec mon collègue, mon patron nous a demandé ce qui s'était passé et il a trouvé une solution simple après quelques recherches sur Google. Ça s'appelle Object.create.

var b = Object.create(a);

b.x = 'b';
b.nested.y = 'b';

Cette solution a été ajoutée à Javascript il y a quelque temps et gère même circular structure.

console.log(a, b);

a --> Object {
    x: "a",
    circ: Circ {
        me: Circ { ... }
    },
    nested: Nested {
        y: "b"
    }
}

b --> Object {
    x: "b",
    circ: Circ {
        me: Circ { ... }
    },
    nested: Nested {
        y: "b"
    }
}

... et vous voyez, cela ne fonctionnait pas avec la structure imbriquée à l'intérieur.

polyfill pour la solution native

Il y a un polyfill pour Object.createl'ancien navigateur, tout comme IE 8. C'est quelque chose comme recommandé par Mozilla, et bien sûr, ce n'est pas parfait et entraîne le même problème que la solution native .

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

var b = clonePF(a);

b.x = 'b';
b.nested.y = 'b';

J'ai mis en Fdehors du champ d'application afin que nous puissions voir ce qui instanceofnous dit.

console.log(a, b);

a --> Object {
    x: "a",
    circ: Circ {
        me: Circ { ... }
    },
    nested: Nested {
        y: "b"
    }
}

b --> F {
    x: "b",
    circ: Circ {
        me: Circ { ... }
    },
    nested: Nested {
        y: "b"
    }
}

console.log(typeof a, typeof b);

a --> object
b --> object

console.log(a instanceof Object, b instanceof Object);

a --> true
b --> true

console.log(a instanceof F, b instanceof F);

a --> false
b --> true

Même problème que la solution native , mais une sortie un peu moins bonne.

la meilleure solution (mais pas parfaite)

En fouillant, j'ai trouvé une question similaire ( en Javascript, lors de l'exécution d'une copie complète , comment puis-je éviter un cycle, car une propriété est "this"? ) À celle-ci, mais avec une bien meilleure solution.

function cloneDR(o) {
    const gdcc = "__getDeepCircularCopy__";
    if (o !== Object(o)) {
        return o; // primitive value
    }

    var set = gdcc in o,
        cache = o[gdcc],
        result;
    if (set && typeof cache == "function") {
        return cache();
    }
    // else
    o[gdcc] = function() { return result; }; // overwrite
    if (o instanceof Array) {
        result = [];
        for (var i=0; i<o.length; i++) {
            result[i] = cloneDR(o[i]);
        }
    } else {
        result = {};
        for (var prop in o)
            if (prop != gdcc)
                result[prop] = cloneDR(o[prop]);
            else if (set)
                result[prop] = cloneDR(cache);
    }
    if (set) {
        o[gdcc] = cache; // reset
    } else {
        delete o[gdcc]; // unset again
    }
    return result;
}

var b = cloneDR(a);

b.x = 'b';
b.nested.y = 'b';

Et regardons la sortie ...

console.log(a, b);

a --> Object {
    x: "a",
    circ: Object {
        me: Object { ... }
    },
    nested: Object {
        y: "a"
    }
}

b --> Object {
    x: "b",
    circ: Object {
        me: Object { ... }
    },
    nested: Object {
        y: "b"
    }
}

console.log(typeof a, typeof b);

a --> object
b --> object

console.log(a instanceof Object, b instanceof Object);

a --> true
b --> true

console.log(a instanceof F, b instanceof F);

a --> false
b --> false

Les exigences sont identiques, mais il y a encore quelques petits problèmes, y compris la modification instancedu nestedet circdu Object.

La structure des arbres qui partagent une feuille ne sera pas copiée, ils deviendront deux feuilles indépendantes:

        [Object]                     [Object]
         /    \                       /    \
        /      \                     /      \
      |/_      _\|                 |/_      _\|  
  [Object]    [Object]   ===>  [Object]    [Object]
       \        /                 |           |
        \      /                  |           |
        _\|  |/_                 \|/         \|/
        [Object]               [Object]    [Object]

conclusion

La dernière solution utilisant la récursivité et un cache n'est peut-être pas la meilleure, mais c'est une véritable copie en profondeur de l'objet. Il gère simple properties, circular structureset nested object, mais il gâchera leur instance lors du clonage.

jsfiddle


12
donc la conclusion est d'éviter ce problème :)
mikus

@mikus jusqu'à ce qu'il existe une véritable spécification qui couvre plus que les cas d'utilisation de base, oui.
Fabio Poloni

2
Une analyse correcte des solutions fournies ci-dessus mais la conclusion tirée par l'auteur indique qu'il n'y a pas de solution à cette question.
Amir Mog

2
Il est dommage que JS n'inclue pas de fonction clone native.
l00k

1
Parmi toutes les meilleures réponses, je pense que cela est proche de la bonne.
KTU

78

Si vous êtes d'accord avec une copie superficielle, la bibliothèque underscore.js a une méthode de clonage .

y = _.clone(x);

ou vous pouvez l'étendre comme

copiedObject = _.extend({},originalObject);

2
Merci. Utilisation de cette technique sur un serveur Meteor.
Turbo

Pour commencer rapidement avec lodash, je recommanderais d'apprendre npm, Browserify, ainsi que lodash. J'ai fait cloner pour travailler avec 'npm i --save lodash.clone' puis 'var clone = require (' lodash.clone ');' Pour obtenir besoin de travailler, vous avez besoin de quelque chose comme browserify. Une fois que vous l'installez et découvrez comment cela fonctionne, vous allez utiliser 'browserify yourfile.js> bundle.js; start chrome index.html' chaque fois que vous exécutez votre code (au lieu d'aller directement dans Chrome). Cela rassemble votre fichier et tous les fichiers dont vous avez besoin du module npm dans bundle.js. Vous pouvez probablement gagner du temps et automatiser cette étape avec Gulp.
Aaron Bell

66

OK, imaginez que vous avez cet objet ci-dessous et que vous souhaitez le cloner:

let obj = {a:1, b:2, c:3}; //ES6

ou

var obj = {a:1, b:2, c:3}; //ES5

la réponse est depeneds principalement sur laquelle EcmaScript vous utilisez, dans ES6+, vous pouvez simplement utiliser Object.assignpour faire le clone:

let cloned = Object.assign({}, obj); //new {a:1, b:2, c:3};

ou en utilisant un opérateur de diffusion comme celui-ci:

let cloned = {...obj}; //new {a:1, b:2, c:3};

Mais si vous utilisez ES5, vous pouvez utiliser quelques méthodes, mais JSON.stringifyassurez-vous de ne pas utiliser pour un gros morceau de données à copier, mais cela pourrait être une ligne pratique dans de nombreux cas, quelque chose comme ceci:

let cloned = JSON.parse(JSON.stringify(obj)); 
//new {a:1, b:2, c:3};, can be handy, but avoid using on big chunk of data over and over

Pouvez-vous donner un exemple de ce que big chunk of datacela équivaudrait? 100kb? 100 Mo? Merci!
user1063287

Oui, @ user1063287, que, fondamentalement, les données les plus volumineuses, les performances sont pires ... donc cela dépend vraiment, pas un ko, un mb ou un gb, c'est plus le nombre de fois que vous voulez faire ça aussi ... De plus cela ne fonctionnera pas pour les fonctions et autres trucs ...
Alireza

3
Object.assignfait une copie superficielle (tout comme la propagation, @Alizera)
Bogdan D

Vous ne pouvez pas utiliser let in es5: ^) @Alireza
SensationSama

41

Une solution particulièrement inélégante consiste à utiliser le codage JSON pour créer des copies complètes d'objets qui n'ont pas de méthodes membres. La méthodologie consiste à encoder JSON votre objet cible, puis en le décodant, vous obtenez la copie que vous recherchez. Vous pouvez décoder autant de fois que vous le souhaitez pour faire autant de copies que vous le souhaitez.

Bien sûr, les fonctions n'appartiennent pas à JSON, donc cela ne fonctionne que pour les objets sans méthodes membres.

Cette méthodologie était parfaite pour mon cas d'utilisation, car je stocke des objets JSON dans un magasin de valeurs-clés, et lorsqu'ils sont exposés en tant qu'objets dans une API JavaScript, chaque objet contient en fait une copie de l'état d'origine de l'objet afin que nous peut calculer le delta après que l'appelant a muté l'objet exposé.

var object1 = {key:"value"};
var object2 = object1;

object2 = JSON.stringify(object1);
object2 = JSON.parse(object2);

object2.key = "a change";
console.log(object1);// returns value

Pourquoi les fonctions n'appartiennent-elles pas à JSON? Je les ai vus transférés en tant que JSON plus d'une fois ...
the_drow

5
Les fonctions ne font pas partie de la spécification JSON car elles ne sont pas un moyen sûr (ou intelligent) de transférer des données, ce pour quoi JSON a été conçu. Je sais que l'encodeur JSON natif de Firefox ignore simplement les fonctions qui lui sont transmises, mais je ne suis pas sûr du comportement des autres.
Kris Walker

1
@mark: { 'foo': function() { return 1; } }est un objet construit littéralement.
abarnert

Les fonctions @abarnert ne sont pas des données. "Littéraux de fonction" est un terme impropre - puisque les fonctions peuvent contenir du code arbitraire, y compris des affectations et toutes sortes de choses "non sérialisables".
vemv

36

Vous pouvez simplement utiliser une propriété spread pour copier un objet sans références. Mais attention (voir commentaires), la «copie» est juste au niveau objet / tableau le plus bas. Les propriétés imbriquées sont toujours des références!


Clone complet:

let x = {a: 'value1'}
let x2 = {...x}

// => mutate without references:

x2.a = 'value2'
console.log(x.a)    // => 'value1'

Clone avec références au deuxième niveau:

const y = {a: {b: 'value3'}}
const y2 = {...y}

// => nested object is still a references:

y2.a.b = 'value4'
console.log(y.a.b)    // => 'value4'

JavaScript ne prend pas en charge nativement les clones profonds. Utilisez une fonction utilitaire. Par exemple Ramda:

http://ramdajs.com/docs/#clone


1
Cela ne fonctionne pas ... cela fonctionnerait probablement quand x sera un tableau par exemple x = ['ab', 'cd', ...]
Kamil Kiełczewski

3
Cela fonctionne, mais gardez à l'esprit qu'il s'agit d'une copie SHALLOW, donc toute référence profonde à d'autres objets reste une référence!
Bugs Bunny

Un clone partiel peut également se produire de cette manière:const first = {a: 'foo', b: 'bar'}; const second = {...{a} = first}
Cristian Traìna

26

Pour ceux qui utilisent AngularJS, il existe également une méthode directe pour le clonage ou l'extension des objets dans cette bibliothèque.

var destination = angular.copy(source);

ou

angular.copy(source, destination);

Plus dans la documentation angular.copy ...


2
Ceci est une copie complète FYI.
zamnuts

23

Voici une fonction que vous pouvez utiliser.

function clone(obj) {
    if(obj == null || typeof(obj) != 'object')
        return obj;    
    var temp = new obj.constructor(); 
    for(var key in obj)
        temp[key] = clone(obj[key]);    
    return temp;
}

10
Cette réponse est assez proche, mais pas tout à fait correcte. Si vous essayez de cloner un objet Date, vous n'obtiendrez pas la même date car l'appel à la fonction constructeur Date initialise la nouvelle Date avec la date / heure actuelle. Cette valeur n'est pas énumérable et ne sera pas copiée par la boucle for / in.
A. Levy

Pas parfait, mais agréable pour ces cas de base. Par exemple, permettre le clonage simple d'un argument qui peut être un objet, un tableau ou une chaîne de base.
james_womack

A voté pour appeler correctement le constructeur à l'aide de new. La réponse acceptée ne le fait pas.
GetFree

fonctionne sur le nœud tout le reste! encore des liens de référence
user956584

La pensée récursive est excellente, mais si la valeur est un tableau, cela fonctionnera-t-il?
Q10Viking

23

La réponse d'A. Levy est presque complète, voici ma petite contribution: il y a un moyen de gérer les références récursives , voir cette ligne

if(this[attr]==this) copy[attr] = copy;

Si l'objet est un élément DOM XML, nous devons utiliser cloneNode à la place

if(this.cloneNode) return this.cloneNode(true);

Inspiré par l'étude approfondie d'A.Levy et l'approche de prototypage de Calvin, je propose cette solution:

Object.prototype.clone = function() {
  if(this.cloneNode) return this.cloneNode(true);
  var copy = this instanceof Array ? [] : {};
  for(var attr in this) {
    if(typeof this[attr] == "function" || this[attr]==null || !this[attr].clone)
      copy[attr] = this[attr];
    else if(this[attr]==this) copy[attr] = copy;
    else copy[attr] = this[attr].clone();
  }
  return copy;
}

Date.prototype.clone = function() {
  var copy = new Date();
  copy.setTime(this.getTime());
  return copy;
}

Number.prototype.clone = 
Boolean.prototype.clone =
String.prototype.clone = function() {
  return this;
}

Voir aussi la note d'Andy Burke dans les réponses.


3
Date.prototype.clone = function() {return new Date(+this)};
RobG

23

De cet article: Comment copier des tableaux et des objets en Javascript par Brian Huisman:

Object.prototype.clone = function() {
  var newObj = (this instanceof Array) ? [] : {};
  for (var i in this) {
    if (i == 'clone') continue;
    if (this[i] && typeof this[i] == "object") {
      newObj[i] = this[i].clone();
    } else newObj[i] = this[i]
  } return newObj;
};

4
C'est proche, mais ne fonctionne pour aucun objet. Essayez de cloner un objet Date avec cela. Toutes les propriétés ne sont pas énumérables, elles n'apparaîtront donc pas toutes dans la boucle for / in.
A. Levy

L'ajout au prototype d'objet comme celui-ci a cassé jQuery pour moi. Même lorsque j'ai renommé clone2.
iPadDeveloper2011

3
@ iPadDeveloper2011 Le code ci-dessus contenait un bogue où il créait une variable globale appelée 'i' '(pour i dans ceci)', plutôt que '(pour var i dans ceci)'. J'ai assez de karma pour l'éditer et le réparer et je l'ai fait.
mikemaccana

1
@Calvin: cela devrait être créé dans une propriété non énumérable, sinon 'clone' apparaîtra dans les boucles 'for'.
mikemaccana

2
pourquoi n'est-ce pas aussi var copiedObj = Object.create(obj);un excellent moyen?
Dan P.

20

Dans ES-6, vous pouvez simplement utiliser Object.assign (...). Ex:

let obj = {person: 'Thor Odinson'};
let clone = Object.assign({}, obj);

Une bonne référence est ici: https://googlechrome.github.io/samples/object-assign-es6/


12
Il ne clone pas profondément l'objet.
août

C'est une mission, pas une copie. clone.Title = "juste un clone" signifie que obj.Title = "juste un clone".
HoldOffHunger

@HoldOffHunger Vous vous trompez. Vérifiez-le dans la console JS de votre navigateur ( let obj = {person: 'Thor Odinson'}; let clone = Object.assign({}, obj); clone.title = "Whazzup";)
collapsar

@collapsar: C'est précisément ce que j'ai vérifié, puis console.log (personne) sera "Whazzup", pas "Thor Odinson". Voir le commentaire d'Août.
HoldOffHunger

1
@HoldOffHunger ne se produit pas dans Chrome 60.0.3112.113 ni dans Edge 14.14393; Le commentaire d'August ne s'applique pas car les valeurs des types primitifs de objpropriétés sont en effet clonées. Les valeurs de propriété qui sont des objets eux-mêmes ne seront pas clonées.
collapsar

19

Dans ECMAScript 2018

let objClone = { ...obj };

N'oubliez pas que les objets imbriqués sont toujours copiés comme référence.


1
Merci pour l'indication que les objets imbriqués sont toujours copiés comme référence! Je suis presque devenu fou en déboguant mon code car j'ai modifié les propriétés imbriquées sur le "clone" mais l'original a été modifié.
Benny Neugebauer

C'est ES2016, pas 2018, et cette réponse a été donnée deux ans plus tôt .
Dan Dascalescu

alors si je veux aussi une copie de la propriété imbriquée
Sunil Garg

1
@SunilGarg Pour copier également une propriété imbriquée, vous pouvez utiliser const objDeepClone = JSON.parse(JSON.stringify(obj));
Pavan Garre

17

Utilisation de Lodash:

var y = _.clone(x, true);

5
OMG, il serait fou de réinventer le clonage. Ceci est la seule réponse sensée.
Dan Ross

5
Je préfère _.cloneDeep(x)car c'est essentiellement la même chose que ci-dessus, mais lit mieux.
garbanzio


14

Vous pouvez cloner un objet et supprimer toute référence de l'ancien à l'aide d'une seule ligne de code. Faites simplement:

var obj1 = { text: 'moo1' };
var obj2 = Object.create(obj1); // Creates a new clone without references

obj2.text = 'moo2'; // Only updates obj2's text property

console.log(obj1, obj2); // Outputs: obj1: {text:'moo1'}, obj2: {text:'moo2'}

Pour les navigateurs / moteurs qui ne prennent actuellement pas en charge Object.create, vous pouvez utiliser ce polyfill:

// Polyfill Object.create if it does not exist
if (!Object.create) {
    Object.create = function (o) {
        var F = function () {};
        F.prototype = o;
        return new F();
    };
}

1
+1 Object.create(...)semble définitivement la voie à suivre.
René Nyffenegger

Réponse parfaite. Peut-être pourriez-vous ajouter une explication Object.hasOwnProperty? De cette façon, les gens savent comment empêcher la recherche du lien prototype.
froginvasion

Fonctionne bien mais dans quels navigateurs le polyfill fonctionne-t-il?
Ian Lunn

11
Cela crée obj2 avec un obj1 comme prototype. Cela ne fonctionne que parce que vous textobservez le membre dans obj2. Vous ne faites pas de copie, vous vous contentez de vous reporter à la chaîne de prototype lorsqu'un membre n'est pas trouvé sur obj2.
Nick Desaulniers

2
Cela ne le crée PAS "sans références", il déplace simplement la référence vers le prototype. C'est toujours une référence. Si une propriété change dans l'original, la propriété du prototype dans le "clone" aussi. Ce n'est pas du tout un clone.
Jimbo Jonny

13

Nouvelle réponse à une vieille question! Si vous avez le plaisir d'utiliser ECMAScript 2016 (ES6) avec Spread Syntax , c'est facile.

keepMeTheSame = {first: "Me!", second: "You!"};
cloned = {...keepMeTheSame}

Cela fournit une méthode propre pour une copie superficielle d'un objet. Faire une copie profonde, ce qui signifie créer une nouvelle copie de chaque valeur dans chaque objet imbriqué récursivement, nécessite l'une des solutions les plus lourdes ci-dessus.

JavaScript continue d'évoluer.


2
cela ne fonctionne pas lorsque vous avez des fonctions définies sur des objets
Petr Marek

pour autant que je vois, l'opérateur de propagation ne fonctionne qu'avec les itérables - developer.mozilla.org dit: var obj = {'key1': 'value1'}; var array = [...obj]; // TypeError: obj is not iterable
Oleh

@Oleh alors utilisez `{... obj} au lieu de [... obj];`
manikant gautam
@manikantgautam J'utilisais Object.assign () auparavant, mais maintenant, en effet, la syntaxe de propagation d'objet est prise en charge dans les derniers Chrome, Firefox (toujours pas dans Edge et Safari). Sa proposition ECMAScript ... mais Babel la soutient pour autant que je puisse voir, donc probablement son utilisation est sûre.
Oleh
12
let clone = Object.assign( Object.create( Object.getPrototypeOf(obj)), obj)

Solution ES6 si vous voulez cloner (superficiellement) une instance de classe et pas seulement un objet de propriété.

flori
la source
En quoi est-ce différent de let cloned = Object.assign({}, obj)?
ceztko
10

Je pense qu'il existe une réponse simple et efficace. Dans la copie profonde, il y a deux préoccupations:

  1. Gardez les propriétés indépendantes les unes des autres.
  2. Et gardez les méthodes en vie sur un objet cloné.

Je pense donc qu'une solution simple sera d'abord de sérialiser et de désérialiser puis de faire une affectation dessus pour copier des fonctions aussi.

let deepCloned = JSON.parse(JSON.stringify(source));
let merged = Object.assign({}, source);
Object.assign(merged, deepCloned);

Bien que cette question ait de nombreuses réponses, j'espère que celle-ci vous aidera aussi.

ConductedClever
la source
Bien que si je suis autorisé à importer du lodash, je préfère utiliser le lodash cloneDeep.
ConductedClever
2
J'utilise JSON.parse (JSON.stringify (source)). Travaille toujours.
Misha
2
@Misha, de cette façon, vous manquerez les fonctions. Le terme «œuvres» a de nombreuses significations.
ConductedClever
Et gardez à l'esprit que, comme je l'ai mentionné, seules les fonctions de la première couche seront copiées. Donc, si nous avons des objets les uns dans les autres, la seule façon est de copier récursivement champ par champ.
ConductedClever
9

Pour une copie complète et un clone, JSON.stringify puis JSON.parse l'objet:

obj = { a: 0 , b: { c: 0}};
let deepClone = JSON.parse(JSON.stringify(obj));
obj.a = 5;
obj.b.c = 5;
console.log(JSON.stringify(deepClone)); // { a: 0, b: { c: 0}}
Nishant Dwivedi
la source
assez intelligent ... des inconvénients à cette approche?
Aleks
8

Ceci est une adaptation du code de A. Levy pour gérer également le clonage de fonctions et de références multiples / cycliques - ce que cela signifie, c'est que si deux propriétés dans l'arbre qui est cloné sont des références du même objet, l'arbre d'objet cloné aura ces Les propriétés pointent vers un seul et même clone de l'objet référencé. Cela résout également le cas des dépendances cycliques qui, si elles ne sont pas gérées, conduisent à une boucle infinie. La complexité de l'algorithme est O (n)

function clone(obj){
    var clonedObjectsArray = [];
    var originalObjectsArray = []; //used to remove the unique ids when finished
    var next_objid = 0;

    function objectId(obj) {
        if (obj == null) return null;
        if (obj.__obj_id == undefined){
            obj.__obj_id = next_objid++;
            originalObjectsArray[obj.__obj_id] = obj;
        }
        return obj.__obj_id;
    }

    function cloneRecursive(obj) {
        if (null == obj || typeof obj == "string" || typeof obj == "number" || typeof obj == "boolean") return obj;

        // Handle Date
        if (obj instanceof Date) {
            var copy = new Date();
            copy.setTime(obj.getTime());
            return copy;
        }

        // Handle Array
        if (obj instanceof Array) {
            var copy = [];
            for (var i = 0; i < obj.length; ++i) {
                copy[i] = cloneRecursive(obj[i]);
            }
            return copy;
        }

        // Handle Object
        if (obj instanceof Object) {
            if (clonedObjectsArray[objectId(obj)] != undefined)
                return clonedObjectsArray[objectId(obj)];

            var copy;
            if (obj instanceof Function)//Handle Function
                copy = function(){return obj.apply(this, arguments);};
            else
                copy = {};

            clonedObjectsArray[objectId(obj)] = copy;

            for (var attr in obj)
                if (attr != "__obj_id" && obj.hasOwnProperty(attr))
                    copy[attr] = cloneRecursive(obj[attr]);                 

            return copy;
        }       


        throw new Error("Unable to copy obj! Its type isn't supported.");
    }
    var cloneObj = cloneRecursive(obj);



    //remove the unique ids
    for (var i = 0; i < originalObjectsArray.length; i++)
    {
        delete originalObjectsArray[i].__obj_id;
    };

    return cloneObj;
}

Quelques tests rapides

var auxobj = {
    prop1 : "prop1 aux val", 
    prop2 : ["prop2 item1", "prop2 item2"]
    };

var obj = new Object();
obj.prop1 = "prop1_value";
obj.prop2 = [auxobj, auxobj, "some extra val", undefined];
obj.nr = 3465;
obj.bool = true;

obj.f1 = function (){
    this.prop1 = "prop1 val changed by f1";
};

objclone = clone(obj);

//some tests i've made
console.log("test number, boolean and string cloning: " + (objclone.prop1 == obj.prop1 && objclone.nr == obj.nr && objclone.bool == obj.bool));

objclone.f1();
console.log("test function cloning 1: " + (objclone.prop1 == 'prop1 val changed by f1'));
objclone.f1.prop = 'some prop';
console.log("test function cloning 2: " + (obj.f1.prop == undefined));

objclone.prop2[0].prop1 = "prop1 aux val NEW";
console.log("test multiple references cloning 1: " + (objclone.prop2[1].prop1 == objclone.prop2[0].prop1));
console.log("test multiple references cloning 2: " + (objclone.prop2[1].prop1 != obj.prop2[0].prop1));
Radu Simionescu
la source
1
Depuis septembre 2016, c'est la seule solution correcte à la question.
DomQ
6

Je voulais juste ajouter à tous les Object.create solutions de ce post, que cela ne fonctionne pas de la manière souhaitée avec nodejs.

Dans Firefox, le résultat de

var a = {"test":"test"};
var b = Object.create(a);
console.log(b);´

est

{test:"test"}.

Dans nodejs c'est

{}
heinob
la source
C'est l'héritage prototypique, pas le clonage.
d13
1
@ d13 tant que votre argument est valide, notez qu'il n'y a pas de moyen standardisé en JavaScript pour cloner un objet. Il s'agit d'un héritage prototypique, mais il peut néanmoins être utilisé comme clones si vous comprenez les concepts.
froginvasion
@froginvasion. Le seul problème avec l'utilisation d'Object.create est que les objets et tableaux imbriqués ne sont que des références de pointeur sur les objets et tableaux imbriqués du prototype. jsbin.com/EKivInO/2/edit?js,console . Techniquement, un objet "cloné" doit avoir ses propres propriétés uniques qui ne sont pas des références partagées aux propriétés d'autres objets.
d13
@ d13 ok, je vois votre point maintenant. Mais ce que je voulais dire, c'est que trop de gens sont aliénés avec le concept d'héritage prototypique, et pour moi, je n'arrive pas à comprendre comment cela fonctionne. Si je ne me trompe pas, votre exemple peut être corrigé en appelant simplement Object.hasOwnPropertypour vérifier si vous possédez le tableau ou non. Oui, cela ajoute une complexité supplémentaire pour gérer l'héritage prototypique.
froginvasion
6
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
2
if(!src && typeof src != "object"){. Je pense que cela ne devrait ||pas l' être &&.
MikeM
6

Depuis mindeavour déclaré que l'objet à cloner est un objet `` construit littéralement '', une solution pourrait être de simplement générer l'objet plusieurs fois plutôt que de cloner une instance de l'objet:

function createMyObject()
{
    var myObject =
    {
        ...
    };
    return myObject;
}

var myObjectInstance1 = createMyObject();
var myObjectInstance2 = createMyObject();
Bert Regelink
la source
6

J'ai écrit ma propre implémentation. Je ne sais pas si cela compte comme une meilleure solution:

/*
    a function for deep cloning objects that contains other nested objects and circular structures.
    objects are stored in a 3D array, according to their length (number of properties) and their depth in the original object.
                                    index (z)
                                         |
                                         |
                                         |
                                         |
                                         |
                                         |                      depth (x)
                                         |_ _ _ _ _ _ _ _ _ _ _ _
                                        /_/_/_/_/_/_/_/_/_/
                                       /_/_/_/_/_/_/_/_/_/
                                      /_/_/_/_/_/_/...../
                                     /................./
                                    /.....            /
                                   /                 /
                                  /------------------
            object length (y)    /
*/

Voici la mise en œuvre:

function deepClone(obj) {
    var depth = -1;
    var arr = [];
    return clone(obj, arr, depth);
}

/**
 *
 * @param obj source object
 * @param arr 3D array to store the references to objects
 * @param depth depth of the current object relative to the passed 'obj'
 * @returns {*}
 */
function clone(obj, arr, depth){
    if (typeof obj !== "object") {
        return obj;
    }

    var length = Object.keys(obj).length; // native method to get the number of properties in 'obj'

    var result = Object.create(Object.getPrototypeOf(obj)); // inherit the prototype of the original object
    if(result instanceof Array){
        result.length = length;
    }

    depth++; // depth is increased because we entered an object here

    arr[depth] = []; // this is the x-axis, each index here is the depth
    arr[depth][length] = []; // this is the y-axis, each index is the length of the object (aka number of props)
    // start the depth at current and go down, cyclic structures won't form on depths more than the current one
    for(var x = depth; x >= 0; x--){
        // loop only if the array at this depth and length already have elements
        if(arr[x][length]){
            for(var index = 0; index < arr[x][length].length; index++){
                if(obj === arr[x][length][index]){
                    return obj;
                }
            }
        }
    }

    arr[depth][length].push(obj); // store the object in the array at the current depth and length
    for (var prop in obj) {
        if (obj.hasOwnProperty(prop)) result[prop] = clone(obj[prop], arr, depth);
    }

    return result;
}
yazjisuhail
la source
ne fonctionne pas pour mon objet, bien que mon cas soit un peu complexe.
Sajuuk