얕은 복사는 객체의 참조값(주소값)을 복사하고, 깊은 복사는 객체의 실제 값을 복사한다.
먼저, 자바스크립트에는 원시 타입(Primitive Type)과 참조 타입(Reference Type) 두 가지 타입의 자료형이 있다.
원시값 (기본 자료형)
- Number
- String
- Boolean
- Null
- Undefined
- Symnol
변수에 원시값을 저장하면 변수의 메모리 공간에 실제 데이터 값이 저장된다.
또한 원시 타입 자료형은 변수 선언, 초기화, 할당 시 값이 저장된 메모리 영역에 직접적으로 접근한다.
즉 변수에 새 값이 할당될 때 변수에 할당된 메모리 블럭에 저장된 값을 바로 변경한다는 뜻이다.
let origin = 100;
let copy = origin;
console.log(copy); // 100
origin = 200;
console.log(copy); // 100
원시 타입은 값 자체를 복사하기 때문에 원본 데이터의 값이 바뀌더라도 기존 데이터 값을 유지한다.
원시 타입을 복사할 때는 항상 깊은 복사가 되므로, 깊은 복사와 얕은 복사를 구분할 필요가 없다.
참조값
- Object
변수에 참조값을 저장하면 독립적인 메모리 공간(Heap)에 값을 저장하고, 변수에 저장된 메모리 공간의 참조를 저장하게 된다. 즉 변수의 값이 저장된 메모리 블록의 주소를 가지고 있다.
let origin = {
name: 'seoyoung'
}
let copy = origin;
console.log(copy.name); // 'seoyoung'
origin.name = 'kimseoyoung';
console.log(copy.name); // 'kimseoyoung'
참조 타입은 주소 값을 참조하기 때문에 원본 데이터의 값이 바뀌면 복사한 데이터의 값도 변경된다.
이렇게 참조 타입의 데이터는 복사 시 데이터의 값이 아닌 '값이 저장된 메모리의 주소'가 저장된다.
따라서 참조 타입의 복사 방법은 얕은 복사와 깊은 복사로 나뉜다.
📍 얕은 복사 (Shllow Copy)
얕은 복사란 객체를 복사할 때 기존 값과 복사된 값이 같은 참조를 가리키고 있는 것을 말한다.
다시 말해 참조 데이터가 저장한 '메모리 주소 값'을 복사한 것을 의미한다.
const arr = [];
const brr = arr;
brr.push(1);
console.log(arr === brr); // true
console.log(arr); // [1]
console.log(brr); // [1]
brr 배열에만 1이라는 값을 push 해줬음에도 arr 배열에도 1이 들어있다.
brr은 얕은 복사를 이용해 만들었고, arr과 brr 두 개의 변수가 하나의 주소값을 공유하고 있기 때문이다.
📍 깊은 복사 (Deep Copy)
깊은 복사란 새로운 메모리 공간을 확보해 완전히 복사하는 것을 의미한다.
원본과의 참조가 완전히 끊어지고 값만 복사하는 것이다.
const arr = [];
const brr = arr.slice();
brr.push(1);
console.log(arr === brr); // false
console.log(arr); // []
console.log(brr); // [1]
얕은 복사와 달리 brr에 1을 push 했음에도 arr은 빈 배열이다. 이는 arr과 brr 배열이 서로 다른 주소값을 가지고 있기 때문이다.
깊은 복사를 하는 방법은 다음과 같다.
◽️Object.assign() - 1차원 객체
문법 : Object.assign(생성할 객체, 복사할 객체)
const obj = { a: 1 };
const copyObj = Object.assign({}, obj);
copyObj.a = 2;
console.log(obj); // { a: 1 }
console.log(obj === copyObj); // false
여기서 주의해야 할 점은 1차원 객체/배열일 경우에만 깊은 복사가 이루어지며 2차원일 경우 깊은 복사가 이루어지지 않는다.
const obj = {
a: 1,
b: { c: 2 },
}
const copyObj = Object.assign({}, obj);
copyObj.b.c = 3;
console.log(obj); // { a: 1, b: { c: 3 } }
console.log(obj === copyObj); // false
console.log(obj.b === copyObj.b); // true
console.log(obj.b.c === copyObj.b.c); // true
같은 Object.assign() 를 이용해 객체를 복사하였지만 복사된 하위 객체는 얕은 복사가 된 것이다.
◽️ Spread 연산자 - 1차원 객체
const obj = {
a: 1,
b: { c: 2 },
};
const copyObj = {...obj};
copyObj.b.c = 3;
console.log(obj); // { a: 1, b: { c: 3 } }
console.log(obj.b.c === copyObj.b.c); // true
Object.assign()와 마찬가지로 2차원 객체/배열일 경우에는 얕은 복사가 이루어진다.
그 다음 깊은 복사 방법들은 2차원 이상의 객체도 깊은 복사가 가능하다.
◽️ JSON.stringify() && JSON.parse()
JSON.stringify()는 객체를 json 문자열로 변환하는데 이 과정에서 원본 객체와의 참조가 끊긴다.
JSON.parse()는 JSON 문자열을 JS 값이나 객체로 변환한다.
즉 JSON.stringify() 통해 참조(references)를 모두 제거하고,
다시 JSON.parse()를 통해 원래 객체 형태로 돌려놓는 방식이다.
이 방식은 간단하지만, 다른 방식에 비해 느리고, function의 경우 undefined로 나온다는 것이 단점이다.
const obj = {
a: 1,
b: { c: 2 },
func: function() {
return this.a;
}
};
const copyObj = JSON.parse(JSON.stringify(obj));
console.log(copyObj.func); // undefined
◽️ 커스텀 재귀 함수
const deepCopy = (obj) => {
if (obj === null || typeof obj !== 'object') return obj;
let copy = {};
for (let key in obj) {
copy[key] = deepCopy(obj[key]);
}
return copy;
};
const obj = {
a: 1,
b: { c: 2 },
func: function () {
return this.a;
}
};
const copyObj = deepCopy(obj);
copyObj.b.c = 3;
console.log(obj); // { a: 1, b: { c: 2 }, func: [Function: func] }
console.log(obj.b.c === copyObj.b.c); // false
직접 함수를 구현하여 다차원 객체를 깊은 복사할 수 있지만 구현이 복잡하다는 것이 단점이다.
◽️ lodash 라이브러리의 cloneDeep()
const obj = {
a: 1,
b: { c: 2 },
func: function () {
return this.a;
}
};
const copyObj = lodash.cloneDeep(obj);
copyObj.b.c = 3;
console.log(obj); // { a: 1, b: { c: 2 }, func: [Function: func] }
console.log(obj.b.c === copyObj.b.c); // false
라이브러리를 이용해 쉽고 안전하게 깊은 복사를 할 수 있다.
'Javascript' 카테고리의 다른 글
[javaScript] 옵셔널 체이닝 Optional chaining (?.) (0) | 2024.04.23 |
---|---|
[JavaScript] 이벤트 버블링, 캡쳐링, 위임 (0) | 2024.04.06 |
[JavaScript] var, let, const 차이점 (0) | 2024.03.27 |