[JavaScript] 얕은 복사와 깊은 복사
얕은 복사와 깊은 복사
자바스크립트 자료형에는 원시 타입과 참조 타입이 존재한다. 원시타입은 null, undefined, boolean, number, bigInt, string, symbol 7가지이며, 참조 타입에는 object 자료형 한가지가 존재한다.(array, function, date 등도 object 참조 타입에 속한다.) 원시타입은 변수에 직접 값이 저장되는 반면, 참조 타입은 메모리의 주소 값이 저장된다.
원시타입 : 값 자체가 복사됨
let a = 1;
let b = a;
console.log(a, b, a === b); // 1, 1, true
a = 10;
console.log(a, b, a === b); // 10, 1, false
b = 2;
console.log(a, b, a === b); // 10, 2, false
참조타입 : 주소 값이 복사됨
let obj1 = { name : '홍길동'};
let obj2 = obj1;
obj1.name = '김철수';
console.log(obj1, obj2, obj1 === obj2); // { name : '김철수'}, { name : '김철수'}, true
참조 타입은 메모리 주소 값이 저장되기 때문에 원본 데이터가 변경되면 해당 참조 타입이 할당된 다른 변수 값에도 데이터 변경이 발생한다. 이를 방지 위해 '복사'에 대한 개념이 필요하다.
얕은 복사
객체의 1차원 속성만 새로운 메모리에 복사하는 방식이다. 객체 내부에 중첩된 객체는 여전히 원본과 같은 참조를 가진다.
얕은 복사 방법
1. 스프레드 연산자( Spread Operator)
const origin = { name: '홍길동', info: { age: 30 };
const copied = {...origin};
copied.name = '김철수';
copied.info.age = 20;
console.log(origin.name); // '홍길동' (변경되지 않음)
console.log(origin.info.age); // 20 (변경됨)
2. Object.assign()
const origin = { name: '홍길동', info: { age: 30 };
const copied = Object.assign({}, origin);
copied.name = '김철수';
copied.info.age = 20;
console.log(origin.name); // '홍길동' (변경되지 않음)
console.log(origin.info.age); // 20 (변경됨)
3. 배열의 얕은 복사
// 1. slice 메서드
const origin = [ 1,2,3, [4,5] ];
const copied = origin.slice()
// 2. Array.from
const copied2 = Array.from(origin);
// 3. 스프레드 연산자
const copied3 = [...origin];
copied[0] = 10;
copied[2][0] = 40;
console.log(origin[0]); // 1 (변경되지 않음
console.log([2][0])); // 40 (변경됨)
깊은 복사중첩된 하위 객체를 포함하여 모든 객체 레벨을 새로운 메모리에 완전히 복사하는 방식이다. 깊은 객체를 통해 생성된 객체는 원본 객체와 완전히 독립적인 관계를 가집니다.
깊은 복사 방법
- JSON 활용하기
const origin = { name: '홍길동', info: { age: 30 };
const copied = JSON.parse(JSON.stringify(original));
copied.name = '김철수';
copied.info.age = 20;
console.log(origin.name); // '홍길동' (변경되지 않음)
console.log(origin.info.age); // 30 (변경되지 않음)
하지만 이 방법은 한계점이 존재한다.
- function, undefined, symbol 등의 값은 복사되지 않는다.
- 순환 참조(circular references)가 있는 객체는 오류가 발생한다.
- Date, RegExp 등의 객체는 문자열로 변환된다.
따라서 아래 방법들을 사용하는 것을 추천한다.
- 재귀 함수 활용하기
function deepCopy(obj) {
let result;
if (obj === null || typeof obj !== 'object') {
return obj;
}
result = Array.isArray(obj) ? [] : {};
Object.keys(obj).forEach(key => {
result[key] = deepCopy(obj[key]);
});
return result;
}
const arr = [1, 2, 3, [4, 5]];
const copiedArr = deepCopy(arr);
copied[1] = 10;
copied[3][0] = 100;
console.log(copiedArr); // [ 1, 10, 3, [ 100, 5 ] ]
console.log(arr); // [ 1, 2, 3, [ 4, 5 ] ]
const obj = { name: '홍길동', info: { age: 30 }};
const copiedObj = deepCopy(obj);
copiedObj.name = '김철수';
copiedObj.infor.age: 10;
console.log(copiedObj) // { name: '김철수', info: { age: 10 } }
console.log(obj) // { name: '홍길동', info: { age: 30 } }
obj.name = '박명수',
obj.infor.age = 20;
console.log(copiedObj) // { name: '김철수', info: { age: 10 } }
console.log(obj) // { name: '박명수', info: { age: 20 } }
3. 라이브러리 활용(lodash의 cloneDeep)
import _ from 'lodash';
const original = { name: '홍길동', info: { age: 30 } };
const copied = _.cloneDeep(original);
참조
https://pozafly.github.io/javascript/shallo-copy-and-deep-copy/
얕은 복사와 깊은 복사 / React에서의 활용
JSON.stringift()ㅡMDN
TypeError: cyclic object valueㅡMDN