Глубокое копирование в ES6 с использованием синтаксиса распространения
Я пытаюсь создать метод карты глубокого копирования для моего проекта Redux, который будет работать с объектами, а не с массивами. Я читал, что в Redux каждое состояние не должно ничего менять в предыдущих состояниях.
export const mapCopy = (object, callback) => {
return Object.keys(object).reduce(function (output, key) {
output[key] = callback.call(this, {...object[key]});
return output;
}, {});
}
Оно работает:
return mapCopy(state, e => {
if (e.id === action.id) {
e.title = 'new item';
}
return e;
})
Однако он не копирует глубоко внутренние элементы, поэтому мне нужно настроить его так:
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;
}, {});
}
Это менее элегантно, так как требует знать, какие объекты передаются. Есть ли способ в ES6 использовать синтаксис распространения для глубокого копирования объекта?
Ответы
Ответ 1
Такая функциональность не встроена в ES6. Я думаю, у вас есть несколько вариантов в зависимости от того, что вы хотите сделать.
Если вы действительно хотите глубокое копирование:
- Используйте библиотеку. Например, в lodash есть метод
cloneDeep
. - Реализуйте свою собственную функцию клонирования.
Альтернативное решение вашей конкретной проблемы (без глубокого копирования)
Тем не менее, я думаю, что если вы хотите изменить пару вещей, вы можете сэкономить себе немного работы. Я предполагаю, что вы контролируете все сайты вызовов для вашей функции.
-
Укажите, что все обратные вызовы, передаваемые в mapCopy
должны возвращать новые объекты, а не изменять существующий объект. Например:
mapCopy(state, e => {
if (e.id === action.id) {
return Object.assign({}, e, {
title: 'new item'
});
} else {
return e;
}
});
Это использует Object.assign
для создания нового объекта, устанавливает свойства e
для этого нового объекта, затем устанавливает новый заголовок для этого нового объекта. Это означает, что вы никогда не изменяете существующие объекты и создаете новые только при необходимости.
-
mapCopy
может быть очень простым:
export const mapCopy = (object, callback) => {
return Object.keys(object).reduce(function (output, key) {
output[key] = callback.call(this, object[key]);
return output;
}, {});
}
По сути, mapCopy
доверяет своим вызывающим mapCopy
делать правильные вещи. Вот почему я сказал, что предполагается, что вы контролируете все сайты вызовов.
Ответ 2
Вместо этого используйте это для глубокой копии
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);
Ответ 3
От MDN
Примечание. Синтаксис Spread эффективно копируется на один уровень при копировании массива. Следовательно, он может быть неподходящим для копирования многомерных массивов, как показано в следующем примере (то же самое с Object.assign() и синтаксисом распространения).
Лично я предлагаю использовать функцию Lodash cloneDeep для многоуровневого клонирования объектов/массивов.
Вот рабочий пример:
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
Я часто использую это:
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;
}, {})
}
}
Ответ 5
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;
}
}
Ответ 6
// 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
}
Ответ 7
Я сам нашел эти ответы в прошлый день, пытаясь найти способ глубокого копирования сложных структур, которые могут включать в себя рекурсивные ссылки. Поскольку я не был удовлетворен тем, что предлагалось ранее, я сам внедрил этот руль. И это работает довольно хорошо. Надеюсь, это кому-нибудь поможет.
Пример использования:
OriginalStruct.deep_copy = deep_copy; // attach the function as a method
TheClone = OriginalStruct.deep_copy();
Пожалуйста, посмотрите на https://github.com/latitov/JS_DeepCopy живые примеры, как его использовать, а также там есть deep_print().
Если вам это нужно быстро, прямо здесь источник функции 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;
}
}
Ура @!
Ответ 8
const cloneData = (dataArray) => {
newData= []
dataArray.forEach((value) => {
newData.push({...value})
})
return newData
}
- a = [{name: "siva"}, {name: "siva1"}];
- b = myCopy (a)
- b === a//false '
Ответ 9
Я искал способ функционально глубокого копирования объекта в JavaScript. Я не мог найти один, поэтому я сделал это. Этот алгоритм неизменен и не вызывает побочных эффектов. Он работает с многомерными объектами и массивами, рекурсивно копируя следующий уровень перед разрешением.
const deepCopy = obj => (
Array.isArray(obj)
? Object.values
: obj=>obj
)(Object.keys(obj).reduce(
(acc, key) => {
return {
...acc,
[key]: (
!obj[key] ? obj[key]
: typeof obj[key] === 'object' ? deepCopy(obj[key])
: obj[key]
)
}
},
{}
))
Ключи от объекта превращаются в другой объект. Он рекурсивно обрабатывает вложенные объекты и массивы, преобразовывая массивы в объекты с числовыми индексами, используемыми в качестве ключей перед копированием. Чтобы напечатать преобразованные вложенные массивы обратно в объекты, я использую Object.values.
Вышеуказанная функция выполняет следующую последовательность операций:
- Если копируемый объект является массивом, 1a) передает свое разрешенное значение в Object.values(), в противном случае 2b) устанавливает функцию для возврата разрешенного объекта,
- Создать массив из ключей объекта для копирования,
- Введите массив ключей в функцию Reduce,
- Установите/назначьте значение для нового объекта по заданному ключу: 4a) Если значение является ложным, установите его; 4b) Если значение является объектом (неявно включает в себя массивы), deepCopy значение затем назначьте его; 4c) В противном случае установите его.
ОБНОВЛЕНИЕ: мне нужно улучшить производительность операции глубокого копирования, используемой в программном обеспечении на моей работе. Я провел некоторое тестирование и обнаружил, что моя предыдущая реализация была неэффективна с тяжелыми операциями с глубоко вложенными объектами. Я подозреваю, что это из-за плохого выбора шаблона использования функции копирования, которая работала с массивами при подаче в Object.values, эффективно удваивая размер стека вызовов с каждым рекурсором.
Самым эффективным шаблоном, который я смог найти, была deepCopy, используемая вместе с deepArrayCopy. Мне удалось добиться в 2-5 раз лучшей производительности, чем у lodash cloneDeep.
const copy = item => {
return !item ? item
: Array.isArray(item) ? deepArrayCopy(item)
: Object.prototype.toString.call(item).includes('Date') ? new Date(item.getTime())
: typeof item === 'object' ? deepCopy(item)
: item
}
const deepCopy = obj => {
const objKeys = Object.keys(obj)
const newObj = {}
let i = objKeys.length
let key
while(i--) {
key = objKeys[i]
item = obj[key]
newObj[key] = copy(item)
}
return newObj
}
const deepArrayCopy = arr => {
let i = arr.length
let item
const newArr = []
while(i--) {
item = arr[i]
newArr[i] = copy(item)
}
return newArr
}
deepCopy({...})
Ниже приведен тестовый код с оценками производительности и результатами:
const myObject = {
name: 'John',
email: '[email protected]',
fn: new Date()
}
const iterations = 100000
let t0, t1, j, objCopy
const copy = item => {
return !item ? item
: Array.isArray(item) ? deepArrayCopy(item)
: Object.prototype.toString.call(item).includes('Date') ? new Date(item.getTime())
: typeof item === 'object' ? deepCopy(item)
: item
}
const deepCopy = obj => {
const objKeys = Object.keys(obj)
const newObj = {}
let i = objKeys.length
let key
while(i--) {
key = objKeys[i]
item = obj[key]
newObj[key] = copy(item)
}
return newObj
}
const deepArrayCopy = arr => {
let i = arr.length
let item
const newArr = []
while(i--) {
item = arr[i]
newArr[i] = copy(item)
}
return newArr
}
/******* CUSTOM TEST ********/
t0 = performance.now()
j = iterations
while(j--) {
objCopy = deepCopy(myObject)
}
console.log('copy: ', objCopy)
t1 = performance.now()
const customTime = t1 - t0
console.log('Custom es6 pure took ${customTime}ms\nAverage: ${(customTime) / iterations}\n\n')
/********** END CUSTOM TEST ***********/
/******* LODASH TEST ********/
t0 = performance.now()
j = iterations
while(j--) {
objCopy = _(myObject).cloneDeep()
}
console.log('copy: ', objCopy)
t1 = performance.now()
const lodashTime = t1 - t0
console.log('Lodash took ${lodashTime}ms\nAverage: ${(lodashTime) / iterations}\n\n')
/********** END LODASH TEST ***********/
console.log('Custom function ${lodashTime / customTime}x faster than lodash cloneDeep')
/*
RESULTS:
TEST ONE: SHALLOW NESTING
input:
const myObject = {
name: 'John',
email: '[email protected]',
array: [1, 2, 3, 4]
}
Custom es6 pure took 48.95500000566244ms
Average: 0.0004895500000566245
app.js:57 Lodash took 287.16000000713393ms
Average: 0.0028716000000713394
app.js:60 Custom function 5.865795117432728x faster than lodash cloneDeep
TEST TWO: DEEP NESTING
const myObject = {
name: 'John',
email: '[email protected]',
array: [1, 2, 3, 4],
nestedObj0: {1: {2: {3: {4: {5: {6: {7: {8: {9: {}}}}}}}}}},
nestedObj1: {1: {2: {3: {4: {5: {6: {7: {8: {9: {}}}}}}}}}},
nestedObj2: {1: {2: {3: {4: {5: {6: {7: {8: {9: {}}}}}}}}}},
nestedObj4: {1: {2: {3: {4: {5: {6: {7: {8: {9: {}}}}}}}}}},
deepArr: [1, [2,[3,[4,[5,[[[[[[]]]]]]]]]]]
}
Custom es6 pure took 1823.8550000241958ms
Average: 0.01823855000024196
Lodash took 4507.660000002943ms
Average: 0.04507660000002943
Custom function 2.4715012980435085x faster than lodash cloneDeep
*/