[모던 자바스크립트 Deep Dive] 16장

Gyuwon Lee·2022년 8월 2일
0
post-thumbnail

42 서울의 모던 자바스크립트 Deep Dive 스터디 학습의 일환으로, 새롭게 알게 된 내용 중심으로 정리되어 있습니다.


CH16. 프로퍼티 어트리뷰트

프로퍼티 어트리뷰트 내지는 프로퍼티 설명자 라고 명명된 이 값들은 프로퍼티의 '상태'를 나타낸다. 자바스크립트 엔진이 관리하는 '내부 상태 값'인 내부 슬롯 의 형태로, 이 개념을 먼저 이해하는 것이 필요하다.


1) 내부 슬롯과 내부 메서드

  • 내부 슬롯: 수도(pseudo) 프로퍼티
  • 내부 메서드: 수도 메서드

수도 라는 데서 알 수 있듯, 개발자가 접근해서 조작하도록 외부로 공개된 프로퍼티는 아니다. 단지 자바스크립트 엔진의 내부 로직을 설명하기 위해 사용된다. 일부 내부 슬롯과 내부 메서드에 한하여 간접적으로 접근할 수는 있다.


2) 프로퍼티 어트리뷰트

프로퍼티 어트리뷰트는 객체의 구성 요소인 각 프로퍼티상태를 나타낸다. 여기서의 '상태'란 프로퍼티의 값과, 갱신/열거/재정의 가능 여부를 의미한다.

  • [[Value]] : 값
  • [[Writable]] : 값의 변경 가능 여부
  • [[Enumerable] : 프로퍼티의 열거 가능 여부
    • false 인 경우, 객체에 대해 for...in 문이나 Object.Keys 등으로 프로퍼티를 나열할 때 보여지지 않는다.
  • [[Configurable]] : 프로퍼티 재정의 가능 여부

접근자 프로퍼티는 위의 [[Value]][[Writable]] 어트리뷰트 대신 [[Get]], [[Set]] 어트리뷰트를 갖는다.


Object.getOwnPropertyDescriptor

프로퍼티 어트리뷰트는 내부 슬롯이므로 직접 접근할 수는 없다. 하지만 이 메소드를 사용하여 간접적으로 확인할 수는 있다.

Object.getOwnPropertyDescriptor(obj, prop)

이 메서드는 프로퍼티 어트리뷰트 정보를 제공하는 프로퍼티 디스크립터 객체, 우리말로는 '프로퍼티 설명자 객체'를 반환한다.

let person = {name : 'Jane', Age : '22',}

// person 객체의 name 프로퍼티의 상태를 객체로 반환한다.
console.log(Object.getOwnPropertyDescriptor(person, 'name'))
// {value: "Jane", writable: ... (이하생략)}

3) 프로퍼티 구분: 데이터와 접근자

  • 데이터 프로퍼티 : 키와 값으로 구성된 일반적인 프로퍼티다.
  • 접근자 프로퍼티 : 자체적인 값은 없다. 다른 데이터 프로퍼티 의 값을 읽거나 저장할 때 호출되는 접근자 함수 로 구성된 프로퍼티다.

접근자 프로퍼티

접근자 프로퍼티의 본질은 함수다. 이 함수는 값을 획득( get )하고 설정( set )하는 역할을 담당한다. 단 객체 외부의 코드에서는 함수가 아닌 일반적인 프로퍼티처럼 보인다.

let user = {
  name: "John",
  surname: "Smith",
	
  // getter, user.fullName을 실행할 때 실행되는 코드
  get fullName() {
    return `${this.name} ${this.surname}`;
  },

  // setter, user.fullName = value를 실행할 때 실행되는 코드
  set fullName(value) {
    [this.name, this.surname] = value.split(" ");
  }
};

// 주어진 값을 사용해 set fullName이 실행됩니다.
user.fullName = "Alice Cooper";

alert(user.name); // Alice
alert(user.surname); // Cooper

코드 출처: 모던 JS 튜토리얼

위 코드에서 나타나듯, 바깥 코드에선 접근자 프로퍼티를 일반 프로퍼티처럼 사용할 수 있다. 접근자 프로퍼티를 사용하면 함수처럼 호출( () ) 하지 않고, 일반 프로퍼티에서 값에 접근하는 것처럼 평범하게 user.fullName 을 사용해 프로퍼티 값을 얻을 수 있다. 나머지 작업은 getter 메서드가 뒷단에서 처리해준다.

여기서 유의할 점은, 위 코드의 객체에는 fullName 이라는 가상의 프로퍼티가 생긴다는 것이다. 가상의 프로퍼티는 읽고 쓸 순 있지만 실제로는 존재하지 않는다.


접근자 왜 쓰나요?

할당 연산자( = )를 사용해서 값을 그대로 할당하는 데이터 프로퍼티와 달리, 접근자 프로퍼티는 값을 할당하는 과정을 함수로 처리한다. 따라서 gettersetter실제 프로퍼티 값을 감싸는 래퍼(wrapper)처럼 사용하면, 프로퍼티 값을 원하는 대로 통제할 수 있다.

let user = {
  get name() {
    return this._name;
  },

  set name(value) {
    if (value.length < 4) {
      alert("입력하신 값이 너무 짧습니다. 네 글자 이상으로 구성된 이름을 입력하세요.");
      return;
    }
    this._name = value;
  }
};

user.name = "Pete";
alert(user.name); // Pete

user.name = ""; // 너무 짧은 이름을 할당하려 함

코드 출처: 모던 JS 튜토리얼

위 코드에서 user 의 이름은 _name 이라는 실제 프로퍼티에 저장되고, 프로퍼티에 접근하는 것은 gettersetter 를 통해 이뤄진다.

접근자를 사용하지 않았다면 프로퍼티에 4글자 문자열이 들어오든, 빈 문자열이 들어오든 할당을 막을 방법이 없다. 하지만 접근자 프로퍼티를 사용하면 함수 내부에서 if 조건문 등을 사용하여 경우에 따라 다르게 처리할 수 있다.

또, 위에서 '접근자'란 다른 데이터 프로퍼티 의 값을 읽거나 저장할 때 호출된다고 했다. 즉, 객체 내부에서 기존 데이터 프로퍼티의 값을 읽어들여 새로운 프로퍼티를 생성하는 데 사용할 수도 있다.

function User(name, birthday) {
  this.name = name;
  this.birthday = birthday;

  Object.defineProperty(this, "age", {
    get() {
      let todayYear = new Date().getFullYear();
      return todayYear - this.birthday.getFullYear();
    }
  });
}

코드 출처: 모던 JS 튜토리얼


접근자 동작원리

  1. 프로퍼티 키가 유효한지 확인한다.
  2. 프로토타입 체인에서 프로퍼티를 검색한다.
  3. 존재한다면, 해당 프로퍼티가 데이터 프로퍼티인지 접근자 프로퍼티인지 확인한다.
  4. 접근자 프로퍼티의 경우, 실행 방식에 따라 해당 프로퍼티의 어트리뷰트 [[get]] 또는 [[set]] 의 값(함수)을 호출하여 그 결과를 반환한다.

4) 프로퍼티 정의

프로퍼티 정의란 새로운 프로퍼티를 추가하면서 프로퍼티 어트리뷰트를 명시적으로 정의하거나, 기존 프로퍼티의 프로퍼티 어트리뷰트를 재정의하는 것을 말한다.

이를 통해 각 프로퍼티가 어떻게 동작해야 하는지를 명확히 정의할 수 있으며, Object.defineProperty 메서드를 사용한다.

Object.defineProperty(obj, prop, descriptor)
  • obj : 속성을 정의할 객체
  • prop : 새로 정의하거나 수정하려는 속성의 이름 또는 Symbol
  • descriptor : 새로 정의하거나 수정하려는 속성을 기술하는 객체

5) 객체 변경 방지

구분메서드추가삭제값 읽기값 쓰기어트리뷰트 재정의
객체 확장 금지Object.preventExtensionsXOOOO
객체 밀봉Object.sealXXOOX
객체 동결Object.freezeXXOXX
불변 객체Object.freeze 재귀 호출XXOXX

객체 확장 금지, 밀봉, 동결 메서드들은 얕은 변경 방지로, 직속 프로퍼티만 변경이 방지되고 중첩 객체까지는 영향을 주지 못한다.

위 메서드를 사용해 객체 자신이 직접 갖고 있는 프로퍼티들의 의 변경을 제어할 때, 중첩 객체를 값으로 갖는 경우에는 그 객체의 참조값을 갖고 있을 뿐 객체를 직접 갖고 있지 않다. 따라서 해당 참조값의 위치로 들어가야 존재하는 중첩 객체의 값이 변경되지 않도록 제어하기는 어렵다.

따라서 중첩 객체까지 동결하여 변경이 불가능한 읽기 전용의 불변 객체를 구현하려면, 객체를 값으로 갖는 모든 프로퍼티에 대해 재귀적으로 Object.freeze 메서드를 호출해야 한다.

profile
하루가 모여 역사가 된다

0개의 댓글