main-logo

object에 불변성 부여하기

preventExtensions, freeze, seal 알아보기

profile
sunny
2023년 03월 25일 · 0 분 소요

들어가며

최근 프로젝트가 많은 인원이 투입되면서, 다른 사람들의 코드를 보게 되는 경험하게 되었는데 object.freeze()에 관한 낯선 코드를 발견하게 되었는데요. 단어가 주는 느낌이 object, ‘객체를 얼린다 == 객체를 현재 상태로 유지한다는 것을 뜻하겠구나’ 라고 어렴풋이 유추하면서 이것의 의미가 무엇인지 왜 사용이 되었는지 알아보고자 관련 내용을 찾아가게 되었습니다.

Object의 성질

자바스크립트는 객체를 참조 형태로 전달하고 사용하면서 재 할당을 허용해줍니다.
즉, 객체({})에는 속성을 추가하거나 값을 갱신(수정)하거나 삭제할 수 있습니다. 그래서 우리가 하나의 객체를 참조 받는 곳에서 데이터를 변경하면 원본 객체의 데이터도 함께 변경이 되고 있습니다. (최근, 우리 그룹 책 “Just Javascript” 1장 강의를 확인하신 분들은 어떤 내용인지 바로 아시겠죠?)

function duplicateSpreadsheet(original) {
  if (original.hasPendingChanges) {
    throw new Error('You need to save the file before you can duplicate it');
  }
  let copy = {
    created: Date.now(),
    author: original.author,
    cells: original.cells,
    metadata: original.metadata,
  };
  copy.metadata.title = 'Copy of' + original.metadata.title;
  return copy;
}

위 예제는 스프레드시트를 복사하는 함수로 기존 스프레드시트를 복사하면서 해당 타이틀 명에 “Copy”를 붙이려다가 원본 파일도 함께 수정하고 있습니다.

 

객체를 갖고있는 변수는 객체의 메모리 주소를 참조하는 것으로 객체 내부의 property(속성) 들의 주소를 참조하지 않기 때문에 데이터의 변화를 감지하지 못하는 건데요. 그래서 우리가 객체를 const 속성으로 선언하더라도 내부의 변화를 인지하지 못하기 때문에 재 할당을 할 수 있게 됩니다.

 

방어적 코드 작성하기 STEP 01. Object 객체 불변성 부여하기

기본 가변 객체를 불변 객체로 변경하는 방법은 3가지가 있습니다.

  • preventExtensions() : 객체 확장 금지
  • seal() : 객체 밀봉
  • freeze() : 객체 동결

1. preventExtensions

새로운 속성이 추가되는 것만을 방지합니다.
객체 안에 object.name = ‘sunny’ 와 같이 동적으로 추가하려고 하면 TypeError를 출력해줍니다. 하지만, 객체의 property의 값을 재 할당하거나 삭제하거나 프로토타입 재정의도 가능합니다.

const obj = {
  name: 'sunny',
  age: '32',
  get introduce() {
    return `My name is ${this.name}, and ${this.age} years old`.
  },
  set introduce(newValue) {
    [this.name, this.age] = newValue.split(',');
  }
}

 

2. seal

preventExtensions처럼 새로운 속성이 추가할 수 없고, 기존 속성을 삭제할 수 없고 attribute 재정의를 할 수 없습니다. 다만, 기존 속성의 값을 재 할당 할 수 있습니다.

 

3. freeze

seal과 같이 새로운 속성 추가, 삭제, attribute 재정의도 안 되고, 기존 속성도 재 할당 할 수 있어 javascript가 제공하는 가장 높은 무결성을 제공합니다. 다만, Object.freeze는 얕은 불변한 메소드로 해당 객체 내부 객체까지 불변하게 만들려면 재귀적으로 선언해주어야 합니다.

이 세 가지 메소드의 속성을 표로 비교해보면 다음과 같습니다.

preventExtensions vs seal vs freeze
메서드 property 추가 property 삭제 property 읽기 property 쓰기 prototype 재 할당
preventExtensions X O O O O
seal X X O O X
preventExtensions X X O X X

 

방어적 코드 작성하기 STEP 02. 불변 객체를 방어적으로 복사하기

  • Object.assign()
  • Spread Operator 사용하기

Object.assign(target, source) 는 열거 가능한 source 객체. 즉, 여러 개의 source 객체를 target 객체로 복사하는 메소드로 수정된 객체를 반환해줍니다. 얕은 복사로서 객체 내부의 객체는 참조 값이 그대로 유지되므로 참주 무결정을 유지하고자 한다면 깊은 복사(deep copy)하는 방법으로 처리를 해줘야 합니다.

Spread Operator는 우리가 흔히 쉽게 사용하는 것으로 복사하고 하는 객체의 앞에 ”…” 표기하여 새로운 객체로 반환하여 사용하는 방법입니다. 이것 또한 얕은 복사이므로 내부 객체의 복사에는 유의해줘야 합니다.



객체를 불변하게 하면서 가지는 이점은?

사용하고자 하는 객체가 여러 군데서 참조해서 사용할 경우, 참조하는 객체를 화면별로 다르게 추가, 수정, 삭제를 하게 되면 값의 변화를 추적하기 어려울 수 있습니다. 또한 객체 내부가 변경될 때 메모리 주소는 변경되지 않으므로 객체 내부의 변화를 감지하는 게 필요한데, 객체를 불변하게 만듦으로써 예측하지 못한 상태의 변경을 방어 할 수 있습니다.

마치며

현재 작업하고 있던 코드에서 freeze 메소드를 사용했던 이유는 해당 컴포넌트에서 크고 복잡한 상태를 갖고 있는 객체였는데, 아마도 다른 화면 내 데이터 변경의 위험성을 방어하기 위한 코드가 아닐까 추측해봅니다.

혹시 잘못된 내용이 있거나 수정이 필요한 내용이 있으시면 댓글로 알려주세요!
감사합니다.

참고문서