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


99

Я намагаюся створити метод глибокої копії карти для мого проекту 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для складання двох (або більше) разом. Якщо ви використовуєте ідіоматичні методи відновлення, ваша проблема глибокого клонування об'єктів зникає.
Дякую,

Відповіді:


72

Жодна така функціональність не вбудована в 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 () копіює значення властивостей. "Якщо вихідне значення є посиланням на об'єкт, воно лише копіює це посилальне значення."
Грег Сомерс

Правильно. Це альтернативне рішення, яке не передбачає глибокого копіювання. Я оновлю свою відповідь, щоб бути більш чітким щодо цього.
Frank Tan

104

Натомість використовуйте це для глибокої копії

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


63
Це працює, лише якщо вам не потрібно клонувати функції. JSON буде ігнорувати всі функції, тому ви не матимете їх у клоні.
Ноланд

7
Окрім функцій, у вас будуть проблеми з невизначеним та нульовим використанням цього методу
Джеймс Хізлвуд,

2
У вас також будуть проблеми з будь-якими визначеними користувачем класами, оскільки ланцюжки прототипів не серіалізовані.
Патрік Робертс

8
Ваше рішення із використанням серіалізації JSON має деякі проблеми. Роблячи це, ви втратите будь-яку властивість Javascript, яка не має еквівалентного типу в JSON, наприклад, функція або нескінченність. Будь-яке властивість, призначене невизначеному, буде проігноровано JSON.stringify, через що їх буде пропущено на клонованому об’єкті. Крім того, деякі об'єкти перетворюються на рядки, такі як Дата, Встановити, Карта та багато інших.
Джонатан

2
Я мав жахливий кошмар, намагаючись створити справжню копію масиву об’єктів - об’єктів, які по суті були значеннями даних, не мали жодних функцій. Якщо це все, про що вам слід турбуватися, то такий підхід працює чудово.
Чарлі

29

З MDN

Примітка: Синтаксис розповсюдження ефективно копіює масив на один рівень глибше. Тому він може бути непридатним для копіювання багатовимірних масивів, як показано в наступному прикладі (те саме з Object.assign () та синтаксисом поширення).

Особисто я пропоную використовувати функцію cloashDeep Лодаша для багаторівневого клонування об’єктів / масивів.

Ось робочий приклад:

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і він повинен працювати правильно.
Mina Luke

3
arr6 у мене теж не працює. У браузері - chrome 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;
        }, {})
    }
}

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
}

Коментарі містяться в коді для тих, хто шукає пояснення.
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`

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

Ось мій алгоритм глибокої копії.

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) також повертає "об'єкт"
Pramod Mali

0

Ось функція 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
      }
    }
  }
}) ) 

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.