공부 및 일상기록

[Javascript] 불변성을 유지하는 방법 본문

개발/Javascript

[Javascript] 불변성을 유지하는 방법

낚시하고싶어요 2023. 1. 16. 17:03

사실 블로그에서 몇번 다룬 내용이지만 다시 한번 다뤄보려고 한다.

 

불변성이란 변하지 않는 성질을 의미한다.

 

우리가 변수 a에 값을 할당하는 것을 이미지로 살펴보자

let a = 'abc'
a = 'bbb'

식별자 a는 메모리에 값을 바로 할당하는 것이 아닌 abc라는 데이터가 존재하는 주소를 값으로 가지고 있다.

 

여기서 a='bbb'로 재할당하면 어떻게 변하게 될까?

a의 값을 변경하면 새로운 값 bbb가 존재하는 주소값이 변경된다.

이처럼 자바스크립트의 원시데이터들은 한번 만든 값을 바꿀 수 없는 불변성을 가지고 있다.

 

하지만 참조형은 불변성이 아닌 가변성을 갖는다.

let obj = {
	a:1,
    b:'abc'
};

위처럼 obj객체를 하나 생성했다. 그럼 메모리는 다음과 같은 구조를 가질 것이다.

먼저 obj식별자에는 b3030이라는 주소값이 할당되어 있고,  b3030의 데이터에는 c1010~c2020의 주소값이 할당되어있다.

c1010과 c2020에는 obj내부 데이터가 각각 할당되어있으며 이 역시 데이터의 주소값을 가지고 있다.

c1010과 c2020에 있는 주소값을 따라가 보면 해당 데이터를 찾을 수 있다.

 

만약 여기서 obj.a = 3 으로 재할당을 하면 어떻게 될까?

그렇다면 아마도 주소 b5050을 만들고 값 3을 할당하고, c1010에서 가지고있는 주소값을 b5050으로 바꾸게 될것이다.

 

그럼 실제 obj의 메모리값은 b3030으로 변한게 없지만 데이터는 변하게 된것이다.

 


그렇다면 우리가 불변 객체를 만들어 주려면 어떻게 해야할까?

객체에 재할당이나 복사를 해야할 때 깊은 복사를 통해 값을 복사하면 된다.

깊은 복사란 객체안에 객체가 있는 경우에도 원본과의 참조가 완전히 끊어진 객체를 생성하는 복사를 의미한다.

 

1. 재귀함수를 구현하여 복사한다.

let 학생 = {
  이름: "휘린",
  반: 3,
  성별: "남자",
  친구: {
    이름: "피카츄",
  },
};

let copyObjectDeep = function (target) {
  let result = {};
  if (typeof target === "object" && target !== null) {
    for (let prop in target) {
      result[prop] = copyObjectDeep(target[prop]);
    }
  } else {
    result = target;
  }
  return result;
};

let 학생2 = copyObjectDeep(학생);

학생2.이름 = "민수";
학생2.친구.이름 = "라이츄"; 

console.log(학생 === 학생2);                  //false
console.log(학생.이름, 학생2.이름);           //휘린    민수
console.log(학생.친구.이름, 학생2.친구.이름); //피카츄  라이츄

위 함수는 재귀복사를 통한 깊은 복사를 하는 간단한 예시이다. 설명해보자면, copyObjectDeep 함수는 복사하려는 대상이 객체이면 해당 객체를 돌면서 각 요소를 다시한번 copyObjectDeep에 넣어 내부에 모든 객체를 검사하여 복사 하도록 한다. result에 객체 리터럴로 새로운 객체를 만들고 해당 객체에 반환값을 넣으므로 완전히 새로운 객체를 만들게 되는 것이다.

 

2. Spread연산자 ( ... )를 통한 복사

const obj1 = { a:1, b:2 };
const obj2 = { ...obj };
obj2.a = 100;
console.log( obj1 === obj2 ) // false
console.log( obj1.a ) // 1

위처럼 스프레드 연산자를 통해 새로운 객체 안에 obj1의 속성을 복사하여 obj2를 만들면 다른 주소를 갖게된다.

하지만 이는 딱 1depth까지만 깊은 복사가 이뤄지고 객체안의 객체가 존재했다면 그 객체안의 객체는 얕은 복사가 이뤄지게 된다.

 

3. Object.assign() 메소드를 통한 복사

const obj1 = { a:1, b:2 };
const obj2 = Object.assign({}, obj1);
obj2.a = 100;
console.log( obj1 === obj2 ) // false
console.log( obj1.a ) // 1

Object.assign() 메소드를 통해 첫 번째 인자로 빈 객체를, 두 번째 인자로 obj1을 넣어서 obj2에 할당하면 새로운 객체가 만들어진다.

하지만 이 역시 딱 1depth까지만 이뤄지게 된다.