Question Comment cloner correctement un objet JavaScript?


J'ai un objet, x. Je voudrais le copier en tant qu'objet y, tels que les changements à y ne pas modifier x. 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 non souhaitées. Ce n'est pas un problème, puisque je copie un de mes propres objets construits littéralement.

Comment cloner correctement un objet JavaScript?


2395


origine


Réponses:


Réponse mise à jour

Juste utiliser Object.assign () comme suggéré ici

Mais sachez que cela ne fait qu'une copie superficielle. Les objets imbriqués sont toujours copiés en tant que références.


Réponse obsolète

Faire ceci pour n'importe quel objet en JavaScript ne sera pas simple ou direct. Vous rencontrerez le problème de récupérer par erreur des attributs du prototype de l'objet qui doivent rester dans le prototype et ne pas être copiés dans la nouvelle instance. Si, par exemple, vous ajoutez un clone méthode pour Object.prototypeComme le montrent certaines réponses, vous devrez ignorer explicitement cet attribut. Mais que se passe-t-il si d'autres méthodes supplémentaires sont 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 avoir, vous devez donc détecter les attributs imprévus et non locaux hasOwnProperty mé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 cachées. Par exemple, prototype est 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 caché, et ne sera pas copié par une boucle for / in itérant sur les attributs de l'objet source. je pense __proto__ peut être spécifique à l'interpréteur JavaScript de Firefox et 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.

Encore un hic dans la quête d'une solution élégante est le problème de la mise en place de l'héritage du prototype correctement. Si le prototype de votre objet source est Object, puis simplement en créant 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 hasOwnProperty filtre, ou qui étaient dans le prototype, mais n'étaient pas énumérables en premier lieu. Une solution pourrait être d'appeler l'objet source constructor propriété pour obtenir l'objet copie initiale, puis copiez sur les attributs, mais vous n'obtiendrez toujours pas d'attributs non énumérables. Par exemple, un Date object stocke ses données en tant que membre caché:

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 d1 sera 5 secondes derrière celle de d2. Une façon de faire un Date le même que l'autre est en appelant le setTime méthode, mais qui est spécifique à la Date classe. Je ne pense pas qu'il existe une solution générale à ce problème, mais je serais heureux de me tromper!

Quand j'ai dû implémenter la copie profonde générale, j'ai fini par faire des compromis en supposant que je n'aurais besoin que de copier une plaine Object, Array, Date, String, Number, ou Boolean. Les 3 derniers types sont immuables, donc je pourrais effectuer une copie superficielle et ne pas m'inquiéter de la changer. J'ai en outre supposé que tous les éléments contenus dans Object ou Array serait également l'un des 6 types simples dans cette liste. Cela peut être accompli avec un 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, aussi longtemps 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 capable de gérer n'importe quel objet JavaScript, mais cela peut suffire à de nombreuses fins tant que vous ne supposez pas que cela fonctionnera pour tout ce que vous lui lancerez.


1308



Avec jQuery, vous pouvez copie superficielle avec étendre:

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

les modifications ultérieures apportées à copiedObject n'affectent pas l'objet original et vice versa.

Ou pour faire un copie profonde:

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

712



Si vous n'utilisez pas de fonctions au sein de votre objet, un très simple doublure peut être la suivante:

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

Cela fonctionne pour tous les types d'objets contenant des objets, des tableaux, des chaînes, des booléens et des nombres.

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


687



Dans ECMAScript 6, il y a Object.assign méthode, 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.


508



Il y a beaucoup de réponses, mais aucune qui mentionne Object.create d'ECMAScript 5, qui, certes, ne vous donne 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 d'une ligne 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 un problème.

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écursion. Cependant, les anciens navigateurs auront besoin d'un polyfill.


113



Une manière élégante de cloner un objet Javascript dans une ligne de code

Un Object.assign Cette mé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;
    }
  });
}

105



Par MDN:

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

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


103



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

y = _.clone(x);

ou vous pouvez l'étendre comme

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

70



Il y a 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 copie profonde un Javascript Object avec tous ses enfants et leurs enfants et ainsi de suite. Mais puisque je ne suis pas un développeur normal, mon Object a Ordinaire  properties, circular structures et même nested objects.

Alors créons un circular structure et un nested object premier.

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

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

Mettons tout ensemble dans un Object nommé a.

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

Ensuite, nous voulons copier a dans une variable nommée b et le muter.

var b = a;

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

Vous savez ce qui s'est passé ici, sinon vous ne vous poseriez même pas sur cette excellente 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é était en utilisant JSON.

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

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

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

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

Jetons un coup d'oeil à 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, comme Date, mais ce n'était pas une exigence.

var b = cloneSO(a);

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

Récursivité et circular structures ne 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'est passé, et il a trouvé un Solution après quelques recherches sur Google. C'est appelé 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 n'a pas fonctionné avec la structure imbriquée à l'intérieur.

polyfill pour la solution native

Il y a un polyfill pour Object.create dans le navigateur plus ancien, tout comme l'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 le 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 F en dehors de la portée de sorte que nous pouvons jeter un oeil à ce que instanceof nous 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 le solution native, mais une sortie un peu pire.

la meilleure solution (mais pas parfaite)

En creusant, j'ai trouvé une question similaire (En Javascript, quand j'effectue une copie en profondeur, comment éviter un cycle, car une propriété est "ceci"?) à celui-ci, mais avec une 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 jetons un coup d'oeil à 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 assorties, mais il y a encore quelques problèmes mineurs, y compris la modification de instance de nested et circ à 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, peut ne pas être la meilleure, mais c'est un réal copie profonde de l'objet. Il gère simple properties, circular structures et nested object, mais il va gâcher l'instance d'eux tout en clonant.

http://jsfiddle.net/einfallstoll/N4mr2/


63



Une solution particulièrement inélégante consiste à utiliser le codage JSON pour effectuer des copies profondes des objets qui n'ont pas de méthodes membres. La méthodologie est de coder 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 voulez faire autant de copies que nécessaire.

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 blobs 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 réalité une copie de l'état d'origine de l'objet. 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

36