Глубокая копия в ES6 с использованием синтаксиса распространения

103

Я пытаюсь создать метод карты глубокого копирования для моего проекта 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 использовать синтаксис распространения для глубокого копирования объекта?

Парень
источник
8
Это проблема XY. Вам не придется много работать над глубокими свойствами в redux. вместо этого вы должны просто создать еще один редуктор, который работает с дочерним фрагментом фигуры состояния, а затем использовать combineReducersдля объединения двух (или более) вместе. Если вы используете идиоматические методы редукции, ваша проблема глубокого клонирования объектов исчезнет.
Спасибо

Ответы:

73

В 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доверяет своим абонентам делать правильные вещи. Вот почему я сказал, что это предполагает, что вы контролируете все сайты вызовов.

Фрэнк Тан
источник
3
Object.assign не копирует объекты глубоко. см. developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/… - Object.assign () копирует значения свойств. «Если исходное значение является ссылкой на объект, оно копирует только это ссылочное значение».
Грег Сомерс
Правильно. Это альтернативное решение, не требующее глубокого копирования. Я обновлю свой ответ, чтобы более четко об этом сказать.
Фрэнк Тан
105

Вместо этого используйте это для глубокой копии

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

Никхил Махиррао
источник
65
Это работает, только если вам не нужно клонировать функции. JSON игнорирует все функции, поэтому у вас не будет их в клоне.
Ноланд
7
Помимо функций, при использовании этого метода у вас будут проблемы с undefined и null
Джеймс Хизлвуд,
3
У вас также будут проблемы с любыми пользовательскими классами, поскольку цепочки прототипов не сериализуются.
Патрик Робертс
9
У вашего решения с использованием сериализации JSON есть некоторые проблемы. Сделав это, вы потеряете любое свойство Javascript, которое не имеет эквивалентного типа в JSON, например Function или Infinity. Любое свойство, присвоенное undefined, будет игнорироваться JSON.stringify, в результате чего они будут пропущены в клонированном объекте. Кроме того, некоторые объекты преобразуются в строки, например Date, Set, Map и многие другие.
Джонатан Брицио
2
Мне снился ужасный кошмар попытки создать истинную копию массива объектов - объектов, которые по сути были значениями данных, а не функциями. Если это все, о чем вам нужно беспокоиться, то этот подход прекрасно работает.
Чарли
30

Из 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
arr6 у меня не работает. В браузере (chrome 59.0, который поддерживает ES6, я получаю Uncaught SyntaxError: Unexpected token ... и в узле 8.9.3, который поддерживает ES7, я получаю TypeError: undefined не является функциональным ответом: 1: 22
Achi Even-dar
@ AchiEven-dar не сир, почему у вас ошибка. Вы можете запустить этот код непосредственно в stackoverflow, нажав синюю кнопку, Run code snippetи он должен работать правильно.
Мина Люк
3
arr6 у меня тоже не работает. В браузере - хром 65
yehonatan yehezkel
18

Я часто использую это:

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
источник
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

Использование JSON.stringifyи JSON.parse- лучший способ. Потому что, используя оператор распространения, мы не получим эффективного ответа, когда объект json содержит внутри себя другой объект. нам нужно указать это вручную.

Шашидхар Редди
источник
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;
    }
}
Джерун Брин
источник
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
источник
Комментарии в коде для тех, кто ищет объяснения.
Wookies-Will-Code
1
const cloneData = (dataArray) => {
    newData= []
    dataArray.forEach((value) => {
        newData.push({...value})
    })
    return newData
}
  • a = [{name: "siva"}, {name: "siva1"}];
  • b = myCopy (а)
  • b === a // ложь`
Хариш Секар
источник
1

Я сам натолкнулся на эти ответы в прошлый день, пытаясь найти способ глубокого копирования сложных структур, которые могут включать рекурсивные ссылки. Поскольку меня не устраивало что-либо из предложенных ранее, я сам реализовал это колесо. И работает неплохо. Надеюсь, это кому-то поможет.

Пример использования:

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

Ура @!

латитов
источник
1

Вот функция deepClone, которая обрабатывает все типы данных примитивов, массивов, объектов и функций.

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

Ганеш Фирке
источник
1

Вот мой алгоритм глубокого копирования.

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;
  };
Бектур Муратов
источник
Вам также необходимо проверить, возвращает ли 'obj [prop]! == null' как typeof (null) также 'object'
Прамод Мали,