Copie profonde dans ES6 en utilisant la syntaxe de diffusion


99

J'essaie de créer une méthode de carte de copie profonde pour mon projet Redux qui fonctionnera avec des objets plutôt que des tableaux. J'ai lu que dans Redux, chaque état ne devrait rien changer aux états précédents.

export const mapCopy = (object, callback) => {
    return Object.keys(object).reduce(function (output, key) {

    output[key] = callback.call(this, {...object[key]});

    return output;
    }, {});
}

Ça marche:

    return mapCopy(state, e => {

            if (e.id === action.id) {
                 e.title = 'new item';
            }

            return e;
        })

Cependant, il ne copie pas en profondeur les éléments internes, je dois donc le modifier pour:

export const mapCopy = (object, callback) => {
    return Object.keys(object).reduce(function (output, key) {

    let newObject = {...object[key]};
    newObject.style = {...newObject.style};
    newObject.data = {...newObject.data};

    output[key] = callback.call(this, newObject);

    return output;
    }, {});
}

Ceci est moins élégant car il nécessite de savoir quels objets sont passés. Existe-t-il un moyen dans ES6 d'utiliser la syntaxe de diffusion pour copier en profondeur un objet?



8
C'est un problème XY. Vous ne devriez pas avoir à travailler beaucoup sur les propriétés profondes dans redux. au lieu de cela, vous devez simplement créer un autre réducteur qui fonctionne sur la tranche enfant de la forme d'état, puis l'utiliser combineReducerspour composer les deux (ou plus) ensemble. Si vous utilisez des techniques de redux idiomatiques, votre problème de clonage profond des objets disparaît.
Merci le

Réponses:


72

Aucune fonctionnalité de ce type n'est intégrée à ES6. Je pense que vous avez quelques options en fonction de ce que vous voulez faire.

Si vous voulez vraiment copier en profondeur:

  1. Utilisez une bibliothèque. Par exemple, lodash a une cloneDeepméthode.
  2. Implémentez votre propre fonction de clonage.

Solution alternative à votre problème spécifique (pas de copie profonde)

Cependant, je pense que si vous êtes prêt à changer quelques choses, vous pouvez vous épargner du travail. Je suppose que vous contrôlez tous les sites d'appels à votre fonction.

  1. Spécifiez que tous les rappels passés à mapCopydoivent renvoyer de nouveaux objets au lieu de muter l'objet existant. Par exemple:

    mapCopy(state, e => {
      if (e.id === action.id) {
        return Object.assign({}, e, {
          title: 'new item'
        });
      } else {  
        return e;
      }
    });

    Cela permet de Object.assigncréer un nouvel objet, définit les propriétés de esur ce nouvel objet, puis définit un nouveau titre sur ce nouvel objet. Cela signifie que vous ne modifiez jamais les objets existants et n'en créez de nouveaux que si nécessaire.

  2. mapCopy peut être très simple maintenant:

    export const mapCopy = (object, callback) => {
      return Object.keys(object).reduce(function (output, key) {
        output[key] = callback.call(this, object[key]);
        return output;
      }, {});
    }

Essentiellement, mapCopyc'est faire confiance à ses appelants pour faire la bonne chose. C'est pourquoi j'ai dit que cela suppose que vous contrôlez tous les sites d'appel.


3
Object.assign ne copie pas les objets en profondeur. voir developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… - Object.assign () copie les valeurs de propriété. "Si la valeur source est une référence à un objet, elle ne copie que cette valeur de référence."
Greg Somers

Droite. Il s'agit d'une solution alternative qui n'implique pas de copie en profondeur. Je mettrai à jour ma réponse pour être plus explicite à ce sujet.
Frank Tan

104

Utilisez plutôt ceci pour la copie profonde

var newObject = JSON.parse(JSON.stringify(oldObject))

var oldObject = {
  name: 'A',
  address: {
    street: 'Station Road',
    city: 'Pune'
  }
}
var newObject = JSON.parse(JSON.stringify(oldObject));

newObject.address.city = 'Delhi';
console.log('newObject');
console.log(newObject);
console.log('oldObject');
console.log(oldObject);


63
Cela ne fonctionne que si vous n'avez pas besoin de cloner des fonctions. JSON ignorera toutes les fonctions afin que vous ne les ayez pas dans le clone.
Noland

7
Outre les fonctions, vous rencontrerez des problèmes avec undefined et null en utilisant cette méthode
James Heazlewood

2
Vous rencontrerez également des problèmes avec les classes définies par l'utilisateur, car les chaînes de prototypes ne sont pas sérialisées.
Patrick Roberts

8
Votre solution utilisant la sérialisation JSON a quelques problèmes. En faisant cela, vous perdrez toute propriété Javascript qui n'a pas de type équivalent dans JSON, comme Function ou Infinity. Toute propriété attribuée à undefined sera ignorée par JSON.stringify, ce qui entraînera son absence sur l'objet cloné. En outre, certains objets sont convertis en chaînes, telles que Date, Set, Map et bien d'autres.
Jonathan Brizio

2
J'avais un horrible cauchemar d'essayer de créer une copie fidèle d'un tableau d'objets - des objets qui étaient essentiellement des valeurs de données, pas de fonctions. Si c'est tout ce dont vous avez à vous soucier, cette approche fonctionne à merveille.
Charlie le

29

De MDN

Remarque: la syntaxe Spread va effectivement d'un niveau de profondeur lors de la copie d'un tableau. Par conséquent, il peut ne pas convenir pour copier des tableaux multidimensionnels comme le montre l'exemple suivant (c'est la même chose avec Object.assign () et la syntaxe de propagation).

Personnellement, je suggère d'utiliser la fonction cloneDeep de Lodash pour le clonage d'objets / tableaux à plusieurs niveaux.

Voici un exemple de travail:

const arr1 = [{ 'a': 1 }];

const arr2 = [...arr1];

const arr3 = _.clone(arr1);

const arr4 = arr1.slice();

const arr5 = _.cloneDeep(arr1);

const arr6 = [...{...arr1}]; // a bit ugly syntax but it is working!


// first level
console.log(arr1 === arr2); // false
console.log(arr1 === arr3); // false
console.log(arr1 === arr4); // false
console.log(arr1 === arr5); // false
console.log(arr1 === arr6); // false

// second level
console.log(arr1[0] === arr2[0]); // true
console.log(arr1[0] === arr3[0]); // true
console.log(arr1[0] === arr4[0]); // true
console.log(arr1[0] === arr5[0]); // false
console.log(arr1[0] === arr6[0]); // false
<script src="https://cdnjs.cloudflare.com/ajax/libs/lodash.js/4.17.4/lodash.js"></script>


4
arr6 ne fonctionne pas pour moi. Dans le navigateur (chrome 59.0 qui prend en charge ES6 I get Uncaught SyntaxError: Uncaught token ... et dans le nœud 8.9.3 qui prend en charge ES7, j'obtiens TypeError: undefined n'est pas une fonction repl: 1: 22
Achi Even-dar

@ AchiEven-dar pas sire pourquoi vous avez une erreur. Vous pouvez exécuter ce code directement dans stackoverflow en appuyant sur le bouton bleu Run code snippetet il doit fonctionner correctement.
Mina Luke

3
arr6 ne fonctionne pas pour moi aussi. Dans le navigateur - chrome 65
yehonatan yehezkel

18

J'utilise souvent ceci:

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

    if(obj instanceof Date) {
        return new Date(obj.getTime());
    }

    if(obj instanceof Array) {
        return obj.reduce((arr, item, i) => {
            arr[i] = deepCopy(item);
            return arr;
        }, []);
    }

    if(obj instanceof Object) {
        return Object.keys(obj).reduce((newObj, key) => {
            newObj[key] = deepCopy(obj[key]);
            return newObj;
        }, {})
    }
}

3
const a = {
  foods: {
    dinner: 'Pasta'
  }
}
let b = JSON.parse(JSON.stringify(a))
b.foods.dinner = 'Soup'
console.log(b.foods.dinner) // Soup
console.log(a.foods.dinner) // Pasta

Utiliser JSON.stringifyet JSON.parseest le meilleur moyen. Parce qu'en utilisant l'opérateur spread, nous n'obtiendrons pas la réponse efficace lorsque l'objet json contient un autre objet à l'intérieur. nous devons le spécifier manuellement.


1
function deepclone(obj) {
    let newObj = {};

    if (typeof obj === 'object') {
        for (let key in obj) {
            let property = obj[key],
                type = typeof property;
            switch (type) {
                case 'object':
                    if( Object.prototype.toString.call( property ) === '[object Array]' ) {
                        newObj[key] = [];
                        for (let item of property) {
                            newObj[key].push(this.deepclone(item))
                        }
                    } else {
                        newObj[key] = deepclone(property);
                    }
                    break;
                default:
                    newObj[key] = property;
                    break;

            }
        }
        return newObj
    } else {
        return obj;
    }
}

1
// use: clone( <thing to copy> ) returns <new copy>
// untested use at own risk
function clone(o, m){
  // return non object values
  if('object' !==typeof o) return o
  // m: a map of old refs to new object refs to stop recursion
  if('object' !==typeof m || null ===m) m =new WeakMap()
  var n =m.get(o)
  if('undefined' !==typeof n) return n
  // shallow/leaf clone object
  var c =Object.getPrototypeOf(o).constructor
  // TODO: specialize copies for expected built in types i.e. Date etc
  switch(c) {
    // shouldn't be copied, keep reference
    case Boolean:
    case Error:
    case Function:
    case Number:
    case Promise:
    case String:
    case Symbol:
    case WeakMap:
    case WeakSet:
      n =o
      break;
    // array like/collection objects
    case Array:
      m.set(o, n =o.slice(0))
      // recursive copy for child objects
      n.forEach(function(v,i){
        if('object' ===typeof v) n[i] =clone(v, m)
      });
      break;
    case ArrayBuffer:
      m.set(o, n =o.slice(0))
      break;
    case DataView:
      m.set(o, n =new (c)(clone(o.buffer, m), o.byteOffset, o.byteLength))
      break;
    case Map:
    case Set:
      m.set(o, n =new (c)(clone(Array.from(o.entries()), m)))
      break;
    case Int8Array:
    case Uint8Array:
    case Uint8ClampedArray:
    case Int16Array:
    case Uint16Array:
    case Int32Array:
    case Uint32Array:
    case Float32Array:
    case Float64Array:
      m.set(o, n =new (c)(clone(o.buffer, m), o.byteOffset, o.length))
      break;
    // use built in copy constructor
    case Date:
    case RegExp:
      m.set(o, n =new (c)(o))
      break;
    // fallback generic object copy
    default:
      m.set(o, n =Object.assign(new (c)(), o))
      // recursive copy for child objects
      for(c in n) if('object' ===typeof n[c]) n[c] =clone(n[c], m)
  }
  return n
}

Les commentaires sont dans le code pour ceux qui recherchent des explications.
Wookies-Will-Code

1
const cloneData = (dataArray) => {
    newData= []
    dataArray.forEach((value) => {
        newData.push({...value})
    })
    return newData
}
  • a = [{nom: "siva"}, {nom: "siva1"}];
  • b = maCopie (a)
  • b === a // faux`

1

J'ai moi-même atterri sur ces réponses le jour dernier, essayant de trouver un moyen de copier en profondeur des structures complexes, qui peuvent inclure des liens récursifs. Comme je n'étais pas satisfait de tout ce qui avait été suggéré auparavant, j'ai moi-même implémenté cette roue. Et ça marche plutôt bien. J'espère que ça aide quelqu'un.

Exemple d'utilisation:

OriginalStruct.deep_copy = deep_copy; // attach the function as a method

TheClone = OriginalStruct.deep_copy();

Veuillez regarder https://github.com/latitov/JS_DeepCopy pour des exemples en direct sur la façon de l'utiliser, et deep_print () est également là.

Si vous en avez besoin rapidement, voici la source de la fonction deep_copy ():

function deep_copy() {
    'use strict';   // required for undef test of 'this' below

    // Copyright (c) 2019, Leonid Titov, Mentions Highly Appreciated.

    var id_cnt = 1;
    var all_old_objects = {};
    var all_new_objects = {};
    var root_obj = this;

    if (root_obj === undefined) {
        console.log(`deep_copy() error: wrong call context`);
        return;
    }

    var new_obj = copy_obj(root_obj);

    for (var id in all_old_objects) {
        delete all_old_objects[id].__temp_id;
    }

    return new_obj;
    //

    function copy_obj(o) {
        var new_obj = {};
        if (o.__temp_id === undefined) {
            o.__temp_id = id_cnt;
            all_old_objects[id_cnt] = o;
            all_new_objects[id_cnt] = new_obj;
            id_cnt ++;

            for (var prop in o) {
                if (o[prop] instanceof Array) {
                    new_obj[prop] = copy_array(o[prop]);
                }
                else if (o[prop] instanceof Object) {
                    new_obj[prop] = copy_obj(o[prop]);
                }
                else if (prop === '__temp_id') {
                    continue;
                }
                else {
                    new_obj[prop] = o[prop];
                }
            }
        }
        else {
            new_obj = all_new_objects[o.__temp_id];
        }
        return new_obj;
    }
    function copy_array(a) {
        var new_array = [];
        if (a.__temp_id === undefined) {
            a.__temp_id = id_cnt;
            all_old_objects[id_cnt] = a;
            all_new_objects[id_cnt] = new_array;
            id_cnt ++;

            a.forEach((v,i) => {
                if (v instanceof Array) {
                    new_array[i] = copy_array(v);
                }
                else if (v instanceof Object) {
                    new_array[i] = copy_object(v);
                }
                else {
                    new_array[i] = v;
                }
            });
        }
        else {
            new_array = all_new_objects[a.__temp_id];
        }
        return new_array;
    }
}

À votre santé@!


1

Voici mon algorithme de copie profonde.

const DeepClone = (obj) => {
     if(obj===null||typeof(obj)!=='object')return null;
    let newObj = { ...obj };

    for (let prop in obj) {
      if (
        typeof obj[prop] === "object" ||
        typeof obj[prop] === "function"
      ) {
        newObj[prop] = DeepClone(obj[prop]);
      }
    }

    return newObj;
  };

Vous devez également vérifier si 'obj [prop]! == null' as typeof (null) renvoie également 'object'
Pramod Mali

0

Voici la fonction deepClone qui gère tous les types de données primitives, tableau, objet, fonction

function deepClone(obj){
	if(Array.isArray(obj)){
		var arr = [];
		for (var i = 0; i < obj.length; i++) {
			arr[i] = deepClone(obj[i]);
		}
		return arr;
	}

	if(typeof(obj) == "object"){
		var cloned = {};
		for(let key in obj){
			cloned[key] = deepClone(obj[key])
		}
		return cloned;	
	}
	return obj;
}

console.log( deepClone(1) )

console.log( deepClone('abc') )

console.log( deepClone([1,2]) )

console.log( deepClone({a: 'abc', b: 'def'}) )

console.log( deepClone({
  a: 'a',
  num: 123,
  func: function(){'hello'},
  arr: [[1,2,3,[4,5]], 'def'],
  obj: {
    one: {
      two: {
        three: 3
      }
    }
  }
}) ) 

En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.