Front-End/Javascript

JS - 얕은 복사(Shallow Copy )와 깊은 복사(Deep Copy)

태나미 2021. 9. 17. 19:08
바로 전에, primitive type과 reference type의 차이점을 메모리에 어떻게 할당되는지 알아보았습니다. 
오늘은 referenct type에서 객체를 복사할 때 얕은 복사와 깊은 복사의 차이점을 공부해보겠습니다.

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

const age = 29;
let nextYearAge = age;

nextYearAge += 1;

console.log(age); // 29
console.log(nextYearAge); // 30

reference type의 값을 복사할 때는, 선언된 변수의 주소가 객체의 주소를 가리키고 있기 때문에, 아래와 같습니다

const me = {
    region: 'seoul',
    age: 29
};

let jackson = me;
jackson.age = 30;

console.log(me); // {region: 'seoul', age: 30}
console.log(jackson); // {region: 'seoul', age: 30}

jackson object의 내부 프로퍼티인 age의 값을 30으로 바꾸었는데, 원치 않게 me object의 age 값도 바뀌어지게 되었습니다. 

 

이러한 객체의 특징 때문에, 원본 객체를 복사하면서, 원본 객체의 프로퍼티의 값을 바꾸지 않게 하기 위해선 원본 객체를 복사할 때 그 주솟값만 복사하는 방법을 고민해봐야 합니다. 

얕은 복사(Shallow Copy)와 깊은 복사(Deep Copy)를 사용해야 합니다.

 

얕은 복사 ( Shallow Copy )

얕은 복사란 객체를 복사할 때 원래 값과 복사된 값이 같은 참조를 가리키고 있는 것을 말합니다. 얕은 복사를 하기 위한 방법으로 다음 2가지가 있습니다.

1. Object.assign()

Object.assign 메소드는 원본 객체로부터 복사하고 새로운 객체를 반환합니다. 첫 번째 요소로 들어온 객체에 다음 인자로 들어온 객체를 복사해줍니다.

const me = {
    name: 'taenam',
    region: 'korea',
    lastYear: {
        age: 28,
    },
};

const jackson = Object.assign({}, me);
jackson.region = "us";
jackson.lastYear.age = 25;

console.log(me.region); // korea
console.log(jackson.region); // us

console.log(me.lastYear.age); // 25
console.log(jackson.lastYear.age); // 25

2. Spread Operator

const me = {
    name: 'taenam',
    region: 'korea',
    lastYear: {
        age: 28,
    },
};

const jackson = {...me};
jackson.region = "us";
jackson.lastYear.age = 25;

console.log(me.region); // korea
console.log(jackson.region); // us

console.log(me.lastYear.age); // 25
console.log(jackson.lastYear.age); // 25

얕은 복사를 이용하면 객체의 바로 아래에 있는 프로퍼티의 값만 바꿀 수 있습니다. 반면, 한 단계 더 들어간 내부 프로퍼티들은 기존 객체를 그대로 참조하고 있는 문제점이 있습니다. 

 

깊은 복사 ( Deep Copy )

1. 재귀함수 이용

const me = {
    name: 'taenam',
    region: 'korea',
    lastYear: {
        age: 28,
    },
};

const copyObj = (obj) => {
    const result = {};

    for (let key in obj) {
    	if (typeof obj[key] === 'object' && obj[key] !== null) {
      		result[key] = copyObj(obj[key]);
    	} else {
        	result[key] = obj[key];
        }
    }

    return result;
};

let jackson = copyObj(me);
jackson.region = "us";
jackson.lastYear.age = 25;

console.log(me.region); // korea
console.log(jackson.region); // us

console.log(me.lastYear.age); // 28
console.log(jackson.lastYear.age); // 25

Object의 Depth가 길어질수록 Time Complexity(시간 복잡도)도 늘어나게 되는 단점이 있습니다.

2. JSON 객체 method 이용

const me = {
    name: 'taenam',
    region: 'korea',
    lastYear: {
        age: 28,
    },
    greeting: () => {
        console.log("hello ~")
    },
};

const copyObj = (obj) => JSON.parse(JSON.stringify(obj));

let jackson = copyObj(me);
jackson.region = "us";
jackson.lastYear.age = 25;

console.log(me.region); // korea
console.log(jackson.region); // us

console.log(me.lastYear.age); // 28
console.log(jackson.lastYear.age); // 25

console.log(me.greeting()); // hello ~
console.log(jackson.greeting()); // Uncaught TypeError: jackson.greeting is not a function

먼저, JSON.stringify로 자바스크립트 객체(object)를 JSON문자열로 변환시킨 뒤 JSON.parse는 JSON문자열을 자바스크립트 객체로 다시 변환시키면서 객체에 대한 참조가 없어진 것입니다.

이 방법의 단점으로는 다른 방법에 비해서 성능적으로 느리다는 점과, JSON.stringify 메소드가 function을 undefined로 처리한다는 점이 있기 때문에, 복사된 객체에서 함수를 호출하게 되면 에러가 발생합니다.

3. Lodashh의 deepclone 함수 사용

const clonedeep = require("lodash.clonedeep")

const me = {
    name: 'taenam',
    region: 'korea',
    lastYear: {
        age: 28,
    },
};

let jackson = clonedeep(me);
jackson.region = "us";
jackson.lastYear.age = 25;

console.log(me.region); // korea
console.log(jackson.region); // us

console.log(me.lastYear.age); // 28
console.log(jackson.lastYear.age); // 25

Lodash는 많은 메서드들을 제공하는데, 그중 하나인 clonedeep method를 사용하면 깊은 복사가 가능합니다.

 

이번에는 얕은 복사와 깊은 복사를 알아보게 되었는데, 실제 프로젝트에서 기존 객체를 복사하는 일이 많은데, 모듈을 하나 만들어 재사용성을 고려해볼 수 있는 것을 생각해보았습니다!

 

 

 

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

https://junwoo45.github.io/2019-09-23-deep_clone/