Quel est le moyen le plus efficace de cloner profondément un objet en JavaScript ?

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

J'ai fait des choses comme obj = JSON.parse(JSON.stringify(o)); mais je m'interroge sur l'efficacité.

J'ai aussi vu des fonctions de copie récursive avec divers défauts.
Je suis surpris qu'il n'existe pas de solution canonique.

[1] : https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/uneval

Solution

Clonage profond natif

C'est ce qu'on appelle le "clonage structuré", qui fonctionne expérimentalement dans Node 11 et les versions ultérieures, et qui, espérons-le, sera disponible dans les navigateurs. Voir cette réponse pour plus de détails.

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

Si vous n'utilisez pas de Dates, de fonctions, de undefined, de Infinity, de RegExps, de Maps, de Sets, de Blobs, de FileLists, d'ImageDatas, de sparse Arrays, de Typed Arrays ou d'autres types complexes dans votre objet, une méthode très simple pour cloner profondément un objet est :

JSON.parse(JSON.stringify(object)).

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 des points de repère.

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

Puisque le clonage d'objets n'est pas trivial (types complexes, références circulaires, fonction etc.), la plupart des bibliothèques majeures fournissent une fonction pour cloner des objets. Ne réinventez pas la roue - si vous utilisez déjà une bibliothèque, vérifiez si elle possède une fonction de clonage d'objets. Par exemple,

ES6

Pour être complet, notez que ES6 offre deux mécanismes de copie superficielle : Object.assign() et l'opérateur spread.

Commentaires (24)

S'il n'y en avait pas un intégré, 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;
}
Commentaires (11)
function clone(obj)
 { var clone = {};
   clone.prototype = obj.prototype;
   for (property in obj) clone[property] = obj[property];
   return clone;
 }
Commentaires (5)