Głęboka kopia w ES6 przy użyciu składni spreadów

103

Próbuję utworzyć metodę głębokiej mapy dla mojego projektu Redux, która będzie działać z obiektami, a nie z tablicami. Czytałem, że w Redux każdy stan nie powinien nic zmieniać w poprzednich stanach.

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

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

    return output;
    }, {});
}

To działa:

    return mapCopy(state, e => {

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

            return e;
        })

Jednak nie kopiuje głęboko elementów wewnętrznych, więc muszę go dostosować, aby:

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

Jest to mniej eleganckie, ponieważ wymaga wiedzy, które obiekty są przekazywane. Czy w ES6 jest sposób, aby użyć składni rozkładówek do głębokiego kopiowania obiektu?

Chłopak
źródło
8
To jest problem XY. Nie trzeba dużo pracować nad głębokimi właściwościami Reduxu. zamiast tego powinieneś po prostu utworzyć kolejny reduktor, który działa na wycinku podrzędnym kształtu stanu, a następnie użyć go combineReducersdo skomponowania dwóch (lub więcej) razem. Jeśli używasz idiomatycznych technik redux, problem głębokiego klonowania obiektów znika.
Dziękuję

Odpowiedzi:

73

Żadna taka funkcjonalność nie jest wbudowana w ES6. Myślę, że masz kilka opcji w zależności od tego, co chcesz zrobić.

Jeśli naprawdę chcesz głęboko skopiować:

  1. Skorzystaj z biblioteki. Na przykład lodash ma cloneDeepmetodę.
  2. Zaimplementuj własną funkcję klonowania.

Alternatywne rozwiązanie konkretnego problemu (bez głębokiej kopii)

Jednak myślę, że jeśli chcesz zmienić kilka rzeczy, możesz zaoszczędzić sobie trochę pracy. Zakładam, że kontrolujesz wszystkie strony wywołujące swoją funkcję.

  1. Określ, że wszystkie wywołania zwrotne przekazane do mapCopymuszą zwracać nowe obiekty zamiast mutowania istniejącego obiektu. Na przykład:

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

    Służy Object.assigndo tworzenia nowego obiektu, ustawia właściwości etego nowego obiektu, a następnie ustawia nowy tytuł dla tego nowego obiektu. Oznacza to, że nigdy nie modyfikujesz istniejących obiektów i tworzysz nowe tylko wtedy, gdy jest to konieczne.

  2. mapCopy może być teraz naprawdę proste:

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

Zasadniczo mapCopypolega na zaufaniu rozmówcom, że postąpią właściwie. Dlatego właśnie powiedziałem, że przy założeniu, że kontrolujesz wszystkie strony połączeń.

Frank Tan
źródło
3
Object. assign nie kopiuje głęboko obiektów. zobacz developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… - Object. assign () kopiuje wartości właściwości. „Jeśli wartość źródłowa jest odniesieniem do obiektu, kopiuje tylko tę wartość odniesienia”.
Greg Somers,
Dobrze. To alternatywne rozwiązanie, które nie wiąże się z głębokim kopiowaniem. Zaktualizuję swoją odpowiedź, aby była bardziej wyraźna.
Frank Tan,
106

Zamiast tego użyj tego do głębokiej kopii

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

Nikhil Mahirrao
źródło
65
Działa to tylko wtedy, gdy nie musisz klonować funkcji. JSON zignoruje wszystkie funkcje, więc nie będziesz ich mieć w klonie.
Noland
7
Oprócz funkcji, będziesz mieć problemy z undefined i null przy użyciu tej metody
James Heazlewood
2
Będziesz mieć również problemy z klasami zdefiniowanymi przez użytkownika, ponieważ łańcuchy prototypów nie są serializowane.
Patrick Roberts
8
Twoje rozwiązanie korzystające z serializacji JSON ma pewne problemy. W ten sposób utracisz wszelkie właściwości JavaScript, które nie mają odpowiednika typu w formacie JSON, takie jak Function lub Infinity. Każda właściwość przypisana do undefined zostanie zignorowana przez JSON.stringify, przez co zostanie pominięta w sklonowanym obiekcie. Ponadto niektóre obiekty są konwertowane na ciągi, takie jak Data, Ustaw, Mapa i wiele innych.
Jonathan Brizio
2
Miałem okropny koszmar, próbując stworzyć prawdziwą kopię szeregu obiektów - obiektów, które były w istocie wartościami danych, bez funkcji. Jeśli to wszystko, o co musisz się martwić, to podejście działa pięknie.
Charlie
30

Z MDN

Uwaga: składnia rozkładania efektywnie sięga o jeden poziom w głąb podczas kopiowania tablicy. W związku z tym może być nieodpowiednie do kopiowania tablic wielowymiarowych, jak pokazuje poniższy przykład (tak samo jest z Object. assign () i składnią rozkładania).

Osobiście sugeruję użycie funkcji cloneDeep firmy Lodash do wielopoziomowego klonowania obiektów / tablic.

Oto działający przykład:

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>

Mina Luke
źródło
4
arr6 nie działa dla mnie. W przeglądarce (chrome 59.0, który obsługuje ES6, otrzymuję Uncaught SyntaxError: Uknown token ... oraz w węźle 8.9.3, który obsługuje ES7, dostaję TypeError: undefined is not a functionat repl: 1:22
Achi Even-dar
@ AchiEven-dar nie jestem ojcem, dlaczego wystąpił błąd. Możesz uruchomić ten kod bezpośrednio w przepełnieniu stosu, naciskając niebieski przycisk Run code snippeti powinien działać poprawnie.
Mina Luke
3
arr6 też nie działa dla mnie. W przeglądarce - chrome 65
yehonatan yehezkel
18

Często używam tego:

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;
        }, {})
    }
}
HectorGuo
źródło
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

Korzystanie JSON.stringifyi JSON.parsejest najlepszym sposobem. Ponieważ używając operatora spread, nie uzyskamy skutecznej odpowiedzi, gdy obiekt json zawiera w sobie inny obiekt. musimy to ręcznie określić.

Shashidhar Reddy
źródło
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;
    }
}
Jeroen Breen
źródło
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
}
user10919042
źródło
Komentarze znajdują się w kodzie dla tych, którzy szukają wyjaśnienia.
Wookies-Will-Code
1
const cloneData = (dataArray) => {
    newData= []
    dataArray.forEach((value) => {
        newData.push({...value})
    })
    return newData
}
  • a = [{name: "siva"}, {name: "siva1"}];
  • b = myCopy (a)
  • b === a // false`
Harish Sekar
źródło
1

Sam wylądowałem na tych odpowiedziach zeszłego dnia, próbując znaleźć sposób na głębokie kopiowanie złożonych struktur, które mogą obejmować linki rekurencyjne. Ponieważ nie byłem zadowolony z niczego sugerowanego wcześniej, sam zaimplementowałem to koło. I działa całkiem dobrze. Mam nadzieję, że to komuś pomoże.

Przykładowe użycie:

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

TheClone = OriginalStruct.deep_copy();

Proszę spojrzeć na https://github.com/latitov/JS_DeepCopy, aby zobaczyć przykłady na żywo, jak go używać, a także jest tam deep_print ().

Jeśli potrzebujesz tego szybko, oto źródło funkcji 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;
    }
}

Twoje zdrowie@!

latitov
źródło
1

Oto funkcja deepClone, która obsługuje wszystkie typy danych pierwotnych, tablic, obiektów i funkcji

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

ganesh phirke
źródło
1

Oto mój algorytm głębokiego kopiowania.

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;
  };
Бектур Муратов
źródło
Musisz również sprawdzić, czy 'obj [prop]! == null' jako typeof (null) również zwraca 'object'
Pramod Mali