web-dev-qa-db-fra.com

Quel est le moyen le plus efficace de cloner en profondeur un objet en JavaScript?

Quel est le moyen le plus efficace de cloner un objet JavaScript? J'ai vu obj = eval(uneval(o)); utilisé, mais c'est non standard et supporté uniquement par Firefox .

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

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

4863
jschrab

Note: Ceci est une réponse à une autre réponse, pas une réponse appropriée à cette question. Si vous souhaitez un clonage rapide d’objets, veuillez suivre le conseil de Corban dans leur réponse à cette question.


Je tiens à noter que la méthode .clone() de jQuery ne clone que des éléments DOM. Pour cloner des objets JavaScript, vous feriez:

// Shallow copy
var newObject = jQuery.extend({}, oldObject);

// Deep copy
var newObject = jQuery.extend(true, {}, oldObject);

Plus d'informations peuvent être trouvées dans documentation jQuery .

Je tiens également à noter que la copie profonde est en réalité beaucoup plus intelligente que ce qui est présenté ci-dessus - elle permet d'éviter de nombreux pièges (en essayant d'étendre en profondeur un élément DOM, par exemple). Il est fréquemment utilisé dans jQuery core et dans les plug-ins pour un effet optimal.

4217
John Resig

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

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

JSON.parse(JSON.stringify(obj))

être le moyen le plus rapide de cloner en profondeur un objet (il bat jQuery.extend avec un indicateur deep défini sur 10-20%).

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

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

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

Les moteurs de trace JavaScript ont du mal à optimiser les boucles for..in et la vérification de hasOwnProperty vous ralentira également. Clone manuel lorsque la vitesse est un impératif absolu.

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

Faites attention en utilisant la méthode JSON.parse(JSON.stringify(obj)) sur les objets Date - JSON.stringify(new Date()) renvoie une représentation sous forme de chaîne de la date au format ISO, laquelle JSON.parse()n'a pas reconverti en un objet Date. Voir cette réponse pour plus de détails .

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

2066
Corban Brook

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

var newObject = JSON.parse(JSON.stringify(oldObject));
434
Sultan Shakir

Clonage structuré

Le standard HTML inclut un algorithme interne de clonage/sérialisation structuré pouvant créer des clones d'objets profonds. Il est toujours limité à certains types intégrés, mais en plus des quelques types pris en charge par JSON, il prend également en charge les dates, les régressions, les cartes, les ensembles, les blobs, les listes de fichiers, les images, les tableaux fragmentés, les tableaux typés, et probablement davantage à l'avenir. . Il conserve également les références dans les données clonées, ce qui lui permet de prendre en charge des structures cycliques et récursives susceptibles de générer des erreurs pour JSON.

Support dans Node.js: Experimental ????

Le module v8 dans Node.js actuellement (à partir du nœud 11) expose directement l'API de sérialisation structurée , mais cette fonctionnalité est toujours marquée comme "expérimentale" et peut être modifiée ou supprimée dans les versions futures. Si vous utilisez une version compatible, cloner un objet est aussi simple que:

const v8 = require('v8');

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

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

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

const clone = structuredClone(original);

À moins que cela ne soit livré, les implémentations de clones structurés des navigateurs ne sont exposées qu'indirectement.

Solution de contournement asynchrone: utilisable. ????

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

class StructuredCloner {
  constructor() {
    this.pendingClones_ = new Map();
    this.nextKey_ = 0;

    const channel = new MessageChannel();
    this.inPort_ = channel.port1;
    this.outPort_ = channel.port2;

    this.outPort_.onmessage = ({data: {key, value}}) => {
      const resolve = this.pendingClones_.get(key);
      resolve(value);
      this.pendingClones_.delete(key);
    };
    this.outPort_.start();
  }

  cloneAsync(value) {
    return new Promise(resolve => {
      const key = this.nextKey_++;
      this.pendingClones_.set(key, resolve);
      this.inPort_.postMessage({key, value});
    });
  }
}

const structuredCloneAsync = window.structuredCloneAsync =
    StructuredCloner.prototype.cloneAsync.bind(new StructuredCloner);

Exemple d'utilisation:

const main = async () => {
  const original = { date: new Date(), number: Math.random() };
  original.self = original;

  const clone = await structuredCloneAsync(original);

  // They're different objects:
  console.assert(original !== clone);
  console.assert(original.date !== clone.date);

  // They're cyclical:
  console.assert(original.self === original);
  console.assert(clone.self === clone);

  // They contain equivalent values:
  console.assert(original.number === clone.number);
  console.assert(Number(original.date) === Number(clone.date));

  console.log("Assertions complete.");
};

main();

Solutions de contournement synchrones: Horrible! ????

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

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

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

Exemple d'utilisation:

'use strict';

const main = () => {
  const original = { date: new Date(), number: Math.random() };
  original.self = original;

  const clone = structuredClone(original);
  
  // They're different objects:
  console.assert(original !== clone);
  console.assert(original.date !== clone.date);

  // They're cyclical:
  console.assert(original.self === original);
  console.assert(clone.self === clone);

  // They contain equivalent values:
  console.assert(original.number === clone.number);
  console.assert(Number(original.date) === Number(clone.date));
  
  console.log("Assertions complete.");
};

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

main();

Bien que synchrone, cela peut être extrêmement lent. Il entraîne tous les frais généraux associés à la manipulation de l'historique du navigateur. Si vous appelez cette méthode à plusieurs reprises, Chrome risque de ne plus répondre temporairement.

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

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

Exemple d'utilisation:

'use strict';

const main = () => {
  const original = { date: new Date(), number: Math.random() };
  original.self = original;

  const clone = structuredClone(original);
  
  // They're different objects:
  console.assert(original !== clone);
  console.assert(original.date !== clone.date);

  // They're cyclical:
  console.assert(original.self === original);
  console.assert(clone.self === clone);

  // They contain equivalent values:
  console.assert(original.number === clone.number);
  console.assert(Number(original.date) === Number(clone.date));
  
  console.log("Assertions complete.");
};

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

main();

308
Jeremy Banks

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

function clone(obj) {
    if (obj === null || typeof (obj) !== 'object' || 'isActiveClone' in obj)
        return obj;

    if (obj instanceof Date)
        var temp = new obj.constructor(); //or new Date(obj);
    else
        var temp = obj.constructor();

    for (var key in obj) {
        if (Object.prototype.hasOwnProperty.call(obj, key)) {
            obj['isActiveClone'] = null;
            temp[key] = clone(obj[key]);
            delete obj['isActiveClone'];
        }
    }
    return temp;
}
295
ConroyP

Le moyen efficace de cloner (et non de cloner en profondeur) un objet 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);

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...

Polyfill pour prendre en charge 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;
    }
  });
}
149
Eugene Tiurin

Code:

// extends 'from' object with members from 'to'. If 'to' is null, a deep clone of 'from' is returned
function extend(from, to)
{
    if (from == null || typeof from != "object") return from;
    if (from.constructor != Object && from.constructor != Array) return from;
    if (from.constructor == Date || from.constructor == RegExp || from.constructor == Function ||
        from.constructor == String || from.constructor == Number || from.constructor == Boolean)
        return new from.constructor(from);

    to = to || new from.constructor();

    for (var name in from)
    {
        to[name] = typeof to[name] == "undefined" ? extend(from[name], null) : to[name];
    }

    return to;
}

Tester:

var obj =
{
    date: new Date(),
    func: function(q) { return 1 + q; },
    num: 123,
    text: "asdasd",
    array: [1, "asd"],
    regex: new RegExp(/aaa/i),
    subobj:
    {
        num: 234,
        text: "asdsaD"
    }
}

var clone = extend(obj);
94
Kamarey

C'est ce que j'utilise:

function cloneObject(obj) {
    var clone = {};
    for(var i in obj) {
        if(typeof(obj[i])=="object" && obj[i] != null)
            clone[i] = cloneObject(obj[i]);
        else
            clone[i] = obj[i];
    }
    return clone;
}
86
Alan

Copie profonde par performance: Classé du meilleur au pire

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

Deep copie un tableau de chaînes ou de nombres (un niveau - pas de pointeurs de référence):

Lorsqu'un tableau contient des nombres et des chaînes, des fonctions telles que .slice (), .concat (), .splice (), l'opérateur d'attribution "=" et la fonction clone de Underscore.js; fera une copie profonde des éléments du tableau.

Où la réaffectation a la performance la plus rapide:

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

Et .slice () a de meilleures performances que .concat (), http://jsperf.com/duplicate-array-slice-vs-concat/3

var arr1 = ['a', 'b', 'c'];  // Becomes arr1 = ['a', 'b', 'c']
var arr2a = arr1.slice(0);   // Becomes arr2a = ['a', 'b', 'c'] - deep copy
var arr2b = arr1.concat();   // Becomes arr2b = ['a', 'b', 'c'] - deep copy

Deep copier un tableau d'objets (deux niveaux ou plus - pointeurs de référence):

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

Ecrivez une fonction personnalisée (avec des performances plus rapides que $ .extend () ou JSON.parse):

function copy(o) {
   var out, v, key;
   out = Array.isArray(o) ? [] : {};
   for (key in o) {
       v = o[key];
       out[key] = (typeof v === "object" && v !== null) ? copy(v) : v;
   }
   return out;
}

copy(arr1);

Utilisez des fonctions utilitaires tierces:

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

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

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

Object.defineProperty( Object.prototype, "clone", {value: clone, enumerable: false});
60
Zibri

Je sais que ceci est un ancien post, mais je pensais que cela pourrait être utile à la prochaine personne qui trébuche.

Tant que vous n'attribuez rien à un objet, il ne conserve aucune référence en mémoire. Donc, pour créer un objet que vous souhaitez partager avec d'autres objets, vous devez créer une fabrique de la manière suivante:

var a = function(){
    return {
        father:'zacharias'
    };
},
b = a(),
c = a();
c.father = 'johndoe';
alert(b.father);
53
Joe

Cloning un objet a toujours été une préoccupation dans JS, mais c'était avant ES6. J'énumère ci-dessous différentes manières de copier un objet en JavaScript. Imaginez que vous ayez l'objet ci-dessous et souhaitez en avoir une copie complète:

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

Il y a peu de façons de copier cet objet sans changer l'origine:

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

function deepCopyObj(obj) {
    if (null == obj || "object" != typeof obj) return obj;
    if (obj instanceof Date) {
        var copy = new Date();
        copy.setTime(obj.getTime());
        return copy;
    }
    if (obj instanceof Array) {
        var copy = [];
        for (var i = 0, len = obj.length; i < len; i++) {
            copy[i] = cloneSO(obj[i]);
        }
        return copy;
    }
    if (obj instanceof Object) {
        var copy = {};
        for (var attr in obj) {
            if (obj.hasOwnProperty(attr)) copy[attr] = cloneSO(obj[attr]);
        }
        return copy;
    }
    throw new Error("Unable to copy obj this object.");
}

2) ES5 +, en utilisant JSON.parse et JSON.stringify.

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

3) AngularJs: 

var  deepCopyObj = angular.copy(obj);

4) jQuery: 

var deepCopyObj = jQuery.extend(true, {}, obj);

5) UnderscoreJs & Loadash: 

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

Espérons que ces aide ... 

52
Alireza

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

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

Voici un exemple d'utilisation:

Installez-le avec

npm install clone

ou emballez-le avec Ender .

ender build clone [...]

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

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

var clone = require('clone');

var a = { foo: { bar: 'baz' } };  // inital value of a
var b = clone(a);                 // clone a -> b
a.foo.bar = 'foo';                // change a

console.log(a);                   // { foo: { bar: 'foo' } }
console.log(b);                   // { foo: { bar: 'baz' } }

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

52
pvorb

Si vous l'utilisez, la bibliothèque Underscore.js a une méthode clone .

var newObject = _.clone(oldObject);
48
itsadok

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

//If Object.create isn't already defined, we just do the simple shim,
//without the second argument, since that's all we need here
var object_create = Object.create;
if (typeof object_create !== 'function') {
    object_create = function(o) {
        function F() {}
        F.prototype = o;
        return new F();
    };
}

function deepCopy(obj) {
    if(obj == null || typeof(obj) !== 'object'){
        return obj;
    }
    //make sure the returned object has the same prototype as the original
    var ret = object_create(obj.constructor.prototype);
    for(var key in obj){
        ret[key] = deepCopy(obj[key]);
    }
    return ret;
}

Cette fonction est également disponible dans ma simpleoo library.

Modifier:

Voici une version plus robuste (grâce à Justin McCandless, elle supporte également les références cycliques):

/**
 * Deep copy an object (make copies of all its object properties, sub-properties, etc.)
 * An improved version of http://keithdevens.com/weblog/archive/2007/Jun/07/javascript.clone
 * that doesn't break if the constructor has required parameters
 * 
 * It also borrows some code from http://stackoverflow.com/a/11621004/560114
 */ 
function deepCopy(src, /* INTERNAL */ _visited, _copiesVisited) {
    if(src === null || typeof(src) !== 'object'){
        return src;
    }

    //Honor native/custom clone methods
    if(typeof src.clone == 'function'){
        return src.clone(true);
    }

    //Special cases:
    //Date
    if(src instanceof Date){
        return new Date(src.getTime());
    }
    //RegExp
    if(src instanceof RegExp){
        return new RegExp(src);
    }
    //DOM Element
    if(src.nodeType && typeof src.cloneNode == 'function'){
        return src.cloneNode(true);
    }

    // Initialize the visited objects arrays if needed.
    // This is used to detect cyclic references.
    if (_visited === undefined){
        _visited = [];
        _copiesVisited = [];
    }

    // Check if this object has already been visited
    var i, len = _visited.length;
    for (i = 0; i < len; i++) {
        // If so, get the copy we already made
        if (src === _visited[i]) {
            return _copiesVisited[i];
        }
    }

    //Array
    if (Object.prototype.toString.call(src) == '[object Array]') {
        //[].slice() by itself would soft clone
        var ret = src.slice();

        //add it to the visited array
        _visited.Push(src);
        _copiesVisited.Push(ret);

        var i = ret.length;
        while (i--) {
            ret[i] = deepCopy(ret[i], _visited, _copiesVisited);
        }
        return ret;
    }

    //If we've reached here, we have a regular object

    //make sure the returned object has the same prototype as the original
    var proto = (Object.getPrototypeOf ? Object.getPrototypeOf(src): src.__proto__);
    if (!proto) {
        proto = src.constructor.prototype; //this line would probably only be reached by very old browsers 
    }
    var dest = object_create(proto);

    //add this object to the visited array
    _visited.Push(src);
    _copiesVisited.Push(dest);

    for (var key in src) {
        //Note: this does NOT preserve ES5 property attributes like 'writable', 'enumerable', etc.
        //For an example of how this could be modified to do so, see the singleMixin() function
        dest[key] = deepCopy(src[key], _visited, _copiesVisited);
    }
    return dest;
}

//If Object.create isn't already defined, we just do the simple shim,
//without the second argument, since that's all we need here
var object_create = Object.create;
if (typeof object_create !== 'function') {
    object_create = function(o) {
        function F() {}
        F.prototype = o;
        return new F();
    };
}
36
Matt Browne

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

var objToCreate = JSON.parse(JSON.stringify(cloneThis));
31
nathan rogers

Copier en profondeur des objets en JavaScript (je pense le meilleur et le plus simple)

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

var obj = { 
  a: 1,
  b: { 
    c: 2
  }
}
var newObj = JSON.parse(JSON.stringify(obj));
obj.b.c = 20;
console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 2 } } 

2.Utilisation de la méthode créée

function cloneObject(obj) {
    var clone = {};
    for(var i in obj) {
        if(obj[i] != null &&  typeof(obj[i])=="object")
            clone[i] = cloneObject(obj[i]);
        else
            clone[i] = obj[i];
    }
    return clone;
}

var obj = { 
  a: 1,
  b: { 
    c: 2
  }
}
var newObj = cloneObject(obj);
obj.b.c = 20;

console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 2 } } 

3. Utilisation de _.cloneDeep link lodash de Lo-Dash

var obj = { 
  a: 1,
  b: { 
    c: 2
  }
}

var newObj = _.cloneDeep(obj);
obj.b.c = 20;
console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 2 } } 

4. Utilisation de la méthode Object.assign ()

var obj = { 
  a: 1,
  b: 2
}

var newObj = _.clone(obj);
obj.b = 20;
console.log(obj); // { a: 1, b: 20 }
console.log(newObj); // { a: 1, b: 2 }  

MAIS MAUVAIS QUAND

var obj = { 
  a: 1,
  b: { 
    c: 2
  }
}

var newObj = Object.assign({}, obj);
obj.b.c = 20;
console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 20 } } --> WRONG
// Note: Properties on the prototype chain and non-enumerable properties cannot be copied.

5.Utilisation de Underscore.js _.clone link Underscore.js

var obj = { 
  a: 1,
  b: 2
}

var newObj = _.clone(obj);
obj.b = 20;
console.log(obj); // { a: 1, b: 20 }
console.log(newObj); // { a: 1, b: 2 }  

MAIS MAUVAIS QUAND

var obj = { 
  a: 1,
  b: { 
    c: 2
  }
}

var newObj = _.cloneDeep(obj);
obj.b.c = 20;
console.log(obj); // { a: 1, b: { c: 20 } }
console.log(newObj); // { a: 1, b: { c: 20 } } --> WRONG
// (Create a shallow-copied clone of the provided plain object. Any nested objects or arrays will be copied by reference, not duplicated.)

Référence medium.com

JSBEN.CH Analyse comparative des performances Playground 1 ~ 3 http://jsben.ch/KVQLd Performance Deep copying objects in JavaScript

28
TinhNQ

Lodash a une méthode Nice _.cloneDeep (value) :

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

var deep = _.cloneDeep(objects);
console.log(deep[0] === objects[0]);
// => false
23
opensas

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

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

var newObject = object(oldObject);

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


MODIFIER:

Ceci est un polyfill pour Object.create, vous pouvez donc également l'utiliser.

var newObject = Object.create(oldObject);

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

Par exemple si oldObject.a = 5;

newObject.a; // is 5

mais:

oldObject.hasOwnProperty(a); // is true
newObject.hasOwnProperty(a); // is false
23
Chris Broski
function clone(obj)
 { var clone = {};
   clone.prototype = obj.prototype;
   for (property in obj) clone[property] = obj[property];
   return clone;
 }
22
Mark Cidade

Copie peu profonde, une ligne ( 5ème édition d'ECMAScript ):

var Origin = { foo : {} };
var copy = Object.keys(Origin).reduce(function(c,k){c[k]=Origin[k];return c;},{});

console.log(Origin, copy);
console.log(Origin == copy); // false
console.log(Origin.foo == copy.foo); // true

Et copie superficielle one-liner ( ECMAScript 6ème édition , 2015):

var Origin = { foo : {} };
var copy = Object.assign({}, Origin);

console.log(Origin, copy);
console.log(Origin == copy); // false
console.log(Origin.foo == copy.foo); // true
20
Maël Nison

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

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

17
Dan Atkinson

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

function jQueryClone(obj) {
   return jQuery.extend(true, {}, obj)
}

function JSONClone(obj) {
   return JSON.parse(JSON.stringify(obj))
}

var arrayLikeObj = [[1, "a", "b"], [2, "b", "a"]];
arrayLikeObj.names = ["m", "n", "o"];
var JSONCopy = JSONClone(arrayLikeObj);
var jQueryCopy = jQueryClone(arrayLikeObj);

alert("Is arrayLikeObj an array instance?" + (arrayLikeObj instanceof Array) +
      "\nIs the jQueryClone an array instance? " + (jQueryCopy instanceof Array) +
      "\nWhat are the arrayLikeObj names? " + arrayLikeObj.names +
      "\nAnd what are the JSONClone names? " + JSONCopy.names)
16
Page Notes

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

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

Pour les anciens objets JavaScript simples, un bon moyen éprouvé de cloner un objet dans les environnements d'exécution modernes est tout simplement:

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

Notez que l'objet source doit être un objet JSON pur. C'est-à-dire que toutes ses propriétés imbriquées doivent être des scalaires (comme des booléens, des chaînes, des tableaux, des objets, etc.). Les fonctions ou les objets spéciaux tels que RegExp ou Date ne seront pas clonés.

Est-ce efficace? Heck oui. Nous avons essayé toutes sortes de méthodes de clonage et cela fonctionne mieux. Je suis sûr qu'un ninja pourrait imaginer une méthode plus rapide. Mais je suppose que nous parlons de gains marginaux.

Cette approche est simple et facile à mettre en œuvre. Enveloppez-le dans une fonction pratique et si vous avez vraiment besoin de réduire un gain, optez pour plus tard.

Maintenant, pour les objets JavaScript non-simples, il n'y a pas de réponse simple. En fait, cela n’est pas possible en raison de la nature dynamique des fonctions JavaScript et de l’état interne de l’objet. Le clonage en profondeur d'une structure JSON avec des fonctions à l'intérieur nécessite de recréer ces fonctions et leur contexte interne. Et JavaScript n’a tout simplement pas de méthode standard pour le faire.

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

Nous avons écrit notre propre, mais la meilleure approche générale que j'ai vu est couvert ici:

http://davidwalsh.name/javascript-clone

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

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

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

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

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

15

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

function clone(obj, clones) {
    // Makes a deep copy of 'obj'. Handles cyclic structures by
    // tracking cloned obj's in the 'clones' parameter. Functions 
    // are included, but not cloned. Functions members are cloned.
    var new_obj,
        already_cloned,
        t = typeof obj,
        i = 0,
        l,
        pair; 

    clones = clones || [];

    if (obj === null) {
        return obj;
    }

    if (t === "object" || t === "function") {

        // check to see if we've already cloned obj
        for (i = 0, l = clones.length; i < l; i++) {
            pair = clones[i];
            if (pair[0] === obj) {
                already_cloned = pair[1];
                break;
            }
        }

        if (already_cloned) {
            return already_cloned; 
        } else {
            if (t === "object") { // create new object
                new_obj = new obj.constructor();
            } else { // Just use functions as is
                new_obj = obj;
            }

            clones.Push([obj, new_obj]); // keep track of objects we've cloned

            for (key in obj) { // clone object members
                if (obj.hasOwnProperty(key)) {
                    new_obj[key] = clone(obj[key], clones);
                }
            }
        }
    }
    return new_obj || obj;
}

Test de matrice cyclique ...

a = []
a.Push("b", "c", a)
aa = clone(a)
aa === a //=> false
aa[2] === a //=> false
aa[2] === a[2] //=> false
aa[2] === aa //=> true

Test de fonctionnalité...

f = new Function
f.a = a
ff = clone(f)
ff === f //=> true
ff.a === a //=> false
13
neatonk

AngularJS

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

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

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

Et voici la fonction pour une référence rapide:

function cloneDeep (o) {
  let newO
  let i

  if (typeof o !== 'object') return o

  if (!o) return o

  if (Object.prototype.toString.apply(o) === '[object Array]') {
    newO = []
    for (i = 0; i < o.length; i += 1) {
      newO[i] = cloneDeep(o[i])
    }
    return newO
  }

  newO = {}
  for (i in o) {
    if (o.hasOwnProperty(i)) {
      newO[i] = cloneDeep(o[i])
    }
  }
  return newO
}
10
prograhammer
// obj target object, vals source object
var setVals = function (obj, vals) {
    if (obj && vals) {
        for (var x in vals) {
            if (vals.hasOwnProperty(x)) {
                if (obj[x] && typeof vals[x] === 'object') {
                    obj[x] = setVals(obj[x], vals[x]);
                } else {
                    obj[x] = vals[x];
                }
            }
        }
    }
    return obj;
};
10
Dima

Pour les personnes qui souhaitent utiliser la version JSON.parse(JSON.stringify(obj)) sans perdre les objets Date, vous pouvez utiliser l'argument second de la méthode parse pour reconvertir les chaînes en Date:

function clone(obj) {
  var regExp = /^\d{4}-\d{2}-\d{2}T\d{2}:\d{2}:\d{2}\.\d{3}Z$/;
  return JSON.parse(JSON.stringify(x), function(k, v) {
    if (typeof v === 'string' && regExp.test(v))
      return new Date(v);
    return v;
  });
}
8
Buzinas

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

function clone(src, deep) {

    var toString = Object.prototype.toString;
    if (!src && typeof src != "object") {
        // Any non-object (Boolean, String, Number), null, undefined, NaN
        return src;
    }

    // Honor native/custom clone methods
    if (src.clone && toString.call(src.clone) == "[object Function]") {
        return src.clone(deep);
    }

    // DOM elements
    if (src.nodeType && toString.call(src.cloneNode) == "[object Function]") {
        return src.cloneNode(deep);
    }

    // Date
    if (toString.call(src) == "[object Date]") {
        return new Date(src.getTime());
    }

    // RegExp
    if (toString.call(src) == "[object RegExp]") {
        return new RegExp(src);
    }

    // Function
    if (toString.call(src) == "[object Function]") {

        //Wrap in another method to make sure == is not true;
        //Note: Huge performance issue due to closures, comment this :)
        return (function(){
            src.apply(this, arguments);
        });
    }

    var ret, index;
    //Array
    if (toString.call(src) == "[object Array]") {
        //[].slice(0) would soft clone
        ret = src.slice();
        if (deep) {
            index = ret.length;
            while (index--) {
                ret[index] = clone(ret[index], true);
            }
        }
    }
    //Object
    else {
        ret = src.constructor ? new src.constructor() : {};
        for (var prop in src) {
            ret[prop] = deep
                ? clone(src[prop], true)
                : src[prop];
        }
    }
    return ret;
};
8
user1547016

J'utilise habituellement var newObj = JSON.parse( JSON.stringify(oldObje) ); mais, voici une méthode plus appropriée:

var o = {};

var oo = Object.create(o);

(o === oo); // => false

Regardez les anciens navigateurs!

6
Cody

J'utilise la bibliothèque de clones npm. Apparemment, cela fonctionne aussi dans le navigateur.

https://www.npmjs.com/package/clone

let a = clone(b)
6
user3071643

Ce n'est que lorsque vous pouvez utiliser ECMAScript 6 ou transpilers .

Caractéristiques:

  • Ne déclenchera pas le getter/setter pendant la copie.
  • Préserve le getter/setter.
  • Conserve les informations du prototype.
  • Fonctionne avec les styles d'écriture object-literal et functionalOO .

Code:

function clone(target, source){

    for(let key in source){

        // Use getOwnPropertyDescriptor instead of source[key] to prevent from trigering setter/getter.
        let descriptor = Object.getOwnPropertyDescriptor(source, key);
        if(descriptor.value instanceof String){
            target[key] = new String(descriptor.value);
        }
        else if(descriptor.value instanceof Array){
            target[key] = clone([], descriptor.value);
        }
        else if(descriptor.value instanceof Object){
            let prototype = Reflect.getPrototypeOf(descriptor.value);
            let cloneObject = clone({}, descriptor.value);
            Reflect.setPrototypeOf(cloneObject, prototype);
            target[key] = cloneObject;
        }
        else {
            Object.defineProperty(target, key, descriptor);
        }
    }
    let prototype = Reflect.getPrototypeOf(source);
    Reflect.setPrototypeOf(target, prototype);
    return target;
}
6
andrew

Clonage d'un objet à l'aide du code JavaScript actuel: ECMAScript 2015 (anciennement appelé ECMAScript 6)

var original = {a: 1};

// Method 1: New object with original assigned.
var copy1 = Object.assign({}, original);

// Method 2: New object with spread operator assignment.
var copy2 = {...original};

Les anciens navigateurs peuvent ne pas prendre en charge ECMAScript 2015. Une solution courante consiste à utiliser un compilateur JavaScript-à-JavaScript tel que Babel pour générer une version ECMAScript 5 de votre code JavaScript.

Comme signalé par @ jim-hall } _, ceci n’est qu’une copie superficielle. Les propriétés des propriétés sont copiées à titre de référence: en changer une changerait la valeur dans l'autre objet/instance.

6
Barry Staes

Solution ECMAScript 6 à une ligne (types d’objets spéciaux tels que Date/Regex non gérés):

const clone = (o) =>
  typeof o === 'object' && o !== null ?      // only clone objects
  (Array.isArray(o) ?                        // if cloning an array
    o.map(e => clone(e)) :                   // clone each of its elements
    Object.keys(o).reduce(                   // otherwise reduce every key in the object
      (r, k) => (r[k] = clone(o[k]), r), {}  // and save its cloned value into a new object
    )
  ) :
  o;                                         // return non-objects as is

var x = {
  nested: {
    name: 'test'
  }
};

var y = clone(x);

console.log(x.nested !== y.nested);

5
Shishir Arora

Je suis en retard pour répondre à cette question, mais j'ai une autre façon de cloner l'objet:

   function cloneObject(obj) {
        if (obj === null || typeof(obj) !== 'object')
            return obj;
        var temp = obj.constructor(); // changed
        for (var key in obj) {
            if (Object.prototype.hasOwnProperty.call(obj, key)) {
                obj['isActiveClone'] = null;
                temp[key] = cloneObject(obj[key]);
                delete obj['isActiveClone'];
            }
        }
        return temp;
    }



var b = cloneObject({"a":1,"b":2});   // calling

qui est bien meilleur et plus rapide que:

var a = {"a":1,"b":2};
var b = JSON.parse(JSON.stringify(a));  

et

var a = {"a":1,"b":2};

// Deep copy
var newObject = jQuery.extend(true, {}, a);

J'ai référencé le code et vous pouvez tester les résultats ici :

et partager les résultats:  enter image description here Références: https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Object/hasOwnProperty

5
Mayur Agarwal

Lodash a une fonction qui gère ça pour vous.

var foo = {a: 'a', b: {c:'d', e: {f: 'g'}}};

var bar = _.cloneDeep(foo);
// bar = {a: 'a', b: {c:'d', e: {f: 'g'}}} 

Lire la documentation ici .

5
Daniel Barde

C'est la méthode la plus rapide que j'ai créée qui n'utilise pas le prototype. Elle conserve donc hasOwnProperty dans le nouvel objet.

La solution consiste à itérer les propriétés de niveau supérieur de l'objet d'origine, à effectuer deux copies, à supprimer chaque propriété de l'original, puis à réinitialiser l'objet d'origine et à renvoyer la nouvelle copie. Il doit seulement itérer autant de fois que de propriétés de premier niveau. Cela enregistre toutes les conditions if pour vérifier si chaque propriété est une fonction, un objet, une chaîne, etc., et il n'est pas nécessaire d'itérer chaque propriété descendante.

Le seul inconvénient est que l'objet d'origine doit être fourni avec son espace de nom créé d'origine, afin de le réinitialiser.

copyDeleteAndReset:function(namespace,strObjName){
    var obj = namespace[strObjName],
    objNew = {},objOrig = {};
    for(i in obj){
        if(obj.hasOwnProperty(i)){
            objNew[i] = objOrig[i] = obj[i];
            delete obj[i];
        }
    }
    namespace[strObjName] = objOrig;
    return objNew;
}

var namespace = {};
namespace.objOrig = {
    '0':{
        innerObj:{a:0,b:1,c:2}
    }
}

var objNew = copyDeleteAndReset(namespace,'objOrig');
objNew['0'] = 'NEW VALUE';

console.log(objNew['0']) === 'NEW VALUE';
console.log(namespace.objOrig['0']) === innerObj:{a:0,b:1,c:2};
4
Steve Tomlin

Le brouillon actuel de ECMAScript 6 introduit le fichier Object.assign comme moyen de cloner des objets. Exemple de code serait:

var obj1 = { a: true, b: 1 };
var obj2 = Object.assign(obj1);
console.log(obj2); // { a: true, b: 1 }

Au moment de la rédaction de ce document, la prise en charge est limitée à Firefox 34 dans les navigateurs . Il n’est donc pas encore utilisable dans le code de production (à moins que vous n’écriviez une extension Firefox).

4
Robin Whittleton

ES 2017 exemple:

let objectToCopy = someObj;
let copyOfObject = {};
Object.defineProperties(copyOfObject, Object.getOwnPropertyDescriptors(objectToCopy));
// copyOfObject will now be the same as objectToCopy
4
codeMonkey

Qu'en est-il du clonage d'objet asynchrone effectué par un Promise?

async function clone(thingy /**/)
{
    if(thingy instanceof Promise)
    {
        throw Error("This function cannot clone Promises.");
    }
    return thingy;
}

En JavaScript, vous pouvez écrire votre méthode deepCopy comme 

function deepCopy(src) {
  let target = Array.isArray(src) ? [] : {};
  for (let prop in src) {
    let value = src[prop];
    if(value && typeof value === 'object') {
      target[prop] = deepCopy(value);
  } else {
      target[prop] = value;
  }
 }
    return target;
}
3
chandan gupta

Il y a tellement de façons d'y parvenir, mais si vous voulez le faire sans aucune bibliothèque, vous pouvez utiliser ce qui suit:

const cloneObject = (oldObject) => {
  let newObject = oldObject;
  if (oldObject && typeof oldObject === 'object') {
    if(Array.isArray(oldObject)) {
      newObject = [];
    } else if (Object.prototype.toString.call(oldObject) === '[object Date]' && !isNaN(oldObject)) {
      newObject = new Date(oldObject.getTime());
    } else {
      newObject = {};
      for (let i in oldObject) {
        newObject[i] = cloneObject(oldObject[i]);
      }
    }

  }
  return newObject;
}

Laissez-moi savoir ce que vous pensez.

3
shobhit1

Ceci est ma version d'objet cloner. Ceci est une version autonome de la méthode jQuery, avec seulement quelques modifications et ajustements. Découvrez le violon . J'ai beaucoup utilisé jQuery jusqu'au jour où j'ai réalisé que je n'utilisais que cette fonction la plupart du temps x_x.

L'utilisation est la même que celle décrite dans l'API jQuery:

  • Clone non profond: extend(object_dest, object_source);
  • Clone profond: extend(true, object_dest, object_source);

Une fonction supplémentaire est utilisée pour définir si un objet doit être cloné.

/**
 * This is a quasi clone of jQuery's extend() function.
 * by Romain WEEGER for wJs library - www.wexample.com
 * @returns {*|{}}
 */
function extend() {
    // Make a copy of arguments to avoid JavaScript inspector hints.
    var to_add, name, copy_is_array, clone,

    // The target object who receive parameters
    // form other objects.
    target = arguments[0] || {},

    // Index of first argument to mix to target.
    i = 1,

    // Mix target with all function arguments.
    length = arguments.length,

    // Define if we merge object recursively.
    deep = false;

    // Handle a deep copy situation.
    if (typeof target === 'boolean') {
        deep = target;

        // Skip the boolean and the target.
        target = arguments[ i ] || {};

        // Use next object as first added.
        i++;
    }

    // Handle case when target is a string or something (possible in deep copy)
    if (typeof target !== 'object' && typeof target !== 'function') {
        target = {};
    }

    // Loop trough arguments.
    for (false; i < length; i += 1) {

        // Only deal with non-null/undefined values
        if ((to_add = arguments[ i ]) !== null) {

            // Extend the base object.
            for (name in to_add) {

                // We do not wrap for loop into hasOwnProperty,
                // to access to all values of object.
                // Prevent never-ending loop.
                if (target === to_add[name]) {
                    continue;
                }

                // Recurse if we're merging plain objects or arrays.
                if (deep && to_add[name] && (is_plain_object(to_add[name]) || (copy_is_array = Array.isArray(to_add[name])))) {
                    if (copy_is_array) {
                        copy_is_array = false;
                        clone = target[name] && Array.isArray(target[name]) ? target[name] : [];
                    }
                    else {
                        clone = target[name] && is_plain_object(target[name]) ? target[name] : {};
                    }

                    // Never move original objects, clone them.
                    target[name] = extend(deep, clone, to_add[name]);
                }

                // Don't bring in undefined values.
                else if (to_add[name] !== undefined) {
                    target[name] = to_add[name];
                }
            }
        }
    }
    return target;
}

/**
 * Check to see if an object is a plain object
 * (created using "{}" or "new Object").
 * Forked from jQuery.
 * @param obj
 * @returns {boolean}
 */
function is_plain_object(obj) {
    // Not plain objects:
    // - Any object or value whose internal [[Class]] property is not "[object Object]"
    // - DOM nodes
    // - window
    if (obj === null || typeof obj !== "object" || obj.nodeType || (obj !== null && obj === obj.window)) {
        return false;
    }
    // Support: Firefox <20
    // The try/catch suppresses exceptions thrown when attempting to access
    // the "constructor" property of certain Host objects, i.e. |window.location|
    // https://bugzilla.mozilla.org/show_bug.cgi?id=814622
    try {
        if (obj.constructor && !this.hasOwnProperty.call(obj.constructor.prototype, "isPrototypeOf")) {
            return false;
        }
    }
    catch (e) {
        return false;
    }

    // If the function hasn't returned already, we're confident that
    // |obj| is a plain object, created by {} or constructed with new Object
    return true;
}
3
weeger

Je pense que c'est la meilleure solution si vous souhaitez généraliser votre algorithme de clonage d'objet.
Il peut être utilisé avec ou sans jQuery, bien que je recommande de ne pas utiliser la méthode extend de jQuery si vous voulez que l'objet cloné ait la même "classe" que celle d'origine.

function clone(obj){
    if(typeof(obj) == 'function')//it's a simple function
        return obj;
    //of it's not an object (but could be an array...even if in javascript arrays are objects)
    if(typeof(obj) !=  'object' || obj.constructor.toString().indexOf('Array')!=-1)
        if(JSON != undefined)//if we have the JSON obj
            try{
                return JSON.parse(JSON.stringify(obj));
            }catch(err){
                return JSON.parse('"'+JSON.stringify(obj)+'"');
            }
        else
            try{
                return eval(uneval(obj));
            }catch(err){
                return eval('"'+uneval(obj)+'"');
            }
    // I used to rely on jQuery for this, but the "extend" function returns
    //an object similar to the one cloned,
    //but that was not an instance (instanceof) of the cloned class
    /*
    if(jQuery != undefined)//if we use the jQuery plugin
        return jQuery.extend(true,{},obj);
    else//we recursivley clone the object
    */
    return (function _clone(obj){
        if(obj == null || typeof(obj) != 'object')
            return obj;
        function temp () {};
        temp.prototype = obj;
        var F = new temp;
        for(var key in obj)
            F[key] = clone(obj[key]);
        return F;
    })(obj);            
}
3
gion_13

Il y a beaucoup de réponses, mais aucune d'entre elles n'a donné l'effet souhaité. Je voulais utiliser la puissance de la copie en profondeur de jQuery ... Cependant, lorsqu'elle se trouve dans un tableau, elle copie simplement la référence dans le tableau et copie en profondeur les éléments qu'il contient. Pour résoudre ce problème, j'ai créé une petite fonction récursive de Nice qui créera automatiquement un nouveau tableau. 

(Il vérifie même kendo.data.ObservableArray si vous le souhaitez! Assurez-vous toutefois d'appeler kendo.observable (newItem) si vous souhaitez que les tableaux soient à nouveau visibles.) 

Donc, pour copier intégralement un élément existant, procédez comme suit:

var newItem = jQuery.extend(true, {}, oldItem);
createNewArrays(newItem);


function createNewArrays(obj) {
    for (var prop in obj) {
        if ((kendo != null && obj[prop] instanceof kendo.data.ObservableArray) || obj[prop] instanceof Array) {
            var copy = [];
            $.each(obj[prop], function (i, item) {
                var newChild = $.extend(true, {}, item);
                createNewArrays(newChild);
                copy.Push(newChild);
            });
            obj[prop] = copy;
        }
    }
}
3
Daniel Lorenz

Voici ma façon de cloner en profondeur un objet avec ES2015 valeur par défaut et opérateur spread

 const makeDeepCopy = (obj, copy = {}) => {
  for (let item in obj) {
    if (typeof obj[item] === 'object') {
      makeDeepCopy(obj[item], copy)
    }
    if (obj.hasOwnProperty(item)) {
      copy = {
        ...obj
      }
    }
  }
  return copy
}

const testObj = {
  "type": "object",
  "properties": {
    "userId": {
      "type": "string",
      "chance": "guid"
    },
    "emailAddr": {
      "type": "string",
      "chance": {
        "email": {
          "domain": "fake.com"
        }
      },
      "pattern": "[email protected]"
    }
  },
  "required": [
    "userId",
    "emailAddr"
  ]
}

const makeDeepCopy = (obj, copy = {}) => {
  for (let item in obj) {
    if (typeof obj[item] === 'object') {
      makeDeepCopy(obj[item], copy)
    }
    if (obj.hasOwnProperty(item)) {
      copy = {
        ...obj
      }
    }
  }
  return copy
}

console.log(makeDeepCopy(testObj))

3
Julez

Sans toucher à l'héritage prototype, vous pouvez approfondir des objets et des tableaux isolés comme suit:

function objectClone(o){
  var ot = Array.isArray(o);
  return o !== null && typeof o === "object" ? Object.keys(o)
                                                     .reduce((r,k) => o[k] !== null && typeof o[k] === "object" ? (r[k] = objectClone(o[k]),r)
                                                                                                                : (r[k] = o[k],r), ot ? [] : {})
                                             : o;
}
var obj = {a: 1, b: {c: 2, d: {e: 3, f: {g: 4, h: null}}}},
    arr = [1,2,[3,4,[5,6,[7]]]],
    nil = null,
  clobj = objectClone(obj),
  clarr = objectClone(arr),
  clnil = objectClone(nil);
console.log(clobj, obj === clobj);
console.log(clarr, arr === clarr);
console.log(clnil, nil === clnil);
clarr[2][2][2] = "seven";
console.log(arr, clarr);

2
Redu

Pour une copie superficielle, une méthode simple et efficace a été introduite dans le standard ECMAScript2018. Cela implique l'utilisation de Spread Operator :

let obj = {a : "foo", b:"bar" , c:10 , d:true , e:[1,2,3] };

let objClone = { ...obj };

Je l'ai testé dans le navigateur Chrome. Les deux objets étant stockés à des emplacements différents, la modification immédiate des valeurs enfant dans l'un ne change pas l'autre. Bien que (dans l'exemple), modifier une valeur dans e affectera les deux copies.

Cette technique est très simple et directe. Je considère cela comme une vraie meilleure pratique pour cette question une fois pour toutes. 

2
Vikram K

Utilisez Object.create() pour obtenir la prototype et le support de instanceof, et utilisez une boucle for() pour obtenir les clés énumérables:

function cloneObject(source) {
    var key,value;
    var clone = Object.create(source);

    for (key in source) {
        if (source.hasOwnProperty(key) === true) {
            value = source[key];

            if (value!==null && typeof value==="object") {
                clone[key] = cloneObject(value);
            } else {
                clone[key] = value;
            }
        }
    }

    return clone;
}
2
Steven Vachon

class Handler {
  static deepCopy (obj) {
    if (Object.prototype.toString.call(obj) === '[object Array]') {
      const result = [];
      
      for (let i = 0, len = obj.length; i < len; i++) {
        result[i] = Handler.deepCopy(obj[i]);
      }
      return result;
    } else if (Object.prototype.toString.call(obj) === '[object Object]') {
      const result = {};
      for (let prop in obj) {
        result[prop] = Handler.deepCopy(obj[prop]);
      }
      return result;
    }
    return obj;
  }
}

2
Ihor Pavlyk

Nécessite des navigateurs nouveaux, mais ...

Étendons l'objet natif et obtenons un real .extend();

Object.defineProperty(Object.prototype, 'extend', {
    enumerable: false,
    value: function(){
        var that = this;

        Array.prototype.slice.call(arguments).map(function(source){
            var props = Object.getOwnPropertyNames(source),
                i = 0, l = props.length,
                prop;

            for(; i < l; ++i){
                prop = props[i];

                if(that.hasOwnProperty(prop) && typeof(that[prop]) === 'object'){
                    that[prop] = that[prop].extend(source[prop]);
                }else{
                    Object.defineProperty(that, prop, Object.getOwnPropertyDescriptor(source, prop));
                }
            }
        });

        return this;
    }
});

Il suffit de l'afficher avant tout code utilisant .extend () sur un objet.

Exemple:

var obj1 = {
    node1: '1',
    node2: '2',
    node3: 3
};

var obj2 = {
    node1: '4',
    node2: 5,
    node3: '6'
};

var obj3 = ({}).extend(obj1, obj2);

console.log(obj3);
// Object {node1: "4", node2: 5, node3: "6"}
2
Tristian

En 2019, j'utilise:

deepCopy(object) {
  const getCircularReplacer = () => {
    const seen = new WeakSet();
    return (key, value) => {
      if(typeof value === 'object' && value !== null) {
        if(seen.has(value)) return;
        seen.add(value);
      }
      return value;
    };
  };
  return JSON.parse(JSON.stringify(object, getCircularReplacer()));
}

const theCopy = deepCopy(originalObject);

2
Matthieu Chavigny

Comme la récursion est trop coûteuse pour JavaScript, et que la plupart des réponses que j'ai trouvées utilisent cette méthode, l'approche JSON ignore les parties non convertibles en JSON (Fonction, etc.). J'ai donc fait quelques recherches et découvert cette technique de trampoline pour l'éviter. Voici le code:

/*
 * Trampoline to avoid recursion in JavaScript, see:
 *     http://www.integralist.co.uk/posts/js-recursion.html
 */
function trampoline() {
    var func = arguments[0];
    var args = [];
    for (var i = 1; i < arguments.length; i++) {
        args[i - 1] = arguments[i];
    }

    var currentBatch = func.apply(this, args);
    var nextBatch = [];

    while (currentBatch && currentBatch.length > 0) {
        currentBatch.forEach(function(eachFunc) {
            var ret = eachFunc();
            if (ret && ret.length > 0) {
                nextBatch = nextBatch.concat(ret);
            }
        });

        currentBatch = nextBatch;
        nextBatch = [];
    }
};

/*
 *  Deep clone an object using the trampoline technique.
 *
 *  @param target {Object} Object to clone
 *  @return {Object} Cloned object.
 */
function clone(target) {
    if (typeof target !== 'object') {
        return target;
    }
    if (target == null || Object.keys(target).length == 0) {
        return target;
    }

    function _clone(b, a) {
        var nextBatch = [];
        for (var key in b) {
            if (typeof b[key] === 'object' && b[key] !== null) {
                if (b[key] instanceof Array) {
                    a[key] = [];
                }
                else {
                    a[key] = {};
                }
                nextBatch.Push(_clone.bind(null, b[key], a[key]));
            }
            else {
                a[key] = b[key];
            }
        }
        return nextBatch;
    };

    var ret = target instanceof Array ? [] : {};
    (trampoline.bind(null, _clone))(target, ret);
    return ret;
};

Voir aussi ce Gist: https://Gist.github.com/SeanOceanHu/7594cafbfab682f790eb

2
Bodhi Hu

Pour référence future, on peut utiliser ce code

ES6:

_clone: function(obj){
    let newObj = {};
    for(let i in obj){
        if(typeof(obj[i]) === 'object' && Object.keys(obj[i]).length){
            newObj[i] = clone(obj[i]);
        } else{
            newObj[i] = obj[i];
        }
    }
    return Object.assign({},newObj);
}

ES5:

function clone(obj){
let newObj = {};
for(let i in obj){
    if(typeof(obj[i]) === 'object' && Object.keys(obj[i]).length){
        newObj[i] = clone(obj[i]);
    } else{
        newObj[i] = obj[i];
    }
}
return Object.assign({},newObj);

}

Par exemple 

var obj ={a:{b:1,c:3},d:4,e:{f:6}}
var xc = clone(obj);
console.log(obj); //{a:{b:1,c:3},d:4,e:{f:6}}
console.log(xc); //{a:{b:1,c:3},d:4,e:{f:6}}

xc.a.b = 90;
console.log(obj); //{a:{b:1,c:3},d:4,e:{f:6}}
console.log(xc); //{a:{b:90,c:3},d:4,e:{f:6}}
2
Ashutosh Jha

Object.assign({},sourceObj) clone uniquement l'objet si sa propriété n'a pas de clé de type de référence. ex

obj={a:"lol",b:["yes","no","maybe"]}
clonedObj = Object.assign({},obj);

clonedObj.b.Push("skip")// changes will reflected to the actual obj as well because of its reference type.
obj.b //will also console => yes,no,maybe,skip

Donc, pour le clonage en profondeur n'est pas possible de réaliser de cette façon.

La meilleure solution qui fonctionne est

var obj = Json.stringify(yourSourceObj)
var cloned = Json.parse(obj);
2

D'après mon expérience, une version récursive surpasse largement JSON.parse(JSON.stringify(obj)). Voici une fonction de copie d'objet profonde récursive et modernisée pouvant tenir sur une seule ligne

function deepCopy(obj) {
  return Object.keys(obj).reduce((v, d) => Object.assign(v, {
    [d]: (obj[d].constructor === Object) ? deepCopy(obj[d]) : obj[d]
  }), {});
}

Cela fonctionne environ 40 fois plus vite que la méthode JSON.parse....

2
Parabolord

Il existe trois manières différentes de cloner des objets en Javascript.

1: Copie profonde en utilisant l'itération

function iterationCopy(src) {
  let target = {};
  for (let prop in src) {
    if (src.hasOwnProperty(prop)) {
      target[prop] = src[prop];
    }
  }
  return target;
}
const source = {a:1, b:2, c:3};
const target = iterationCopy(source);
console.log(target); // {a:1, b:2, c:3}
// Check if clones it and not changing it
source.a = 'a';
console.log(source.a); // 'a'
console.log(target.a); // 1

Donc, comme vous le voyez, ça marche!

Passons maintenant à la deuxième solution, plus élégante, mais plus limitée.

2: Conversion en JSON et retour

function jsonCopy(src) {
  return JSON.parse(JSON.stringify(src));
}
const source = {a:1, b:2, c:3};
const target = jsonCopy(source);
console.log(target); // {a:1, b:2, c:3}
// Check if clones it and not changing it
source.a = 'a';
console.log(source.a); // 'a'
console.log(target.a); // 1

Remarque: veillez à utiliser cette méthode car votre objet source DOIT être protégé par JSON. Il est donc possible qu'une sorte de gestion des exceptions soit nécessaire pour assurer sa sécurité dans les cas où l'objet source n'est pas convertible en JSON.

3: Utilisation de Object.assign

Update: Cette méthode a un défaut qu'elle ne fait qu'une copie superficielle. Cela signifie que les propriétés imbriquées vont toujours être copiées par référence. Faites attention à ce sujet.

Cette méthode est la meilleure et la plus sûre que je consomme personnellement dans mes projets. Il exploite une méthode statique intégrée à l’objet Object et est géré et fourni par le langage. Alors utilisez celui-ci!

function bestCopyEver(src) {
  return Object.assign({}, src);
}
const source = {a:1, b:2, c:3};
const target = bestCopyEver(source);
console.log(target); // {a:1, b:2, c:3}
// Check if clones it and not changing it
source.a = 'a';
console.log(source.a); // 'a'
console.log(target.a); // 1
2
Kishan Patel

C'est une solution avec récursivité:

obj = {
  a: { b: { c: { d: ['1', '2'] } } },
  e: 'Saeid'
}
const Clone = function (obj) {
  
  const container = Array.isArray(obj) ? [] : {}
  const keys  = Object.keys(obj)
   
  for (let i = 0; i < keys.length; i++) {
    const key = keys[i]
    if(typeof obj[key] == 'object') {
      container[key] = Clone(obj[key])
    }
    else
      container[key] = obj[key].slice()
  }
  
  return container
}
 console.log(Clone(obj))

2
SAlidadi

si vous vous retrouvez régulièrement dans ce genre de situation (par exemple, créer une fonctionnalité d'annulation de restauration), il peut être intéressant de regarder Immutable.js

const map1 = Immutable.fromJS( { a: 1, b: 2, c: { d: 3 } } );
const map2 = map1.setIn( [ 'c', 'd' ], 50 );

console.log( `${ map1.getIn( [ 'c', 'd' ] ) } vs ${ map2.getIn( [ 'c', 'd' ] ) }` ); // "3 vs 50"

https://codepen.io/anon/pen/OBpqNE?editors=1111

1
shunryu111

En parcourant cette longue liste de réponses, presque toutes les solutions ont été abordées, sauf une que je connaisse. Voici la liste des méthodes utilisées par Vanilla JS pour le clonage en profondeur d'un objet.

  1. JSON.parse (JSON.stringify (obj));

  2. À travers history.state avec pushState ou replaceState

  3. API de notifications Web, mais cela présente l'inconvénient de demander à l'utilisateur des autorisations.

  4. Faites votre propre boucle récursive à travers l'objet pour copier chaque niveau.

  5. La réponse que je n'ai pas vue -> Utilisation de ServiceWorkers. Les messages (objets) échangés entre la page et le script ServiceWorker constitueront des clones profonds de tout objet.

1
Steve Griffith

Comme cette question suscite beaucoup d’attention et répond en faisant référence à des fonctionnalités intégrées telles que Object.assign ou un code personnalisé pour deep clone, je voudrais partager certaines bibliothèques avec Deep Clone 

1. esclone

npm install --savedev esclone https://www.npmjs.com/package/esclone

Exemple d'utilisation dans ES6:

import esclone from "esclone";

const rockysGrandFather = {
  name: "Rockys grand father",
  father: "Don't know :("
};
const rockysFather = {
  name: "Rockys Father",
  father: rockysGrandFather
};

const rocky = {
  name: "Rocky",
  father: rockysFather
};

const rockyClone = esclone(rocky);

Exemple d'utilisation dans ES5:

var esclone = require("esclone")
var foo = new String("abcd")
var fooClone = esclone.default(foo)
console.log(fooClone)
console.log(foo === fooClone)

2. copie en profondeur

npm install deep-copy https://www.npmjs.com/package/deep-copy

Exemple:

var dcopy = require('deep-copy')

// deep copy object 
var copy = dcopy({a: {b: [{c: 5}]}})

// deep copy array 
var copy = dcopy([1, 2, {a: {b: 5}}])

3. profondeur de clone

$ npm install --save clone-deep https://www.npmjs.com/package/clone-deep

Exemple:

var cloneDeep = require('clone-deep');

var obj = {a: 'b'};
var arr = [obj];

var copy = cloneDeep(arr);
obj.c = 'd';

console.log(copy);
//=> [{a: 'b'}] 

console.log(arr);
1
JTeam

Mon scénario était un peu différent. J'ai eu un objet avec des objets imbriqués ainsi que des fonctions. Par conséquent, Object.assign() et JSON.stringify() n'étaient pas des solutions à mon problème. L'utilisation de bibliothèques tierces n'était pas une option pour moi non plus.

Par conséquent, j'ai décidé de créer une fonction simple consistant à utiliser des méthodes intégrées pour copier un objet avec ses propriétés littérales, ses objets imbriqués et ses fonctions.

let deepCopy = (target, source) => {
    Object.assign(target, source);
    // check if there's any nested objects
    Object.keys(source).forEach((prop) => {
        /**
          * assign function copies functions and
          * literals (int, strings, etc...)
          * except for objects and arrays, so:
          */
        if (typeof(source[prop]) === 'object') {
            // check if the item is, in fact, an array
            if (Array.isArray(source[prop])) {
                // clear the copied referenece of nested array
                target[prop] = Array();
                // iterate array's item and copy over
                source[prop].forEach((item, index) => {
                    // array's items could be objects too!
                    if (typeof(item) === 'object') {
                        // clear the copied referenece of nested objects
                        target[prop][index] = Object();
                        // and re do the process for nested objects
                        deepCopy(target[prop][index], item);
                    } else {
                        target[prop].Push(item);
                    }
                });
            // otherwise, treat it as an object
            } else {
                // clear the copied referenece of nested objects
                target[prop] = Object();
                // and re do the process for nested objects
                deepCopy(target[prop], source[prop]);
            }
        }
    });
};

Voici un code de test:

let a = {
    name: 'Human', 
    func: () => {
        console.log('Hi!');
    }, 
    prop: {
        age: 21, 
        info: {
            hasShirt: true, 
            hasHat: false
        }
    },
    mark: [89, 92, { exam: [1, 2, 3] }]
};

let b = Object();

deepCopy(b, a);

a.name = 'Alien';
a.func = () => { console.log('Wassup!'); };
a.prop.age = 1024;
a.prop.info.hasShirt = false;
a.mark[0] = 87;
a.mark[1] = 91;
a.mark[2].exam = [4, 5, 6];

console.log(a); // updated props
console.log(b);

En ce qui concerne les problèmes d’efficacité, je crois que c’est la solution la plus simple et la plus efficace au problème que j’avais. J'apprécierais tout commentaire sur cet algorithme qui pourrait le rendre plus efficace.

1
Kamyar

Lorsque votre objet est imbriqué et qu'il contient un objet de données, un autre objet structuré ou un objet de propriété, etc., utiliser JSON.parse(JSON.stringify(object)) ou Object.assign({}, obj) ou $.extend(true, {}, obj) ne fonctionnera pas. Dans ce cas, utilisez lodash. C'est simple et facile..

var obj = {a: 25, b: {a: 1, b: 2}, c: new Date(), d: anotherNestedObject };
var A = _.cloneDeep(obj);

Maintenant, A sera votre nouveau cloné d'obj sans aucune référence. 

1
Prasanth Jaya

J'espère que cela t'aides.

function deepClone(obj) {
    /*
     * Duplicates an object 
     */

    var ret = null;
    if (obj !== Object(obj)) { // primitive types
        return obj;
    }
    if (obj instanceof String || obj instanceof Number || obj instanceof Boolean) { // string objecs
        ret = obj; // for ex: obj = new String("Spidergap")
    } else if (obj instanceof Date) { // date
        ret = new obj.constructor();
    } else
        ret = Object.create(obj.constructor.prototype);

    var prop = null;
    var allProps = Object.getOwnPropertyNames(obj); //gets non enumerables also


    var props = {};
    for (var i in allProps) {
        prop = allProps[i];
        props[prop] = false;
    }

    for (i in obj) {
        props[i] = i;
    }

    //now props contain both enums and non enums 
    var propDescriptor = null;
    var newPropVal = null; // value of the property in new object
    for (i in props) {
        prop = obj[i];
        propDescriptor = Object.getOwnPropertyDescriptor(obj, i);

        if (Array.isArray(prop)) { //not backward compatible
            prop = prop.slice(); // to copy the array
        } else
        if (prop instanceof Date == true) {
            prop = new prop.constructor();
        } else
        if (prop instanceof Object == true) {
            if (prop instanceof Function == true) { // function
                if (!Function.prototype.clone) {
                    Function.prototype.clone = function() {
                        var that = this;
                        var temp = function tmp() {
                            return that.apply(this, arguments);
                        };
                        for (var ky in this) {
                            temp[ky] = this[ky];
                        }
                        return temp;
                    }
                }
                prop = prop.clone();

            } else // normal object 
            {
                prop = deepClone(prop);
            }

        }

        newPropVal = {
            value: prop
        };
        if (propDescriptor) {
            /*
             * If property descriptors are there, they must be copied
             */
            newPropVal.enumerable = propDescriptor.enumerable;
            newPropVal.writable = propDescriptor.writable;

        }
        if (!ret.hasOwnProperty(i)) // when String or other predefined objects
            Object.defineProperty(ret, i, newPropVal); // non enumerable

    }
    return ret;
}

https://github.com/jinujd/Javascript-Deep-Clone

1
Jinu Joseph Daniel

Si vous utilisez es6, vous pouvez simplement le faire en utilisant l'opérateur spread.

let a = {id:1, name:'sample_name'}
let b = {...a};
1
Amaldev ps

Vous pouvez utiliser l'opérateur Spread pour cloner l'objet en JavaScript.

// opérateur de propagation faisant le travail de concat

let arr = [1,2,3];  let arr2 = [4,5];  arr = [...arr,...arr2]; console.log(arr); 
0
Raghul Shree

Ceci est ma solution sans utiliser aucune bibliothèque ou fonction javascript native.

function deepClone(obj) {
  if (typeof obj !== "object") {
    return obj;
  } else {
    let newObj =
      typeof obj === "object" && obj.length !== undefined ? [] : {};
    for (let key in obj) {
      if (key) {
        newObj[key] = deepClone(obj[key]);
      }
    }
    return newObj;
  }
}
0
Ankur Kedia

Que diriez-vous de fusionner le clés de l'objet avec son valeurs?

function deepClone(o) {
    var keys = Object.keys(o);
    var values = Object.values(o);

    var clone = {};

    keys.forEach(function(key, i) {
        clone[key] = typeof values[i] == 'object' ? Object.create(values[i]) : values[i];
    });

    return clone;
}

Remarque: Cette méthode ne crée pas nécessairement de copies superficielles, elle ne fait que des copies avec la profondeur d'un objet interne, ce qui signifie que lorsque vous recevez quelque chose comme {a: {b: {c: null}}}, les objets sont directement à l'intérieur d'eux, donc deepClone(a.b).c est techniquement une référence à a.b.c, alors que deepClone(a).b est un clone, n'est pas une référence.

0
Eternal Darkness

Avec la proposition de la nouvelle méthode Object.fromEntries () , prise en charge par les versions les plus récentes de certains navigateurs ( reference ). Je veux contribuer à la prochaine approche récursive:

const obj = {
  key1: {key11: "key11", key12: "key12", key13: {key131: 22}},
  key2: {key21: "key21", key22: "key22"},
  key3: "key3",
  key4: [1,2,3, {key: "value"}]
}

const cloneObj = (obj) =>
{
    if (Object(obj) !== obj)
       return obj;
    else if (Array.isArray(obj))
       return obj.map(cloneObj);

    return Object.fromEntries(Object.entries(obj).map(
        ([k,v]) => ([k, cloneObj(v)])
    ));
}

// Clone the original object.
let newObj = cloneObj(obj);

// Make changes on the original object.
obj.key1.key11 = "TEST";
obj.key3 = "TEST";
obj.key1.key13.key131 = "TEST";
obj.key4[1] = "TEST";
obj.key4[3].key = "TEST";

// Display both objects on the console.
console.log("Original object: ", obj);
console.log("Cloned object: ", newObj);
.as-console {background-color:black !important; color:Lime;}
.as-console-wrapper {max-height:100% !important; top:0;}
0
Shidersz
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.");
}

utilisez la méthode suivante au lieu de JSON.parse(JSON.stringify(obj)) car .__ est plus lent que la méthode suivante 

Comment cloner correctement un objet JavaScript?

0
shakthi nagaraj