얕은 복사(shallow copy) vs 깊은 복사(deep copy)

정재성·2022년 8월 3일
0
post-thumbnail

자바스크립트에서 값은 원시값과 참조값으로 나뉜다.

원시값

  • Number
  • String
  • Boolean
  • Null
  • Undefined

참조값

  • Object

원시값은 값을 복사 할 때 복사된 값을 다른 메모리에 할당 하기 때문에 원래의 값과 복사된 값이 서로에게 영향을 미치지 않는다

const a = 1;
let b = a;

b = 2

console.log(a); //1
console.log(b); //2

///기존 값에 영향을 끼치지 않는다.

하지만 참조값은 변수가 객체의 주소를 가리키는 값이기 때문에 복사된 값(주소)이 같은 값을 가리킨다

const a = {number: 1};
let b = a;

b.number = 2

console.log(a); // {number: 2}
console.log(b); // {number: 2}

///기존 값에 영향을 끼친다.(얕은 복사)

원시 값을 변수에 할당하면 변수(확보된 메모리 공간)에는 실제 값이 저장된다.

반면에, 객체를 변수에 할당하면 변수(확보된 메모리 공간)에는 참조 값(메모리 주소)이 저장된다.

이런한 객체의 특징 때문에 객체를 복사하는 방법은 크게 두가지로 나뉜다

얕은 복사 Shllow Copy

 참조값을 복사할 때는 변수가 객체의 참조를 가리키고 있기 때문에 복사된 변수 또한 객체가 저장된 메모리 공간의 참조를 가리키고 있다. 그래서 복사를 하고 객체를 수정하면 두 변수는 똑같은 참조를 가리키고 있기 때문에 기존 객체를 저장한 변수에 영향을 끼칩니다. 이처럼 객체의 참조값(주소값)을 복사하는 것을 얕은 복사라고 한다

const a = {  one: 1,  two: 2,};
let b = a; 
b.one = 3;
console.log(a); // { one: 3, two: 2 } 출력
console.log(b); // { one: 3, two: 2 } 출력

// 기존 값에 영향을 끼친다.

객체를 복사할 때 = 키워드를 사용해서 복사하면 얕은 복사가 돼서 기존 변수 또한 수정돼서 당황하게 될 때가 있을것.그렇다면 자바스크립트에서 얕은 복사 혹은 깊은 복사를 하는 방법은 어떤 것이 있을까?

얕은 복사(shllow Copy) 방법

 "얕은 복사란 객체를 복사할 때 기존 값과 복사된 값이 같은 참조를 가리키고 있는 것을 말합니다. 객체 안에 객체가 있을 경우 한 개의 객체라도 기존 변수의 객체를 참조하고 있다면 이를 얕은 복사라고 합니다."

1) Array.prototype.slice()

얕은 복사 방법의 대표적인 예라고 할 수 잇습니다. start부터 end 인덱스까지 기존 배열에서 추출하여 새로운 배열을 리턴하는 메소드 입니다. 만약 start와 end를 설정하지 않는다면, 기존 배열을 전체 얕은 복사합니다. 

const original = ['a',2,true,4,"hi"];
const copy = original.slice(); 
console.log(JSON.stringify(original) === JSON.stringify(copy)); // true 
copy.push(10); 
console.log(JSON.stringify(original) === JSON.stringify(copy)); // false 
console.log(original); // [ 'a', 2, true, 4, 'hi' ]
console.log(copy); // [ 'a', 2, true, 4, 'hi', 10 ]
const original = [  [1, 1, 1, 1],  [0, 0, 0, 0],  [2, 2, 2, 2],  [3, 3, 3, 3],]; const copy = original.slice(); console.log(JSON.stringify(original) === JSON.stringify(copy)); // true
// 복사된 배열에만 변경과 추가.
copy[0][0] = 99;
copy[2].push(98);

console.log(JSON.stringify(original) === JSON.stringify(copy)); // true 

console.log(original);// [ [ 99, 1, 1, 1 ], [ 0, 0, 0, 0 ], [ 2, 2, 2, 2, 98 ], [ 3, 3, 3, 3 ] ]출력

console.log(copy);// [ [ 99, 1, 1, 1 ], [ 0, 0, 0, 0 ], [ 2, 2, 2, 2, 98 ], [ 3, 3, 3, 3 ] ]출력

만약 1차원 배열이 아닌 중첩 구조를 갖는 2차원 배열이면 얕은 복사를 수행하게 됩니다.  

const original = [  {    a: 1,    b: 2,  },  true,];
const copy = original.slice(); 
console.log(JSON.stringify(original) === JSON.stringify(copy)); // true 
// 복사된 배열에만 변경.
copy[0].a = 99;copy[1] = false; 
console.log(JSON.stringify(original) === JSON.stringify(copy)); // false 
console.log(original);// [ { a: 99, b: 2 }, true ]
console.log(copy);// [ { a: 99, b: 2 }, false ]

 또 다른 예시 입니다. 배열 안에 객체를 수정하고자 할 경우 얕은 복사를 수행하는 것을 볼 수 있습니다. 하지만 원시값은 기본적으로 깊은 복사라 기존 변수에 있는 값과는 다른 값을 도출하는 것을 볼 수 있습니다. 

2) Object.assign()

Object.assign(생성할 객체, 복사할 객체)

메소드의 첫 번째 인자로 빈 객체를 넣어주고 두 번째 인자로 복사할 객체를 넣어주면 됩니다. 

const object = {  a: "a",  number: {    one: 1,    two: 2,  },}; 
const copy = Object.assign({}, object); 
copy.number.one = 3;
console.log(object === copy); // false
console.log(object.number.one  === copy.number.one); // true

복사된 객체 copy 자체는 기존 object와 다른 객체지만 그 안에 들어가 있는 값은 기존 object안의 값과 같은 참조 값을 가리키고 있습니다. 

3) Spread 연산자 (전개 연산자)

const object = {  a: "a",  number: {    one: 1,    two: 2,  },}; 
const copy = {...object} 
copy.number.one = 3;
console.log(object === copy); // false
console.log(object.number.one  === copy.number.one); // true

깊은 복사(Deep Copy) 방법

깊은 복사된 객체는 객체 안에 객체가 있을 경우에도 원본과의 참조가 완전히 끊어진 객체를 말합니다.

  복사를 하는 목적은 기존 객체의 값만 복사본으로 가져와 별도로 활용하기 위함이 대부분이라고 생각합니다. 기존 객체까지 건드린다면 이것은 복사를 하는 목적에 벗어난다고 생각합니다. 

JSON.parse && JSON.stringify

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

객체를 json 문자열로 변환 후, JSON.parse()를 이용해 다시 원래 객체(자바스크립트 객체)로 만들어줍니다.

이 방법이 가장 간단하고 쉽지만 다른 방법에 비해 느리다는 것과 객체가 function일 경우,  undefined로 처리한다는 것이 단점입니다.

const object = {  a: "a",  number: {    one: 1,    two: 2,  },  arr: [1, 2, [3, 4]],};
const copy = JSON.parse(JSON.stringify(object)); copy.number.one = 3;copy.arr[2].push(5); console.log(object === copy); // false
console.log(object.number.one === copy.number.one); // false
console.log(object.arr === copy.arr); // false 
console.log(object); // { a: 'a', number: { one: 1, two: 2 }, arr: [ 1, 2, [ 3, 4 ] ] }
console.log(copy); // { a: 'a', number: { one: 3, two: 2 }, arr: [ 1, 2, [ 3, 4, 5 ] ] }

Lodash 라이브러리 사용

라이브러리를 사용하면 더 쉽고 안전하게 깊은 복사를 할 수 있습니다. 설치를 해야 한다는 점과 일반적인 개발에는 효율적이겠지만, 코딩 테스트에는 사용할 수 없다는 것이 단점입니다.

const deepCopy = require("lodash.clonedeep") 
const object = {  a: "a",  number: {    one: 1,    two: 2,  },  arr: [1, 2, [3, 4]],};
const copy = deepCopy(object); 
copy.number.one = 3;
copy.arr[2].push(5); 
console.log(object === copy); // false
console.log(object.number.one === copy.number.one); // false
console.log(object.arr === copy.arr); // false 
profile
기술블로그 / 일상블로그

0개의 댓글