161 votes

Comment faire un clone profond en javascript

Comment cloner profondément un objet JavaScript ?

Je sais qu'il existe différentes fonctions basées sur des cadres tels que JSON.parse(JSON.stringify(o)) y $.extend(true, {}, o) mais je ne veux pas utiliser un tel cadre.

Quelle est la manière la plus élégante ou la plus efficace de créer un clone profond ?

Nous nous intéressons à des cas particuliers tels que le clonage de tableaux. Ne pas briser les chaînes de prototypes, gérer l'autoréférence.

Nous ne nous soucions pas de la prise en charge de la copie d'objets DOM et autres car .cloneNode existe pour cette raison.

Comme je veux surtout utiliser des clones profonds dans les node.js l'utilisation des fonctionnalités ES5 du moteur V8 est acceptable.

[Editer]

Avant que quelqu'un ne le suggère, permettez-moi de préciser qu'il y a une nette différence entre créer une copie en héritant prototypiquement de l'objet et clonage il. Le premier fait des dégâts dans la chaîne de prototypes.

[Suite de la rédaction]

Après avoir lu votre réponse, j'ai fait la fâcheuse découverte que le clonage d'objets entiers est un jeu très dangereux et difficile. Prenons par exemple l'objet suivant, basé sur une fermeture

var o = (function() {
     var magic = 42;

     var magicContainer = function() {
          this.get = function() { return magic; };
          this.set = function(i) { magic = i; };
     }

      return new magicContainer;
}());

var n = clone(o); // how to implement clone to support closures

Existe-t-il un moyen d'écrire une fonction de clonage qui clone l'objet, qui a le même état au moment du clonage mais qui ne peut pas modifier l'état de l'objet ? o sans écrire un parseur JS en JS.

Une telle fonction ne devrait plus être nécessaire dans le monde réel. Il s'agit d'un simple intérêt académique.

13voto

svarog Points 197

Les Bibliothèque contributive Underscore.js dispose d'une fonction appelée instantané qui clone profondément un objet

extrait de la source :

snapshot: function(obj) {
  if(obj == null || typeof(obj) != 'object') {
    return obj;
  }

  var temp = new obj.constructor();

  for(var key in obj) {
    if (obj.hasOwnProperty(key)) {
      temp[key] = _.snapshot(obj[key]);
    }
  }

  return temp;
}

une fois que la bibliothèque est liée à votre projet, invoquez la fonction simplement en utilisant

_.snapshot(object);

4voto

Lawrence Dol Points 27976

Comme d'autres l'ont fait remarquer sur cette question et d'autres questions similaires, le clonage d'un "objet", au sens général du terme, est douteux en JavaScript.

Cependant, il existe une classe d'objets que j'appelle les objets "données", c'est-à-dire ceux qui sont construits simplement à partir des éléments suivants { ... } et/ou de simples assignations de propriétés ou désérialisées à partir de JSON pour lesquelles il est raisonnable de vouloir cloner. Aujourd'hui même, j'ai voulu gonfler artificiellement les données reçues d'un serveur par 5x pour tester ce qui se passe pour un grand ensemble de données, mais l'objet (un tableau) et ses enfants devaient être des objets distincts pour que les choses fonctionnent correctement. Le clonage m'a permis de multiplier mon ensemble de données :

return dta.concat(clone(dta),clone(dta),clone(dta),clone(dta));

L'autre raison pour laquelle je clone souvent des objets de données est la transmission de données à l'hôte, lorsque je souhaite supprimer les champs d'état de l'objet dans le modèle de données avant de l'envoyer. Par exemple, je peux vouloir supprimer tous les champs commençant par "_" de l'objet lorsqu'il est cloné.

Voici le code que j'ai fini par écrire pour faire cela de manière générique, y compris la prise en charge des tableaux et un sélecteur pour choisir les membres à cloner (qui utilise une chaîne "path" pour déterminer le contexte) :

function clone(obj,sel) {
    return (obj ? _clone("",obj,sel) : obj);
    }

function _clone(pth,src,sel) {
    var ret=(src instanceof Array ? [] : {});

    for(var key in src) {
        if(!src.hasOwnProperty(key)) { continue; }

        var val=src[key], sub;

        if(sel) {
            sub+=pth+"/"+key;
            if(!sel(sub,key,val)) { continue; }
            }

        if(val && typeof(val)=='object') {
            if     (val instanceof Boolean) { val=Boolean(val);        }
            else if(val instanceof Number ) { val=Number (val);        }
            else if(val instanceof String ) { val=String (val);        }
            else                            { val=_clone(sub,val,sel); }
            }
        ret[key]=val;
        }
    return ret;
    }

La solution de clonage profond la plus simple et la plus raisonnable, dans l'hypothèse d'un objet racine non nul et sans sélection de membres, est la suivante :

function clone(src) {
    var ret=(src instanceof Array ? [] : {});
    for(var key in src) {
        if(!src.hasOwnProperty(key)) { continue; }
        var val=src[key];
        if(val && typeof(val)=='object') { val=clone(val);  }
        ret[key]=val;
        }
    return ret;
    }

4voto

CPHPython Points 2219

Lo-Dash , qui est maintenant un surensemble de Underscore.js Il dispose de quelques fonctions de clonage en profondeur :

D'un réponse de l'auteur lui-même :

lodash underscore est fournie pour assurer la compatibilité avec la dernière version stable d'Underscore.

3voto

Stitch Points 31

C'est la méthode de clonage en profondeur que j'utilise. Super, j'espère que vous ferez des suggestions

function deepClone (obj) {
    var _out = new obj.constructor;

    var getType = function (n) {
        return Object.prototype.toString.call(n).slice(8, -1);
    }

    for (var _key in obj) {
        if (obj.hasOwnProperty(_key)) {
            _out[_key] = getType(obj[_key]) === 'Object' || getType(obj[_key]) === 'Array' ? deepClone(obj[_key]) : obj[_key];
        }
    }
    return _out;
}

3voto

Kooldandy Points 87

La fonction ci-dessous est le moyen le plus efficace de cloner profondément des objets javascript.

function deepCopy(obj){
    if (!obj || typeof obj !== "object") return obj;

    var retObj = {};

    for (var attr in obj){
        var type = obj[attr];

        switch(true){
            case (type instanceof Date):
                var _d = new Date();
                _d.setDate(type.getDate())
                retObj[attr]= _d;
                break;

            case (type instanceof Function):
                retObj[attr]= obj[attr];
                break;

            case (type instanceof Array):
                var _a =[];
                for (var e of type){
                    //_a.push(e);
                    _a.push(deepCopy(e));
                }
                retObj[attr]= _a;
                break;

            case (type instanceof Object):
                var _o ={};
                for (var e in type){
                    //_o[e] = type[e];
                    _o[e] = deepCopy(type[e]);
                }
                retObj[attr]= _o;
                break;

            default:
                retObj[attr]= obj[attr];
        }
    }
    return retObj;
}

var obj = {
    string: 'test',
    array: ['1'],
    date: new Date(),
    object:{c: 2, d:{e: 3}},
    function: function(){
        return this.date;
    }
};

var copyObj = deepCopy(obj);

console.log('object comparison', copyObj === obj); //false
console.log('string check', copyObj.string === obj.string); //true
console.log('array check', copyObj.array === obj.array); //false
console.log('date check', copyObj2.date === obj.date); //false
console.log('object check', copyObj.object === obj.object); //false
console.log('function check', copyObj.function() === obj.function()); //true

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X