Глубокое копирование в 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. Я думаю, у вас есть несколько вариантов в зависимости от того, что вы хотите сделать.

Если вы действительно хотите глубокое копирование:

  1. Используйте библиотеку. Например, в lodash есть метод cloneDeep.
  2. Реализуйте свою собственную функцию клонирования.

Альтернативное решение вашей конкретной проблемы (без глубокого копирования)

Тем не менее, я думаю, что если вы хотите изменить пару вещей, вы можете сэкономить себе немного работы. Я предполагаю, что вы контролируете все сайты вызовов для вашей функции.

  1. Укажите, что все обратные вызовы, передаваемые в mapCopy должны возвращать новые объекты, а не изменять существующий объект. Например:

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

    Это использует Object.assign для создания нового объекта, устанавливает свойства e для этого нового объекта, затем устанавливает новый заголовок для этого нового объекта. Это означает, что вы никогда не изменяете существующие объекты и создаете новые только при необходимости.

  2. 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.

Вышеуказанная функция выполняет следующую последовательность операций:

  1. Если копируемый объект является массивом, 1a) передает свое разрешенное значение в Object.values(), в противном случае 2b) устанавливает функцию для возврата разрешенного объекта,
  2. Создать массив из ключей объекта для копирования,
  3. Введите массив ключей в функцию Reduce,
  4. Установите/назначьте значение для нового объекта по заданному ключу: 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

*/