깊은 복사, 얕은 복사

이현섭·2023년 4월 18일
0

객체와 원시타입의 근본적인 차이
=> 객체는 '참조에 의해 (by reference)' 저장되고 복사.
=> 원시값 (문자열, 숫자, 불리언) 은 '값 그대로' 저장, 할당, 복사.

let a = 'Hello';
let b = a;

위의 예시를 보면 2개의 독립된 변수에 문자열 Hello 가 저장됨.

상자 a와 b에 각각 Hello가 담김.
하지만, 객체의 동작방식은 이와 다름.

변수엔 객체가 그대로 저장되는 것이 아니라, 객체가 저장되어 있는 '메모리 주소'인 객체에 대한 '참조 값'이 저장됨.

let user = {
	name : "현섭"
}

let admin = user; // 참조값을 복사.

상자는 2개 이지만, 각 상자에는 동일 객체에 대한 참조 값이 저장됨.
=> 따라서 객체에 접근하거나 객체를 조작할 때에는 여러 변수를 사용할 수 있음.

let a = {};
let b = a; // 참조에 의한 복사

console.log(a == b); // true;
console.log(a === b); // true;

let c = {};
let d = {}; // c와 d는 독립된 두 객체

console.log( c == d ); // false

그렇다면 객체를 진짜로 복제하고 싶다면 어떻게 해야할까 ?
자바스크립트는 객체 복제 내장 메서드를 지원하지 않기 때문에 조금 어려움.
정말 복제가 필요한 상황이라면 새로운 객체를 만든 다음 기존 객체의 프로퍼티들을 순회해 원시 수준까지 프로퍼티를 복사하면 됨.

let user = {
	name : "이현섭",
	age : 28
};

let clone = {}; // 새로운 빈 객체

// 빈 객체에 user 프로퍼티 전부를 복사해 넣는다.
for (let key in user) {
	clone[key] = user[key];
}

// 이제 clone은 완전한 독립적인 복제본.
clone.name = "섭섭";

console.log(user.name); // "이현섭" 출력

위의 방법 말고 Object.assign 를 사용하는 방법도 있음.

Object.assign(dest, [src1, src2, src3 ... ])
  1. 첫 번째 인수 dest 는 목표로 하는 객체
  2. 이어지는 인수 src1, ... , srcN 는 복사하고자 하는 객체. ... 은 필요에 따라 얼마든지 많은 객체를 인수로 사용할 수 있다는 것을 의미.
  3. 객체 src1, ... , srcN 의 프로퍼티를 dest에 복사. dest를 제외한 인수의 프로퍼티 전부가 첫 번째 인수로 복사됨.
  4. 마지막으로 dest 반환.
let user = {
	name : "이현섭"
};

let obj1 = { canView : true };
let obj2 = { canEdit : true };

Object.assign(user, obj1, obj2);

console.log(user); // user = { name : "이현섭", canView : true, canEdit : true }

목표 객체에 동일한 이름을 가진 프로퍼티가 있는 경우엔 기존 값이 덮어씌워짐.

let user = {
	name : "이현섭"
}

Object.assign(user, { name : "섭섭" });

// now user = { name : "섭섭" }

Object.assign 을 사용하면 반복문 없이도 간단하게 객체 복사 가능.

let user = {
	name : "이현섭",
	age : 28
};

let clone = Object.assign({}, user);

// user에 있는 모든 프로퍼티가 빈 배열에 복사되고 변수에 할당됩니다.

중첩 객체 복사.

현재까지는 모든 프로퍼티가 원시값인 경우만 가정. 만약, 프로퍼티가 다른 객체에 대한 참조 값이라면?

let user = {
	name : "이현섭",
  	info : {
		phone : 1234,
      	money : 10
	}
}

let clone = Object.assign({}, user);

console.log(user.info === clone.info); // true

// user와 clone은 info를 공유.
user.info.money++;		// 한 객체에서 프로퍼티를 변경.
alert(clone.info.money) // 11, 다른 객체에서 변경 사항 확인 가능.

이러한 문제를 해결하려면 user[key] 의 각 값을 검사하면서, 그 값이 객체인 경우 객체의 구조도 복사해주는 반복문을 사용해야함.

=> 이러한 방식을 '깊은 복사 (deep cloning)' 이라고 함.

깊은 복사 시 사용되는 표준 알고리즘인 Structured cloning algorithm 을 사용하면 위의 경우를 비롯한 다양한 상황에서 객체 복사 가능.

자바스크립트의 라이브러리인 lodash 의 메서드인 _.cloneDeep(obj)를 사용하면 이 알고리즘을 직접 구현하지 않고도 깊은 복사 가능.

요약

객체는 참조에 의해 할당되고 복사됨. 변수엔 '객체' 자체가 아닌 메모리상의 주소인 '참조' 가 저장됨. 따라서 객체가 할당된 변수를 복사하거나 함수의 인자로 넘길 때 객체가 아닌 객체의 참조가 복사됨.

그리고 복사된 참조를 이용한 모든 작업은 동일한 객체를 대상으로 이뤄짐.

객체의 '진짜 복사본을' 만들려면 '얕은 복사' 를 가능하게 해주는 Object.assign 이나 '깊은 복사' 를 가능하게 해주는 _.cloneDeep(obj) 를 사용하면 됨.
이 때 얕은 복사본은 중첩 객체를 처리하지 못함.

출처 : https://ko.javascript.info/object-copy

profile
안녕하세요. 프론트엔드 개발자 이현섭입니다.

0개의 댓글