본문 바로가기

개발/JavaScript

[Javscript] 깊은 복사, 얕은 복사

얕은 복사 (Shallow Copy) 

참조값이 같다

= 연산자

// 아예 선언을 따로 한 경우
const obj1 = {value : 10};
const newObj1 = {value : 10};
console.log(obj1 === newObj1); // false - 참조값이 다르다

// = 연산자를 이용해 얕은 복사(객체)
const obj = {value : 10};
const newObj = obj;
console.log(obj === newObj); // true - 참조값이 같다

// = 연산자를 이용해 얕은 복사(배열)
const arr = [1, 2, 3]
const newArr = arr
console.log(arr === newArr) // true - 참조값이 같다

// 참조 값이 같은 경우 원본 데이터(arr)에 데이터를 넣으면 newArr이 영향을 받는다.
arr.push(4)
console.log(newArr) // [1,2,3,4]

 


깊은복사 + 얕은 복사 (Deep + Shallow Copy)

Object.assign()

https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Object/assign

 

Object.assign() - JavaScript | MDN

Object.assign() 메서드는 출처 객체들의 모든 열거 가능한 자체 속성을 복사해 대상 객체에 붙여넣습니다. 그 후 대상 객체를 반환합니다.

developer.mozilla.org

 

const category = {category: "espresso"}
const category1 = Object.assign({}, category); // false - 참조값이 다르다
const category2 = Object.assign(category, {}); // true - 참조값이 같다
const category3 = Object.assign(category) // true - 참조값이 같다

console.log(category === category1) // false
console.log(category === category2) // true
console.log(category === category3) // true

// 객체의 속성을 변경하였을 경우, 참조값이 다른 경우만 변경되지 않음
category.category = "tibana"
console.log(category1) // {"category":"espresso"} 
console.log(category2) // {"category":"tibana"}
console.log(category3) // {"category":"tibana"}

Object.assign(target, source)

target이 같은 경우 해당 target을 덮어쓰고, source값이 같은 경우에도 새로 생성된다.

여기까지만 보면 해당 메서드는 깊은 복사(Deep copy)를 하는 것 같다. 하지만 아래의 경우를 살펴보자.

 

const obj = {
  a: 1,
  b: {
    c: 2,
  },
};

const newObj = Object.assign({}, obj);
console.log(newObj); // {"a":1,"b":{"c":2}}
console.log(newObj === obj); // false 참조값이 다르다

// obj의 하위객체는 다른 참조값을 바라보고 있다.
obj.a = 444;
console.log(newObj); // {"a":1,"b":{"c":2}}
console.log(obj); // {"a":444,"b":{"c":2}}

// 그러나 하위 객체의 하위 객체는 같은 참조값을 바라보고 있다.
obj.b.c = 100;

console.log(newObj); // {"a":1,"b":{"c":100}}
console.log(obj); // {"a":444,"b":{"c":100}}

console.log(newObj === obj); // false
console.log(newObj.b.c === obj.b.c); // true

 

객체 자체는 다르나, 객체의 하위 객체 속성들은 같은 참조 값을 참조하고 있다.

 

전개연산자

전개연산자도 마찬가지로 깊은복사 + 얕은 복사 (Deep + Shallow Copy) 이다.

 

const category = {"category":"espresso"} 
const category1 = {...category} // false - 참조값이 다르다

console.log(category === category1) // false

// 객체의 속성을 변경하였을 경우, 참조값이 다른 경우만 변경되지 않음
category.category = "tibana"
console.log(category1) // {"category":"espresso"} 

// 그러나 객체의 속성의 속성, 즉 객체 안의 객체는 같은 참조값을 갖는다.

const obj = {
  a: 1,
  b: {
    c: 2,
  },
};

const newObj = {...obj}

// obj의 하위객체는 다른 참조값을 바라보고 있다.
obj.a = 444;
console.log(newObj); // {"a":1,"b":{"c":2}}
console.log(obj); // {"a":444,"b":{"c":2}}

// 그러나 하위 객체의 하위 객체는 같은 참조값을 바라보고 있다.
obj.b.c = 100;

console.log(newObj); // {"a":1,"b":{"c":100}}
console.log(obj); // {"a":444,"b":{"c":100}}

console.log(newObj === obj); // false
console.log(newObj.b.c === obj.b.c); // true

 


깊은 복사(Deep Copy)

깊은 복사는 객체 안에 객체가 있더라도 참조값이 다른 것을 말한다.

 

1. 재귀 함수를 이용한 깊은 복사


function clone(item) {
  if (!item) {
    return item;
  } // null, undefined values check

  var types = [Number, String, Boolean],
    result;

  // new String('aaa'), or new Number('444') 이런것들은 원시값으로 변환
  types.forEach(function (type) {
    if (item instanceof type) {
      result = type(item);
    }
  });

  if (typeof result == "undefined") {
    if (Object.prototype.toString.call(item) === "[object Array]") {
      result = [];
      item.forEach(function (child, index, array) {
        result[index] = clone(child);
      });
    } else if (typeof item == "object") {
      if (item.nodeType && typeof item.cloneNode == "function") {
        result = item.cloneNode(true);
      } else if (!item.prototype) {
        if (item instanceof Date) {
          result = new Date(item);
        } else {
          result = {};
          for (var i in item) {
            result[i] = clone(item[i]);
          }
        }
      } else {
        if (false && item.constructor) {
          result = new item.constructor();
        } else {
          result = item;
        }
      }
    } else {
      result = item;
    }
  }

  return result;
}

const obj = {
  a: 1,
  b: {
    c: 2,
  },
};

const newObj = clone(obj);

console.log(obj); // {a: 1, b: { c: 2 } }
console.log(newObj); // {a: 1, b: { c: 2 } }
obj.b.c = 100;
console.log(obj); // {a: 1, b: { c: 100 } }
console.log(newObj);// {a: 1, b: { c: 2 } }

 

2.  JSON.stringify()를 이용한 복사

JSON.stringify()은 JSON을 문자열로 변환하는데 이 과정에서 원본 객체와 참조가 모두 끊어진다. 

그리고 JSON.parse()를 이용해 다시 JSON객체로 만들어주면 깊은 복사가 된다.

하지만 이 방법은 다른 방법에 비해 느리다.

또한 객체에 Dates, functions, undefined, Infinity, RegExps, Maps, Sets, Blobs, FileLists, ImageDatas, sparse Arrays, Typed Arrays 또는 기타 복합 유형이 포함되어 있으면 이 방법이 작동하지 않는다. 

const deepCopy = (data) => {
	return JSON.parse(JSON.stringify(data));
}

const obj = {
  a: 1,
  b: {
    c: 2,
  },
};

const newObj = deepCopy(obj);
console.log(obj);		// {a: 1, b: { c : 2 }}
console.log(newObj); // {a: 1, b: { c : 2 }}
console.log(obj === newObj) // false

// 객체 안의 객체도 변하지 않음
obj.b.c = 100;
console.log(obj);		// {a: 1, b: { c : 100 }}
console.log(newObj);	// {a: 1, b: { c : 2 }}

3. 라이브러리 사용(lodash)

var obj2 = _.cloneDeep(obj, true);

속도를 고려한 추천 방법

객체 안의 객체가 없다면 -> Object.assign() 
deepClone이 필요한 경우 -> lodash 라이브러리 사용하거나 직접 함수 구현하기

 


reference

https://velog.io/@th0566/Javascript-%EC%96%95%EC%9D%80-%EB%B3%B5%EC%82%AC-%EA%B9%8A%EC%9D%80-%EB%B3%B5%EC%82%AC

 

[Javascript] 얕은 복사, 깊은 복사

자바스크립트에서 값은 원시값과 참조값으로 나뉜다. 원시값 Number String Boolean Null Undefined 참조값 Object Symbol 원시값은 값을 복사 할 때 복사된 값을 다른 메모리에 할당 하기 때문에 원래의 값과

velog.io