3369 votes

La plus élégante façon de cloner un objet JavaScript

J'ai un objet x. Je voudrais la copier comme objet y, tels que les changements d' y de ne pas modifier x.

Quelle est la façon la plus élégante de le faire en JavaScript?

Edit: je me rends compte que la copie d'objets dérivés de l'agglomération dans les objets JavaScript entraînera supplémentaires, indésirables propriétés. Ce n'est pas un problème, puisque je suis la copie d'un de mes propre, littéral-objets construits.

1659voto

A. Levy Points 8344

Pour ce faire, pour tout objet en JavaScript ne va pas être simple ou simple. Vous allez courir dans le problème de tort à ramasser les attributs de l'objet prototype qui devrait être laissé dans le prototype et non pas copié dans la nouvelle instance. Si, par exemple, l'ajout d'un clone de la méthode à l' Objet.prototype, que certaines réponses représenter, vous devrez explicitement ignorer cet attribut. Mais que faire si il y a d'autres méthodes supplémentaires ajoutés à l'Objet.prototype, ou à d'autres intermédiaires de prototypes, que vous ne le sais pas? Dans ce cas, vous pourrez copier les attributs vous ne devriez pas, si vous avez besoin de détecter les imprévus, non-locales des attributs avec la hasOwnProperty méthode.

En plus de non-énumérable attributs, vous rencontrerez plus difficile un problème lorsque vous essayez de copier des objets qui ont des propriétés cachées. Par exemple, le prototype se cache une propriété d'une fonction. Aussi, un prototype de l'objet est référencé par l'attribut __proto__, qui est également caché, et ne sera pas copié par un pour/dans la boucle d'itération sur la source attributs de l'objet. Je pense __proto__ peut-être spécifique à la version de l'interpréteur JavaScript et il peut être quelque chose de différent dans d'autres navigateurs, mais vous obtenez l'image. Tout n'est énumérable. Vous pouvez copier un attribut masqué si vous connaissez son nom, mais je ne sais pas de toute façon de découvrir automatiquement.

Encore un autre os dans la quête d'une solution élégante est le problème de la configuration de l'héritage de prototype correctement. Si votre source prototype de l'objet est un Objet, alors il suffit de créer un nouvel objet général avec {} fonctionne, mais si la source du prototype est quelque descendant de l' Objet, alors vous allez manquer les autres membres de ce prototype qui vous sauté en utilisant le hasOwnProperty filtre, ou qui ont été dans le prototype, mais n'étaient pas énumérable en premier lieu. Une solution pourrait être à la source de l'objet constructeur de propriété pour obtenir la copie initiale de l'objet, puis de copier les attributs, mais, vous n'obtiendrez pas non énumérable attributs. Par exemple, une Date objet stocke ses données comme un caché membre:

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

/* Wait for 5 seconds. */
var start = (new Date()).getTime();
while ((new Date()).getTime() - start < 5000);


var d2 = clone(d1);
alert("d1 = " + d1.toString() + "\nd2 = " + d2.toString());

La chaîne de date pour la d1 sera de 5 secondes de retard sur celui de d2. Une façon de faire un Jour le même que l'autre est en appelant la setTime de la méthode, mais qui est spécifique à la classe Date. Je ne pense pas qu'il y est une épreuve des balles de solution générale à ce problème, même si je serais heureux d'être mauvais!

Lorsque j'ai eu à mettre en œuvre générale de profondeur de la copie j'ai fini par compromettre en supposant que j'aurais seulement besoin de copier un simple Objet, Tableau, Date, Chaîne, Nombre, ou Booléen. Les 3 derniers types sont immuables, afin que je puisse effectuer une copie superficielle et ne pas s'inquiéter il en train de changer. Je suppose que tous les éléments contenus dans l'Objet ou le Tableau serait également l'un des 6 types simples dans cette liste. Ceci peut être accompli avec un code comme suit:

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 va travailler de manière adéquate pour les 6 types de données simples je l'ai dit, tant que les données dans les objets et les matrices de la forme d'une structure arborescente. Qui est, il n'y a pas plus d'une référence aux mêmes données de 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 cylicGraph = {
    "left"  : { "left" : null, "right" : null, "data" : 3 },
    "data"  : 8
};
cylicGraph["right"] = cylicGraph;

Il ne sera pas en mesure de gérer n'importe quel objet JavaScript, mais il peut être suffisant pour de nombreuses fins, comme longtemps que vous ne supposez pas qu'il va travailler pour tout ce que vous jeter à elle.

1063voto

heinob Points 2081

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

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

Cela fonctionne pour tous les types d'objets contenant des objets, tableaux, chaînes de caractères, booléens et des chiffres.

800voto

Pascal Points 1257

Je crois que si vous utilisez jQuery, vous pouvez faire ceci:

var copiedObject = {};
jQuery.étendre(copiedObject,originalObject);

les changements ultérieurs de la copiedObject n'affectera pas la originalObject, et vice versa.

141voto

itpastorn Points 1014

Il y a beaucoup de réponses, mais aucun ne mentionne l'Objet.créer à partir d'ECMAScript 5, qui, certes, ne vous donne pas une copie exacte, mais définit la source comme le prototype de l'objet nouveau.

Ainsi, ce n'est pas une réponse exacte à la question, mais c'est une solution en ligne et ainsi élégant. Et il fonctionne le mieux pour les 2 cas de figure:

  1. Où un tel héritage est utile (duh!)
  2. Où est la source de l'objet ne seront pas modifiés, ce qui rend la relation entre les 2 objets un non-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 dois-je envisager cette solution pour être de qualité supérieure? Il est natif, donc pas de boucle, pas de récursivité. Cependant, les anciens navigateurs aurez besoin d'un polyfill.

80voto

dule Points 1830

Si vous êtes d'accord avec une copie superficielle, l'underscore.js la bibliothèque dispose d'un clone de la méthode.

y = _.clone(x);

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X