web-dev-qa-db-fra.com

Copie profonde dans ES6 à l'aide du signe de propagation

J'essaie de créer une méthode de copie en profondeur pour mon projet Redux qui fonctionne 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;
    }, {});
}

C'est moins élégant car il faut savoir quels objets sont passés. Existe-t-il un moyen dans ES6 d’utiliser le signe spread pour copier en profondeur un objet?

53
Guy

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 copier en profondeur:

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

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

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

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

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

    Cela utilise 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 n'en créez de nouveaux que lorsque cela est 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;
      }, {});
    }
    

mapCopy fait essentiellement confiance à ses appelants pour agir correctement. C'est pourquoi j'ai dit que cela suppose que vous contrôliez tous les sites d'appels.

48
Frank Tan

Utilisez plutôt ceci pour la copie en profondeur

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

60
Nikhil Mahirrao

De MDN

Remarque: La syntaxe Spread a pour effet de gagner un niveau lors de la copie d'un tableau. Par conséquent, il peut être inapproprié de copier des tableaux multidimensionnels, comme le montre l'exemple suivant (il en va de même avec Object.assign () et la syntaxe spread).

Personnellement, je suggère d'utiliser cloneDeep function de Lodash pour le clonage multi-niveaux d'objets/tableaux.

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>

18
Mina Luke

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

J'ai moi-même abordé ces réponses le jour dernier en essayant de trouver un moyen de copier en profondeur des structures complexes pouvant inclure des liens récursifs. Comme je n’étais pas satisfait de tout ce qui a été suggéré auparavant, j’ai mis cette roue en œuvre moi-même. 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 concrets d'utilisation, ainsi que deep_print ().

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
Leonid Titov
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
Jeroen Breen
// 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
}
0
user10919042
const cloneData = (dataArray) => {
    newData= []
    dataArray.forEach((value) => {
        newData.Push({...value})
    })
    return newData
}
  • a = [{name: "siva"}, {name: "siva1"}];
  • b = maCopie (a)
  • b === a // false`
0
Harish Sekar

Je cherchais un moyen de copier fonctionnellement un objet en JavaScript. Je ne pouvais pas en trouver un, alors je l'ai fait. Cet algorithme est immuable, ne produit pas d'effets secondaires et est déclaratif plutôt qu'impératif. Prendre plaisir!

const deepCopy = obj => (Array.isArray(obj) ? Object.values : obj=>obj)(Object.keys(obj).reduce((acc, key) => 
  ({...acc, [key]: (
    !obj[key] ? obj[key]
    : typeof obj[key] === 'object' ? deepCopy(obj[key])
    : obj[key]
  )}),
  {}
))

Ce qui se passe essentiellement, c’est que les clés de l’objet sont réduites en un autre objet. Il gère de manière récursive les objets et les tableaux imbriqués, convertissant les tableaux en objets avec les index numériques utilisés comme clés avant la copie. Pour taper la conversion des tableaux imbriqués en objets, j'utilise Object.values.

La fonction ci-dessus effectue la séquence d'opérations suivante: 

  1. Si l'objet à copier est un tableau, 1a) transmettez sa valeur résolue à Object.values ​​(), sinon 2b) définissez la fonction pour renvoyer l'objet résolu,
  2. Créer un tableau à partir des clés de l'objet à copier,
  3. Introduisez le tableau de clés dans la fonction de réduction,
  4. Définissez/attribuez la valeur sur le nouvel objet à la clé donnée: 4a) Si la valeur est falsy, définissez-le; 4b) Si la valeur est un objet (inclut implicitement des tableaux), deepCopy la valeur puis attribuez it; 4c) Sinon, réglez-le.
0
Jacob Penney