16장 프로퍼티 어트리뷰트


내부 슬롯과 내부 메서드

내부 슬롯 , 내부 메서드 는 쉽게 말해서 숨겨져 있는 프로퍼티, 메서드라고 생각하면 됨.

JS 엔진에서실제로 동작하지만 개발자가 직접 접근하도록 외부로 공개된 객체의 프로퍼티는 아니다. 그래서 내부 슬롯과 내부 메서드에 직접적으로 접근하거나 호출할 수 없음.

일부에 경우에 간접적인 접근 가능 ( ex, [[Prototype]] )

const o = {};

// 내부 슬롯은 자바스크립트 엔진의 내부 로직이므로 직접 접근할 수 없다.
o.[[Prototype]] // -> Uncaught SyntaxError: Unexpected token '['
// 단, 일부 내부 슬롯과 내부 메서드에 한하여 간접적으로 접근할 수 있는 수단을 제공하기는 한다.
o.__proto__ // -> Object.prototype

프로퍼티 어트리 뷰트와 프로퍼티 디스크립터 객체

프로퍼티 생성하면 프로퍼티의 상태를 나타내는 프로퍼티 어트리뷰트 를 기본값으로 자동 정의
여기서 프로퍼티 상태란 value, writable, enumeratble, configurable 말함.
각 상태값에 대한 내부 슬롯 [[Value]], [[Writable]], [[Enumeratble]], [[Configurable ]] 이 있는데, 당연히 직접 접근 할 수 없고 Object.getOwnPropertyDescriptor 메서드 사용해서 간접 확인 가능.

const person = {
  name: 'Lee'
};

// 프로퍼티 어트리뷰트 정보를 제공하는 프로퍼티 디스크립터 객체를 반환한다.
console.log(Object.getOwnPropertyDescriptor(person, 'name'));
// {value: "Lee", writable: true, enumerable: true, configurable: true}

Object.getOwnPropertyDescriptor( 객체의 참조, 프로퍼티 키(문자열))
프로퍼티 디스크립터 객체 반환함.
프로퍼티 디스크립터 : 프로퍼티 어트리뷰트 정보 객체 형태로 가지고 있음.

데이터 프로퍼티와 접근자 프로퍼티

데이터 프로퍼티 : 키와 값으로 구성된 일반적인 프로퍼티
접근자 프로퍼티 : 자체적으로 값 갖지않고 다른 프로퍼티의 값 저장하고 호출하는 접근자 함수 로 구성된 프로퍼티

즉, 접근자 프로퍼티는 값이 아니라, 다른 프로퍼티를 위한 Get, Set 함수로 구성된 일종의 식별자 느낌이랄까.
그래서 프로퍼티 종류마다 가지는 프로퍼티 어트리뷰트 종류가 좀 다름

데이터 프로퍼티

프로퍼티 어트리뷰트 프로퍼티 디스크립터
객체의 프로퍼티
설명
[[Value]] value 프로퍼티 값이라고 보면 됨
[[Writable]] writable 변경가능 여부, false 일 경우 [[Value]]변경 불가
[[Enumerable]] enumerable 열거가능 여부, false 일 경우 for문이나 Object.key 메서드에서 열겨 안됨
[[Configurable]] configurable 재정의 가능 여부, false 일 경우 프로퍼티 삭제, 프로퍼티 어트리뷰트 값 변경 금지. 근데 또, [[Writable]]이 true인 경우에는 [[Value]] 값 변경 가능하고, [[Writable]] flase로 바꾸는건 가능이라나 뭐라나

프로퍼티 생성되면 [[Value]] 값은 프로터피 값으로 초기화 되고, 나머지 어트리뷰트는 true로 초기화 된다. ( 동적 추가도 마찬가지 )

접근자 프로퍼티

프로퍼티 어트리뷰트 프로퍼티 디스크립터
객체의 프로퍼티
설명
[[Get]] get getter 함수 호출
[[Set]] set setter 함수 호출
[[Enumerable]] enumerable 데이터 프로퍼티와 동일
[[Configurable]] configurable 데이터 프로퍼티와 동일
const person = {
  // 데이터 프로퍼티
  firstName: 'Ungmo',
  lastName: 'Lee',

  // fullName은 접근자 함수로 구성된 접근자 프로퍼티다.
  // getter 함수
  get fullName() {
    return `${this.firstName} ${this.lastName}`;
  },
  // setter 함수
  set fullName(name) {
    // 배열 디스트럭처링 할당: "31.1 배열 디스트럭처링 할당" 참고
    [this.firstName, this.lastName] = name.split(' ');
  }
};

// 데이터 프로퍼티를 통한 프로퍼티 값의 참조.
console.log(person.firstName + ' ' + person.lastName); // Ungmo Lee

// 접근자 프로퍼티를 통한 프로퍼티 값의 저장
person.fullName = 'Heegun Lee';
console.log(person); // {firstName: "Heegun", lastName: "Lee"}

// 접근자 프로퍼티를 통한 프로퍼티 값의 참조
console.log(person.fullName); // Heegun Lee

// firstName은 데이터 프로퍼티다.
// 데이터 프로퍼티는 [[Value]], [[Writable]], [[Enumerable]], [[Configurable]] 프로퍼티 어트리뷰트를 갖는다.
let descriptor = Object.getOwnPropertyDescriptor(person, 'firstName');
console.log(descriptor);
// {value: "Heegun", writable: true, enumerable: true, configurable: true}

// fullName은 접근자 프로퍼티다.
// 접근자 프로퍼티는 [[Get]], [[Set]], [[Enumerable]], [[Configurable]] 프로퍼티 어트리뷰트를 갖는다.
descriptor = Object.getOwnPropertyDescriptor(person, 'fullName');
console.log(descriptor);
// {get: ƒ, set: ƒ, enumerable: true, configurable: true}

set , get 으로 접근자 프로퍼티의 접근자 함수 선언
접근자 프로퍼티 이용할 때, 함수처럼 인자를 전달하는게 아니라 변수처럼 사용함.

parameter가 get은 0개 set은 1개가 고정인가?

프로퍼티 정의

새로운 프로퍼티 추가하면서 프로퍼티 어트리뷰트 를 명시적으로 정의하거나 기존 프로퍼티의 어트리뷰트 재정의 가능함.

Object.defineProperties 이용해서 한번에 여러개의 프로퍼티도 정의 가능

객체 변경 방지

객체는 변경 가능한 값이여서 재할당 없이도 값 변경이 가능함
⇒ 객체의 변경을 방지하는 다양한 메서드들이 있다고 한다.

Object.preventExtensions : 객체 확장 금지
Object.seal : 객체 밀봉 (프로퍼티 삭제, 어트리뷰트 재정의 금지)
Object.freeze : 객체 동결 ( 밀봉 + 프로퍼티 값 수정 불가 )

여기서 어트리뷰트 재정의라는 표현을 씀. 즉, 어트리뷰트들이 객체의 형태가 아닌 변수. 즉 원시값 변수형태? 여서 수정 대신 재정의라는 표현을 쓴 듯. 고로 위의 나의 뇌피셜에 대한 검증
(원시갑과 객체의 재할당에 대한 차이는 11장 원시 값과 객체의 비교 참조)

객체 불변

Object.freeze 사용해도 중첩객체 까지는 동결 시킬 수 업음.
⇒ 중첩 객체도 동결해서 불변 객체 만들려고 하면 재귀적으로 얼려주면 됨.

function deepFreeze(target) {
  // 객체가 아니거나 동결된 객체는 무시하고 객체이고 동결되지 않은 객체만 동결한다.
  if (target && typeof target === 'object' && !Object.isFrozen(target)) {
    Object.freeze(target);
    /*
      모든 프로퍼티를 순회하며 재귀적으로 동결한다.
      Object.keys 메서드는 객체 자신의 열거 가능한 프로퍼티 키를 배열로 반환한다.
      ("19.15.2. Object.keys/values/entries 메서드" 참고)
      forEach 메서드는 배열을 순회하며 배열의 각 요소에 대하여 콜백 함수를 실행한다.
      ("27.9.2. Array.prototype.forEach" 참고)
    */
    Object.keys(target).forEach(key => deepFreeze(target[key]));
  }
  return target;
}

const person = {
  name: 'Lee',
  address: { city: 'Seoul' }
};

// 깊은 객체 동결
deepFreeze(person);

console.log(Object.isFrozen(person)); // true
// 중첩 객체까지 동결한다.
console.log(Object.isFrozen(person.address)); // true

person.address.city = 'Busan';
console.log(person); // {name: "Lee", address: {city: "Seoul"}}

reference