스프레드 구문을 사용하는 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두 개 (또는 그 이상)를 함께 구성하는 데 사용해야 합니다. 관용적 redux 기술을 사용하면 객체 딥 복제 문제가 사라집니다.
감사합니다

답변:


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 ()은 속성 값을 복사합니다. "소스 값이 개체에 대한 참조이면 해당 참조 값만 복사합니다."
Greg Somers

권리. 이것은 전체 복사를 포함 하지 않는 대체 솔루션입니다 . 나는 그것에 대해 더 명확하게 대답을 업데이트 할 것입니다.
Frank Tan

102

대신 딥 카피에 이것을 사용하십시오.

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
프로토 타입 체인이 직렬화되지 않기 때문에 사용자 정의 클래스에도 문제가 있습니다.
Patrick Roberts

8
JSON 직렬화를 사용하는 솔루션에는 몇 가지 문제가 있습니다. 이렇게하면 Function 또는 Infinity와 같이 JSON에 동등한 유형이없는 Javascript 속성이 손실됩니다. undefined에 할당 된 모든 속성은 JSON.stringify에서 무시되어 복제 된 개체에서 누락됩니다. 또한 일부 개체는 Date, Set, Map 등의 문자열로 변환됩니다.
Jonathan Brizio

2
나는 본질적으로 데이터 값이었고 기능이없는 개체 배열의 실제 복사본을 만들려고하는 끔찍한 악몽을 꾸었습니다. 이것이 당신이 걱정해야 할 전부라면,이 접근법은 아름답게 작동합니다.
Charlie

29

MDN에서

참고 : 스프레드 구문은 배열을 복사하는 동안 효과적으로 한 수준 깊이가됩니다. 따라서 다음 예제와 같이 다차원 배열을 복사하는 데 적합하지 않을 수 있습니다 (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은 나를 위해 작동하지 않습니다. 브라우저 (ES6를 지원하는 크롬 59.0에서 Uncaught SyntaxError : Unexpected token ... 그리고 ES7을 지원하는 node 8.9.3에서 TypeError : undefined is not a functionat repl : 1 : 22
Achi Even-dar

@ AchiEven-dar는 왜 당신이 오류를 얻었는지 씨가 아닙니다. 파란색 버튼을 눌러이 코드를 stackoverflow에서 직접 실행할 수 있으며 Run code snippet올바르게 실행되어야합니다.
Mina Luke

3
arr6도 나를 위해 작동하지 않습니다. 브라우저에서 - 크롬 65
yehonatan yehezkel

17

나는 이것을 자주 사용합니다 :

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 = [{이름 : "siva"}, {이름 : "siva1"}];
  • b = myCopy (a)
  • 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

여기 내 딥 카피 알고리즘이 있습니다.

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

또한 typeof (null)로 'obj [prop]! == null'도 'object'를 반환하는지 확인해야합니다
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
      }
    }
  }
}) ) 

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.