객체: 기본 3.2 참조에 의한 객체 복사

Eddy·2023년 5월 31일
0

코어 자바스크립트

목록 보기
23/30

♠ 객체: 기본

♣ 3.2 참조에 의한 객체 복사

객체와 원시 타입의 근본적인 차이 중 하나는 객체는 ‘참조에 의해(by reference)’ 저장되고 복사된다는 것입니다.

원시값(문자열, 숫자, 불린 값)은 ‘값 그대로’ 저장·할당되고 복사되는 반면에 말이죠.

예시:

let message = "Hello!";
let phrase = message;

예시를 실행하면 두 개의 독립된 변수에 각각 문자열 "Hello!"가 저장됩니다.

그런데 객체의 동작 방식은 이와 다릅니다.

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

그림을 통해 변수 user에 객체를 할당할 때 무슨 일이 일어나는지 알아봅시다.

let user = {
  name: "John"
};

객체는 메모리 내 어딘가에 저장되고, 변수 user엔 객체를 '참조’할 수 있는 값이 저장됩니다.

따라서 객체가 할당된 변수를 복사할 땐 객체의 참조 값이 복사되고 객체는 복사되지 않습니다.

예시:

let user = { name: "John" };

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

변수는 두 개이지만 각 변수엔 동일 객체에 대한 참조 값이 저장되죠.

따라서 객체에 접근하거나 객체를 조작할 땐 여러 변수를 사용할 수 있습니다.

let user = { name: 'John' };

let admin = user;

*admin.name = 'Pete'; // 'admin' 참조 값에 의해 변경됨*

alert(*user.name*); // 'Pete'가 출력됨. 'user' 참조 값을 이용해 변경사항을 확인함

객체를 서랍장에 비유하면 변수는 서랍장을 열 수 있는 열쇠라고 할 수 있습니다. 서랍장은 하나, 서랍장을 열 수 있는 열쇠는 두 개인데, 그중 하나(admin)를 사용해 서랍장을 열어 정돈한 후, 또 다른 열쇠로 서랍장을 열면 정돈된 내용을 볼 수 있습니다.

객체 복사, 병합과 Object.assign

Object.assign

문법과 동작 방식은 다음과 같습니다.

Object.assign(dest, [src1, src2, src3...])

  • 첫 번째 인수 dest는 목표로 하는 객체입니다.
  • 이어지는 인수 src1, ..., srcN는 복사하고자 하는 객체입니다. ...은 필요에 따라 얼마든지 많은 객체를 인수로 사용할 수 있다는 것을 나타냅니다.
  • 객체 src1, ..., srcN의 프로퍼티를 dest에 복사합니다. dest를 제외한 인수(객체)의 프로퍼티 전부가 첫 번째 인수(객체)로 복사됩니다.
  • 마지막으로 dest를 반환합니다.

assign 메서드를 사용해 여러 객체를 하나로 병합하는 예시를 살펴봅시다.

let user = { name: "John" };

let permissions1 = { canView: true };
let permissions2 = { canEdit: true };

*// permissions1과 permissions2의 프로퍼티를 user로 복사합니다.
Object.assign(user, permissions1, permissions2);*
// now user = { name: "John", canView: true, canEdit: true }

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

let user = { name: "John" };

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

alert(user.name); // user = { name: "Pete" }

Object.assign을 사용하면 반복문 없이도 간단하게 객체를 복사할 수 있습니다.

let user = {
  name: "John",
  age: 30
};

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

예시를 실행하면 user에 있는 모든 프로퍼티가 빈 배열에 복사되고 변수에 할당됩니다.

중첩 객체 복사

지금까진 user의 모든 프로퍼티가 원시값인 경우만 가정했습니다. 그런데 프로퍼티는 다른 객체에 대한 참조 값일 수도 있습니다. 이 경우는 어떻게 해야 할까요?

아래와 같이 말이죠.

let user = {
  name: "John",
  sizes: {
    height: 182,
    width: 50
  }
};

alert( user.sizes.height ); // 182

clone.sizes = user.sizes로 프로퍼티를 복사하는 것만으론 객체를 복제할 수 없습니다. user.sizes는 객체이기 때문에 참조 값이 복사되기 때문입니다. clone.sizes = user.sizes로 프로퍼티를 복사하면 clone과 user는 같은 sizes를 공유하게 됩니다.

아래와 같이 말이죠.

let user = {
  name: "John",
  sizes: {
    height: 182,
    width: 50
  }
};

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

alert( user.sizes === clone.sizes ); // true, 같은 객체입니다.

// user와 clone는 sizes를 공유합니다.
user.sizes.width++;       // 한 객체에서 프로퍼티를 변경합니다.
alert(clone.sizes.width); // 51, 다른 객체에서 변경 사항을 확인할 수 있습니다.

이 문제를 해결하려면 user[key]의 각 값을 검사하면서, 그 값이 객체인 경우 객체의 구조도 복사해주는 반복문을 사용해야 합니다. 이런 방식을 '깊은 복사(deep cloning)'라고 합니다.

깊은 복사 시 사용되는 표준 알고리즘인 Structured cloning algorithm을 사용하면 위 사례를 비롯한 다양한 상황에서 객체를 복제할 수 있습니다.

자바스크립트 라이브러리 lodash의 메서드인 _.cloneDeep(obj)을 사용하면 이 알고리즘을 직접 구현하지 않고도 깊은 복사를 처리할 수 있으므로 참고하시기 바랍니다.

eddy’s point

전개연산자를 이용한 중첩 객체 복사

    const [person, setPerson] = useState({
    	name: "알린",
    	title: "개발자",
    	mentor: {
    			name: "우디",
    			title: "시니어개발자",
    	},
    });
    
    setPerson((person) => ({
        ...person,
        mentor: { ...person.mentor, name: newName },
      }));

0개의 댓글