118 votes

Copie en profondeur dans ES6 en utilisant la syntaxe "spread".

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 doit 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 et je dois donc le modifier :

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

C'est moins élégant car il faut savoir quels objets sont passés. Existe-t-il un moyen dans ES6 d'utiliser la syntaxe étendue pour copier profondément un objet ?

0 votes

9 votes

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 devriez juste créer un autre reducer qui travaille sur la tranche enfant de la forme d'état et ensuite utiliser combineReducers pour composer les deux (ou plus) ensemble. Si vous utilisez des techniques de redux idiomatiques, votre problème de clonage profond des objets disparaît.

0 votes

"Existe-t-il un moyen dans ES6 d'utiliser la syntaxe spread pour copier en profondeur un objet ?". Dans le cas général, impossible. L'appel de la syntaxe d'étalement au niveau supérieur quel qu'il soit écrase les propriétés avec profondeur qui auraient dû être fusionnées.

125voto

Nikhil Mahirrao Points 817

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

80 votes

Cela ne fonctionne que si vous n'avez pas besoin de cloner des fonctions. JSON ignorera toutes les fonctions, de sorte que vous ne les aurez pas dans le clone.

9 votes

En dehors des fonctions, vous aurez des problèmes avec undefined et null en utilisant cette méthode.

3 votes

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

79voto

Frank Tan Points 2434

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

Si vous voulez vraiment faire une copie profonde :

  1. Utilisez une bibliothèque. Par exemple, lodash dispose d'une cloneDeep mé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 éléments, vous pouvez vous épargner du travail. Je suppose que vous contrôlez tous les sites d'appel de votre fonction.

  1. Spécifier que tous les callbacks passés à mapCopy doivent retourner 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 fait appel à Object.assign pour créer un nouvel objet, définit les propriétés de e sur ce nouvel objet, puis définit un nouveau titre sur ce nouvel objet. Cela signifie que vous ne modifiez jamais les objets existants et que vous n'en créez de nouveaux qu'en cas de besoin.

  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, mapCopy fait confiance à ses interlocuteurs pour faire le bon choix. C'est pourquoi j'ai dit que cela suppose que vous contrôliez tous les sites d'appel.

6 votes

Object.assign ne copie pas profondément les objets. voir developer.mozilla.org/fr/US/docs/Web/JavaScript/Référence/ - Object.assign() copie les valeurs des propriétés. "Si la valeur source est une référence à un objet, elle ne copie que cette valeur de référence".

0 votes

Bien. C'est une solution alternative qui fait no impliquent une copie profonde. Je vais mettre à jour ma réponse pour être plus explicite à ce sujet.

37voto

Menocomp Points 31

De MDN

Note : La syntaxe Spread va effectivement à un niveau de profondeur lors de la copie d'un tableau. Par conséquent, elle peut être inadaptée à la copie de tableaux multidimensionnels, comme le montre l'exemple suivant (il en va de même avec Object.assign() et la syntaxe d'étalement).

Personnellement, je suggère d'utiliser CloneDeep de Lodash pour le clonage d'objets/de matrices à plusieurs niveaux.

Voici un exemple concret :

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 votes

L'arr6 ne fonctionne pas pour moi. Dans le navigateur (chrome 59.0 qui supporte ES6) j'obtiens Erreur de syntaxe involontaire : Unxpected token ... et dans le nœud 8.9.3 qui supporte ES7 j'obtiens TypeError : undefined is not a functionat repl:1:22

1 votes

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

3 votes

L'arr6 ne fonctionne pas pour moi non plus. Dans le navigateur - chrome 65

25voto

HectorGuo Points 68

Je l'utilise souvent :

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

4voto

Shashidhar Reddy Points 126
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

Utilisation de JSON.stringify y JSON.parse est le meilleur moyen. Parce qu'en utilisant l'opérateur d'étalement, nous n'obtiendrons pas une réponse efficace lorsque l'objet json contient un autre objet. Nous devons le spécifier manuellement.

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