얕은 복사 & 깊은 복사
Data Types
자바스크립트의 데이터는 원시형(Primitive Type)과 참조형(Object Type)이라는 두 가지 종류의 타입으로 나뉩니다. 원시형에는 정수, 문자열, 심볼 등이 해당되고, 참조형에는 배열, 객체 등이 해당됩니다. 데이터 타입을 먼저 얘기하는 이유는 특정 변수의 데이터를 다른 변수에 복사하고자 할 때, 원본 변수가 담고 있는 데이터의 타입에 따라 복사 방식이 다르기 때문입니다.
각 방식을 얕은 복사(Shallow copy)와 깊은 복사(Deep Copy)라 말합니다. 👏
얕은 복사
다음 코드의 결과를 예상해 보세요.
let num1 = 123;
let num2 = num1;
console.log(num1 === num2);
num2 = 456;
console.log(num1 === num2);
const obj1 = { name: 'tom', age: 5 };
const obj2 = obj1;
console.log(obj1 === obj2);
obj2.name = 'jerry';
console.log(obj1 === obj2);
결과로 아마 [true, false, true, false]를 예상하셨을 텐데, 정답은 [true, false, true, true]입니다. 결과가 이렇게 나오는 이유는 원시형 데이터와 참조형 데이터가 메모리에 저장되는 방식이 서로 다르기 때문입니다. 위 예제에서 사용된 4개의 변수가 담긴 메모리 구조를 그림으로 간단하게 그려보면 다음과 같습니다.

그림에서 확인할 수 있는 것처럼 원시형 데이터는 스택 메모리만을 이용하고, 메모리에 값 자체를 저장합니다. 그래서 원본(num1)을 새로운 변수(num2)에 할당하면 값을 복사하기에 완전히 새로운 값이 만들어 집니다. 다만 비교 연산자는 123이라는 값 자체만 두고 비교하기 때문에, true라는 결과를 얻게 되는 것이지요.
반면 참조형 데이터는 스택 외에도 힙이라 하는 메모리를 사용하며, 이를 스택에서 참조하는 방식으로 동작합니다. 그렇기 때문에 스택에는 힙을 가리키는 주소를 담게 되고, 원본(obj1)을 새로운 변수(obj2)에 복사하게 되면 스택에 담긴 메모리 주소를 복사하게 됩니다. 그래서 두 변수는 동일한 하나의 객체를 가리키게 되고, obj2를 수정했음에도 obj1을 수정한 것과 같게 됩니다. 참조형 데이터의 특성으로 인해 메모리가 복사되는 것을 얕은 복사라 부릅니다.
원시형 데이터는 기본적으로 깊은 복사가, 참조형 데이터는 얕은 복사가 발생합니다.
깊은 복사
원시형 데이터와 달리 참조형 데이터는 얕은 복사가 발생하기 때문에 별도의 처리가 필요합니다. 깊은 복사를 위한 다양한 방법이 있지만, 본문에서는 대표적인 두 가지 방법에 대해 소개합니다.
반복문을 이용한 방법
const arr1 = [1, 2, 3, 4, 5];
const arr2 = [];
for (let i = 0; i < arr1.length; i++) {
arr2.push(arr1[i]);
}
console.log(arr1 === arr2); // false;
const obj1 = { name: 'Tom', age: 5 };
const obj2 = {};
Object.keys(obj1).forEach((key) => {
obj2[key] = obj1[key];
});
console.log(obj1 === obj2); // false;
반복문을 이용한 방법은 새로운 변수에 빈 배열([])과 빈 객체({})를 할당하면서 시작합니다. 할당 이후에는 반복문을 통해 복사하고자 하는 데이터의 값을 하나씩 복사하면 됩니다. 그렇게 해서 같은 값을 가졌지만, 서로 다른 변수가 됩니다.
전개 연산자를 이용한 방법
const arr1 = [1, 2, 3, 4, 5];
const arr2 = [...arr1];
console.log(arr1 === arr2); // false;
const obj1 = { name: 'Tom', age: 5 };
const obj2 = { ...obj1 };
console.log(obj1 === obj2); // false;
전개 연산자(Spread Operator)는 반복 가능한 요소(문자열, 배열, 객체)를 전개하여 배열 또는 객체로 만들어 줍니다. 내부의 값들을 전개하여 새로운 데이터를 만들기에 깊은 복사가 되며, 반복문을 이용한 방법보다 짧은 코드로 구현 가능합니다.
재귀 함수로 구현한 깊은 복사
위에서 소개한 두 가지 방법은 한 가지 아쉬운 점이 있습니다. 바로 최상위 블록(Level 1)에 대해서만 깊은 복사가 되고, 내부의 참조형 데이터들은 여전히 얕은 복사 된다는 점입니다. 따라서 이는 재귀 함수를 통해 구현해야 합니다.
const deepCopy = (param) => {
if (typeof param !== 'object' || param === null) {
return param;
}
if (param instanceof Array) {
return param.reduce((newArr, val) => {
newArr.push(deepCopy(val));
return newArr;
}, []);
} else if (param instanceof Object) {
return Object.keys(param).reduce((newObj, key) => {
newObj[key] = deepCopy(param[key]);
return newObj;
}, {});
}
};
const user = {
champ: '이즈리얼',
level: 1,
stat: {
ad: 50,
ap: 10,
},
items: [
{ name: '롱소드', stack: 1 },
{ name: '체력 물약', stack: 3 },
],
};
console.log(user);
console.log(deepCopy(user));
중첩에 대해 깊은 복사를 하기 위해 이렇게 만들어도 좋지만, lodash 같은 라이브러리의 도움을 받아도 좋습니다. 하지만 깊은 복사만을 위해 라이브러리를 불러오는 것은 낭비라 생각되어, 그런 경우를 대비해 코드를 직접 구현할 수 있는 것도 중요하다 생각합니다. 😆
References
'🌈 기술스택 > JavaScript' 카테고리의 다른 글
자기 자신을 가리키는 값 this (0) | 2021.10.03 |
---|---|
자바스크립트 코드의 유효 범위 Scope (0) | 2021.10.03 |
유사 배열 객체 만들기 (0) | 2021.10.02 |
일급 객체 (First Class Object) (0) | 2021.10.02 |
호이스팅 (Hoisting) (0) | 2021.10.01 |
댓글
이 글 공유하기
다른 글
-
자기 자신을 가리키는 값 this
자기 자신을 가리키는 값 this
2021.10.03 -
자바스크립트 코드의 유효 범위 Scope
자바스크립트 코드의 유효 범위 Scope
2021.10.03 -
유사 배열 객체 만들기
유사 배열 객체 만들기
2021.10.02 -
일급 객체 (First Class Object)
일급 객체 (First Class Object)
2021.10.02
댓글을 사용할 수 없습니다.