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




object push javascript (20)

Quel est le moyen le plus efficace de cloner un objet JavaScript? J'ai vu obj = eval(uneval(o)); en cours d'utilisation, mais ce n'est pas standard et n'est supporté que par Firefox .

J'ai fait des choses comme obj = JSON.parse(JSON.stringify(o)); mais questionne 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.

https://code.i-harness.com


Remarque: 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, suivez les conseils de Corban dans sa réponse à cette question.

Je tiens à noter que la méthode .clone() dans 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 la documentation de 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.


Clonage structuré

HTML5 définit un algorithme de clonage "structuré" interne 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 préserve é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.

Prise en charge directe dans les navigateurs: bientôt disponible? 🙂

Les navigateurs ne fournissent pas actuellement d’interface directe pour l’algorithme de clonage structuré, mais une fonction globale structuredClone() est en cours de discussion dans whatwg / html # 793 sur GitHub et pourrait être disponible sous peu! Tel que proposé actuellement, son utilisation dans la plupart des cas sera aussi simple que:

const clone = structuredClone(original);

Jusqu'à ce que cela 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 de 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 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 tente également d'afficher une notification du navigateur à l'utilisateur, mais cela échouera silencieusement sauf si vous avez demandé l'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();


AngularJS

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

var newObject = angular.copy(oldObject);

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;
}

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 pouvez éviter les tableaux imbriqués profonds, vous pouvez écrire une boucle simple for (var i in obj) pour cloner votre objet tout en vérifiant hasOwnProperty. Elle 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 sont particulièrement for..in à optimiser.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 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, que JSON.parse() ne reconvertit pas. à un objet Date . Voir cette réponse pour plus de détails .

De plus, veuillez noter que, dans Chrome 65 au moins, le clonage natif n’est pas la solution. Selon JSPerf , effectuer un clonage natif en créant une nouvelle fonction est près de 800 fois plus lent que l’utilisation de JSON.stringify, qui est incroyablement rapide dans tous les domaines.


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

Je sais que c’est un vieux billet, mais j’ai pensé 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);

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;
}

Copie profonde par performance: classé du meilleur au pire

  • Réaffectation "=" (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

Copier en profondeur 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

Copier en profondeur 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:


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 du lien lodash de _.cloneDeep de Lo-Dashlodash

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 FAUX 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 du lien Underscore.js _.clone 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 FAUX 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


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 vieux 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 de l’objet interne. 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 bien recréer le graphique 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 de manière spéciale 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.


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 bibliothèque simpleoo .

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://.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();
    };
}

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

Copie peu profonde sur un support ( ECMAScript 5ème édition ):

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

Je suis en désaccord avec la réponse avec les plus grands votes here . Un clone 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
}

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.


Pour les personnes qui souhaitent utiliser la JSON.parse(JSON.stringify(obj))version, mais sans perdre les objets Date, vous pouvez utiliser le deuxième argument de parsemethod pour convertir 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;
  });
}

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

var newObject = _.clone(oldObject);

// 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;
};

function clone(obj)
 { var clone = {};
   clone.prototype = obj.prototype;
   for (property in obj) clone[property] = obj[property];
   return clone;
 }




clone