Comment cloner correctement un objet JavaScript?

j'ai un objet", 151900920" . J'aimerais le copier comme objet y , de sorte que les changements à y ne modifient pas x . J'ai réalisé que copier des objets dérivés d'objets JavaScript intégrés entraînera des propriétés supplémentaires et indésirables. Ce n'est pas un problème, puisque je copie un de mes propres objets, littéralement construits.

comment cloner correctement un objet JavaScript?

2476
demandé sur mindeavor 2009-04-08 07:01:06

30 réponses

pour faire cela pour tout objet en JavaScript ne sera pas simple ou simple. Vous rencontrerez le problème de récupérer par erreur des attributs du prototype de l'objet qui devraient être laissés dans le prototype et non copiés dans la nouvelle instance. Si, par exemple, l'ajout d'un clone méthode Object.prototype , que certaines réponses représenter, vous devrez explicitement ignorer cet attribut. Mais qu'en est-il s'il y a d'autres méthodes supplémentaires ajoutées à Object.prototype , ou d'autres des prototypes intermédiaires, que vous ne connaissez pas? Dans ce cas, vous copierez des attributs que vous ne devriez pas, vous devez donc détecter des attributs non locaux imprévus avec la méthode hasOwnProperty .

en plus des attributs non-dénombrables, 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é par le l'attribut __proto__ , qui est également caché, et ne sera pas copié par une boucle for/in itérant les attributs de l'objet source. Je pense que __proto__ pourrait être spécifique à L'interpréteur JavaScript de Firefox 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 caché si vous connaissez son nom, mais je ne connais aucun moyen de le découvrir automatiquement.

encore un autre accroc dans la quête d'un la solution élégante est le problème de configurer correctement l'héritage du prototype. Si le prototype de votre objet source est Object , alors la création d'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 filtre hasOwnProperty , ou qui étaient dans le prototype, mais qui n'étaient pas dénombrables en premier lieu. Une solution pourrait il faut appeler la propriété constructor de l'objet source pour obtenir l'objet copie initial et ensuite copier les attributs, mais alors vous n'obtiendrez toujours pas d'attributs non-énumérables. Par exemple, un Date objet stocke ses données comme un 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 datation pour d1 sera 5 secondes derrière celle de d2 . Une façon de faire un Date la même chose qu'un autre est en appelant le setTime méthode, mais qui est spécifique à la classe Date . Je ne pense pas qu'il existe une solution générale à ce problème, même si je serais heureux de me tromper!

quand j'ai dû mettre en œuvre la copie générale profonde, j'ai fini par faire des compromis en présumant que je n'aurais besoin de copier qu'une Object , Array , Date , String , Number , ou Boolean . Les 3 derniers types sont immuables, donc je pourrais effectuer une copie superficielle et ne pas s'inquiéter sur elle en train de changer. J'ai supposé en outre que tout élément contenu dans Object ou Array serait aussi l'un des 6 types simples dans cette liste. Cela peut être accompli avec le code suivant:

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 d'arbre. Qui est, il n'y a pas plus d'une référence aux mêmes données de l'objet. 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 tout objet JavaScript, mais il peut être suffisant pour de nombreuses fins aussi longtemps que vous ne supposez pas que cela fonctionnera juste pour tout ce que vous jetez à elle.

1345
répondu A. Levy 2018-08-14 15:12:54

avec jQuery, vous pouvez copie peu profonde avec étendre :

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

les modifications ultérieures de l'objet copié n'affecteront pas l'objet original, et vice versa.

ou pour faire une copie profonde :

var copiedObject = jQuery.extend(true, {}, originalObject)
728
répondu Pascal 2016-08-18 13:28:04

si vous n'utilisez pas de fonctions dans votre objet, une doublure très simple peut être la suivante:

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

cela fonctionne pour tous les types d'objets contenant des objets, tableaux, cordes, booléens et numéros.

Voir aussi cet article à propos de la structuration de la clone algorithme de navigateurs qui est utilisé lors de l'envoi de messages vers et à partir d'un travailleur. Il contient également une fonction pour le clonage profond.

720
répondu heinob 2016-01-26 14:44:29

dans ECMAScript 6 Il y a objet.assignez la 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.

531
répondu Vitalii Fedorenko 2016-10-21 11:14:00

Per MDN :

  • si vous voulez une copie superficielle, utilisez Object.assign({}, a)
  • pour "copie profonde", utiliser JSON.parse(JSON.stringify(a))

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

125
répondu Tareq 2018-03-08 07:42:24

il y a beaucoup de réponses, mais aucune qui mentionne objet.créez à partir D'ECMAScript 5, qui, il est vrai, 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 en ligne et ainsi élégant. Et il fonctionne mieux pour 2 cas:

  1. Lorsqu'un tel héritage est utile (duh!)
  2. où le l'objet source ne sera pas modifié, faisant ainsi de 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 cette solution me semble-t-elle supérieure? Il est natif, donc pas de boucle, pas de récursivité. Cependant, les navigateurs plus anciens auront besoin d'un polyfill.

119
répondu itpastorn 2012-03-19 15:17:52

une façon élégante de cloner un objet Javascript dans une ligne de code

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

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

L'Objet.assign() la méthode 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.

plus d'informations...

Le polyfill pour soutenir 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;
    }
  });
}
106
répondu Eugene Tiurin 2015-12-15 16:42:26

Si vous êtes d'accord avec une copie superficielle, le trait de soulignement.la bibliothèque js a une méthode clone .

y = _.clone(x);

ou vous pouvez l'étendre comme

copiedObject = _.extend({},originalObject);
71
répondu dule 2015-01-19 18:47:56

il y a plusieurs problèmes avec la plupart des solutions sur internet. Alors j'ai 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 en profondeur Javascript Object avec tous ses enfants et leurs enfants et ainsi de suite. Mais puisque je ne suis pas une sorte de développeur normal, mon Object a normal properties , circular structures et même nested objects .

alors créons d'abord un circular structure et un nested object .

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

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

rassemblons tout dans un Object nommé a .

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

ensuite, nous voulons copier a dans une variable appelée b et 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"
    }
}

trouvons une solution.

JSON

la première tentative que j'ai essayé était d'utiliser JSON .

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

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

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

copie Récursive (accepté la "réponse"),

regardons 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.");
}

C'est bien, hein? C'est une copie récursive de l'objet et traite d'autres types aussi bien, comme Date , mais ce n'était pas une exigence.

var b = cloneSO(a);

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

récursion 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'était passé, et il a trouvé une simple solution après certains googler. Ça s'appelle Object.create .

var b = Object.create(a);

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

cette solution a été ajoutée au Javascript il y a quelque temps et traite 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, ça n'a pas marché avec la structure emboîtée à l'intérieur.

polyfill pour la solution native

il y a un polyfill pour Object.create dans l'ancien navigateur tout comme le IE 8. C'est quelque chose comme recommandé par Mozilla, et de bien sûr, ce n'est pas parfait et il en résulte 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 afin que nous puissions avoir un regard sur 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 un peu pire sortie.

le mieux (mais pas parfait) solution

quand en fouillant, j'ai trouvé une question similaire ( en Javascript, lors de l'exécution d'une copie profonde, Comment éviter un cycle, en raison d'une propriété étant "ceci"? ) à celui-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 satisfaites, mais il y a encore des problèmes plus petits, y compris le changement du 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écursion et une cache, n'est peut-être pas la meilleure, mais c'est une réelle copie profonde de l'objet. Il gère simple properties , circular structures et nested object , mais il va exemple, alors que le clonage.

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

66
répondu Fabio Poloni 2017-11-25 10:44:05

une solution particulièrement inélégante est d'utiliser l'encodage JSON pour faire des copies profondes 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 voulez pour faire autant de copies que vous avez besoin.

bien sûr, les fonctions n'ont pas leur place dans JSON, donc cela ne fonctionne que pour les objets sans méthode de membre.

cette méthodologie était parfait pour mon cas d'utilisation, puisque je stocke des blobs JSON dans un magasin de valeurs clés, et quand ils sont exposés comme des objets dans une API JavaScript, chaque objet contient en fait une copie de l'état original de l'objet de sorte que nous pouvons 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
37
répondu Tim 2016-10-28 17:24:12

, Vous pouvez simplement utiliser un propagation de la propriété pour copier un objet sans références. Mais attention (voir Commentaires), la' copie ' est juste au plus bas niveau objet/tableau. 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 second 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 supporte pas les clones profonds nativement. Utilisez une fonction utilitaire. Par exemple Ramda:

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

25
répondu musemind 2017-04-05 12:30:15

pour ceux qui utilisent AngularJS, il existe également une méthode directe pour cloner ou étendre les objets de cette bibliothèque.

var destination = angular.copy(source);

ou

angular.copy(source, destination);

plus en angle.copie documentation ...

23
répondu Lukas Jelinek 2014-09-03 19:08:28

OK, imaginez que vous ayez cet objet ci-dessous et que vous voulez 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 principalement depeneds sur lequel ECMAscript vous en utilisant, dans ES6+ , vous pouvez simplement utiliser Object.assign pour faire le clone:

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

ou en utilisant l'opérateur spread comme ceci:

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

Mais si vous utilisez ES5 , vous pouvez utiliser peu de méthodes, mais le JSON.stringify , assurez-vous juste que vous n'utilisez pas pour un gros morceau de données à copier, mais il pourrait être une ligne maniable 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
22
répondu Alireza 2017-07-06 22:48:04

A. Levy réponse est presque terminée, voici ma petite contribution: il y a une façon de gérer récursive références , 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 exhaustive D'A. Levy et L'approche de Calvin en prototypage, 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 Andy Burke note les réponses.

20
répondu Jan Turoň 2012-12-02 20:49:01

à Partir 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;
};
19
répondu Calvin 2012-09-22 22:06:59

dans ES-6 Vous pouvez simplement utiliser L'objet.assigner.(..). 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 /

18
répondu João Oliveira 2017-04-13 20:17:11

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;
}
17
répondu picardo 2012-02-23 20:04:41

vous pouvez cloner un objet et supprimer toute référence du précédent en utilisant une seule ligne de code. Il suffit de faire:

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 pas actuellement en charge L'objet.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();
    };
}
14
répondu Rob Evans 2012-09-16 05:27:01

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, c'est-à-dire faire une nouvelle copie de chaque valeur dans chaque objet imbriqué de façon récursive, nécessite l'une des solutions plus lourdes ci-dessus.

JavaScript continue d'évoluer.

11
répondu Charles Merriam 2016-12-16 11:34:33

Utilisant Lodash:

var y = _.clone(x, true);
10
répondu VaZaA 2012-12-13 00:05:07
let clone = Object.assign( Object.create( Object.getPrototypeOf(obj)), obj)

solution ES6 si vous voulez (superficiel) cloner un instance de classe et pas seulement un objet de propriété.

10
répondu flori 2017-06-27 12:57:57

Intéressés par le clonage des objets simples :

JSON.parse (JSON.stringify (json_original));

Source: comment copier objet JavaScript vers une nouvelle variable non par référence?

9
répondu Mohammed Akdim 2017-05-23 12:18:27

la réponse de Jan Turoň ci-dessus est très proche, et peut-être la meilleure à utiliser dans un navigateur en raison de problèmes de compatibilité, mais cela pourrait causer quelques étranges problèmes d'énumération. Par exemple, l'exécution:

for ( var i in someArray ) { ... }

assignera la méthode clone() à i après itération à travers les éléments du tableau. Voici une adaptation qui évite l'énumération et fonctionne avec noeud.js:

Object.defineProperty( Object.prototype, "clone", {
    value: 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;
    }
});

Object.defineProperty( Date.prototype, "clone", {
    value: function() {
        var copy = new Date();
        copy.setTime( this.getTime() );
        return copy;
    }
});

Object.defineProperty( Number.prototype, "clone", { value: function() { return this; } } );
Object.defineProperty( Boolean.prototype, "clone", { value: function() { return this; } } );
Object.defineProperty( String.prototype, "clone", { value: function() { return this; } } );

cela évite de faire la méthode clone() énumérable parce que defineProperty () par défaut énumérable à false.

6
répondu Andy Burke 2012-03-30 06:03:57

il s'agit d'une adaptation du code de A. Levy pour gérer également le clonage de fonctions et de références multiples/cycliques - ce qui signifie 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 propriétés pointer 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 manipulé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));
6
répondu Radu Simionescu 2012-07-16 09:23:13

j'ai écrit ma propre implémentation. Pas sûr si elle 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)    /
*/

suit 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;
}
6
répondu yazjisuhail 2016-07-29 16:32:06

je voulais juste ajouter à toutes les solutions Object.create dans 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"} .

En nodejs c'est

{}
5
répondu heinob 2012-06-03 09:29:51
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;
};
5
répondu user1547016 2012-07-31 20:53:16

puisque mindeavor a 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();
5
répondu Bert Regelink 2017-05-23 12:34:51

Consultez http://www.w3.org/html/wg/drafts/html/master/infrastructure.html#safe-passing-of-structured-data pour le W3C sécurisé de transmission de données structurées" de l'algorithme, destiné à être mis en œuvre par les navigateurs de la transmission des données par exemple à des web workers. Cependant, il a quelques limites, en ce qu'il ne gère pas les fonctions. Voir https://developer.mozilla.org/en-US/docs/DOM/The_structured_clone_algorithm pour plus d'informations, y compris un un algorithme alternatif dans JS qui vous permet d'y aller en partie.

4
répondu user663031 2013-04-16 02:56:17

ci-dessous est ma version du clonage profond, des fonctions de couverture et avec manipulation pour les références circulaires.

https://github.com/radsimu/UaicNlpToolkit/blob/master/Modules/GGS/GGSEngine/src/main/resources/ro/uaic/info/nlptools/ggs/engine/core/jsInitCode.js#L17

4
répondu Radu Simionescu 2018-02-05 09:24:25