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

StackOverflow https://stackoverflow.com/questions/122102

  •  02-07-2019
  •  | 
  •  

Question

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

J'ai déjà effectué des opérations telles que obj = JSON.parse (JSON.stringify (o)); mais remettez en question l'efficacité.

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

Était-ce utile?

La solution

  

Note 2019-juin: Il s'agissait à l'origine d'une réponse à une autre réponse, et non d'une réponse appropriée à cette question. Aucune idée pourquoi il a été choisi comme la bonne réponse. Mais comme les votes favorables ont fait boule de neige et que c'est de loin la réponse n ° 1 à cette question, les solutions seront résumées sous forme de réponse wiki.

Clonage profond natif

Il s’appelle "clonage structuré", fonctionne à titre expérimental dans les nœuds 11 et ultérieurs, et devrait atterrir dans les navigateurs. Voir ceci répondez pour plus de détails.

Clonage rapide avec perte de données - JSON.parse / stringify

Si vous n'utilisez pas les Date , fonctions, indéfini , Infinity , RegExps, Cartes, Ensembles, Blobs, FileLists, ImageDatas, des tableaux épars, des tableaux typés ou d’autres types complexes au sein de votre objet, un simple liner pour cloner en profondeur un objet est:

JSON.parse (JSON.stringify (objet))

const a = {
  string: 'string',
  number: 123,
  bool: false,
  nul: null,
  date: new Date(),  // stringified
  undef: undefined,  // lost
  inf: Infinity,  // forced to 'null'
  re: /.*/,  // lost
}
console.log(a);
console.log(typeof a.date);  // Date object
const clone = JSON.parse(JSON.stringify(a));
console.log(clone);
console.log(typeof clone.date);  // result of .toISOString()

Voir La réponse de Corban pour les points de repère.

Clonage fiable à l'aide d'une bibliothèque

Le clonage d'objets n'étant pas trivial (types complexes, références circulaires, fonction, etc.), la plupart des principales bibliothèques fournissent des fonctions pour cloner des objets. Ne réinventez pas la roue : si vous utilisez déjà une bibliothèque, vérifiez si elle dispose d'une fonction de clonage d'objet. Par exemple,

  • lodash - cloneDeep ; peuvent être importés séparément via le module lodash.clonedeep et constitue probablement votre meilleur choix si vous ' Ne pas utiliser une bibliothèque fournissant une fonction de clonage en profondeur
  • Angular - angular.copy
  • jQuery - true, {}, oldObject) ; .clone () ne clone que les éléments DOM

ES6

Pour être complet, notez que ES6 propose deux mécanismes de copie superficielle: Object.assign () et les opérateur de propagation .

Autres conseils

Consultez ce repère: http://jsben.ch/#/bWfk9

Lors de 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 lent de cloner en profondeur un objet (il est plus lent que jQuery.extend avec l'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 du 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 simple boucle pour (var i in obj) afin de cloner votre objet tout en vérifiant hasOwnProperty et ce 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 ne savent pas vraiment optimiser les boucles for..in et vérifier hasOwnProperty vous ralentira également. Cloner manuellement lorsque la vitesse est une nécessité absolue.

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

Faites attention en utilisant la méthode JSON.parse (JSON.stringify (obj)) sur les objets Date - JSON.stringify (nouvelle date ()) renvoie une représentation sous forme de chaîne de la date au format ISO que JSON.parse () ne ne reconvertit pas en 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 cette JSPerf , effectuer un clonage natif en créant une nouvelle fonction est presque 800x plus lent que l’utilisation de JSON.stringify, qui est incroyablement rapide à tous les niveaux.

Mise à jour pour ES6

Si vous utilisez Javascript ES6, essayez cette méthode native pour le clonage ou la copie superficielle.

Object.assign({}, obj);

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

Clonage structuré

La norme HTML inclut les un algorithme de clonage / sérialisation 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égularisations, les cartes, les ensembles, les blobs, les listes de fichiers, les fichiers ImageDatas, 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.

Support dans Node.js: Expérimental ??

Le module v8 dans Node.js actuellement (à partir du noeud 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 actuellement pas d’interface directe pour l’algorithme de clonage structuré, mais une fonction globale structuralClone () 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. ??

Pour créer un clone structuré avec des API existantes, la procédure la plus simple 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, écouter 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’existe aucune option intéressante 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 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 tel que 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é 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();

S'il n'y en avait pas, vous pouvez 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;
}

Le moyen efficace de cloner (et non de cloner en profondeur) un objet dans une ligne de code

Un Object.assign Cette méthode fait partie de la norme ECMAScript 2015 (ES6) et répond exactement à vos besoins.

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.

En savoir plus ...

Le polyfill destiné à 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;
    }
  });
}

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

Test:

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

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

Copie profonde par performances: 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
  • Les $ .extend de jQuery
  • JSON.parse (tableaux de chaînes, tableaux de nombres, tableaux d’objets - uniquement)
  • Le fichier _.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'affectation "quot = =" et la fonction clone de Underscore.js; fera une copie profonde des éléments du tableau.

Le meilleur résultat pour la réaffectation est:

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

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

Utiliser des fonctions utilitaires tierces:

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

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

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

Il existe une bibliothèque (appelée & # 8220; clone & # 8221;) , cela le fait très bien. Il fournit le clonage / la copie récursive 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 également le trouver sur npm . 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.

Vous pouvez ensuite 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.)

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 aucun objet à 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 comme suit:

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

Cloner un objet a toujours été une préoccupation de JS, mais avant ES6, je listais différentes manières de copier un objet en JavaScript ci-dessous, imaginez que vous avez l'objet ci-dessous et souhaitez le avoir une copie conforme de celle-ci:

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

Il existe plusieurs façons de copier cet objet sans changer l'origine:

1) ES5 +, Utilisation d'une fonction simple pour effectuer 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 +, 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 & amp; Loadash:

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

J'espère que cela vous aidera ...

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

var newObject = _.clone(oldObject);

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

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

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 _.cloneDeep de Lo-Dash lodash

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 lien Underscore.js

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

MAIS MAUVAIS QUAND

<*>

Référence medium.com

Analyse comparative des performances de JSBEN.CH Playground 1 ~ 3 http://jsben.ch/KVQLd Performances Copie profonde des objets en JavaScript

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

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

Lodash a une belle méthode _.cloneDeep (valeur) :

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

var deep = _.cloneDeep(objects);
console.log(deep[0] === objects[0]);
// => false
function clone(obj)
 { var clone = {};
   clone.prototype = obj.prototype;
   for (property in obj) clone[property] = obj[property];
   return clone;
 }

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

Cela est concis, fonctionne comme prévu et vous n'avez pas besoin d'une bibliothèque.

EDIT:

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

var newObject = Object.create(oldObject);

REMARQUE: si vous en utilisez une partie, vous rencontrerez peut-être des problèmes lors de certaines itérations utilisant hasOwnProperty . En effet, 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

Copie peu profonde dans 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 une copie superficielle dans une couche ( 6ème édition d'ECMAScript , 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

Tout simplement parce que je n’ai pas vu AngularJS penser que les gens voudraient peut-être savoir. ..

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

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)

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

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

Désormais, pour les objets JavaScript non simples, la réponse n’est pas vraiment 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. Cloner en profondeur 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 normalisée.

La bonne façon de le faire, 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 connaissance 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 texte, mais la meilleure approche générale que j'ai vue est décrite 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écifique l'instanciation de vos fonctions (ou classes prototypales, 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 d'é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.

Ce n’est généralement pas la solution la plus efficace, mais c’est ce que 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 fonctionnement ...

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

AngularJS

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

var newObject = angular.copy(oldObject);

Je ne suis pas d'accord avec la réponse qui suscite le plus grand nombre de votes, ici . 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
}
// 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;
};

Pour les personnes souhaitant utiliser la version JSON.parse (JSON.stringify (obj)) , mais sans perdre les objets Date, vous pouvez utiliser la deuxième argument de la méthode parse pour reconvertit 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;
  });
}

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;
};
Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top