16장 프로퍼티 어트리뷰트

이로그·2023년 12월 5일
0

16장 프로퍼티 어트리뷰트

16.1 내부 슬롯과 내부 메서드

  • ECMAScript 사양에서 이중 대괄호([[...]])로 감싼 이름들이 내부 슬롯과 내부 메서드다.
  • 내부 슬롯과 내부 메서드는 외부로 공개된 객체의 프로퍼티가 아니기 때문에, 직접적으로 접근하거나 호출할 수 있는 방법을 제공하지 않는다.
  • 일부 내부 슬롯과 내부 메서드에 한하여 간접적으로 접근할 수 있는 수단을 제공한다.
  • 예를 들어, 모든 객체는 [[Prototype]]이라는 내부 슬롯을 갖는다. 원칙적으로 접근할 수 없지만 proto 를 통해 간접적으로 접근할 수 있다.
const o = {};
o.[[Prototype]] // Uncaught SyntaxError
o.__proto__ // Object.prototype

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

  • 자바스크립트 엔진은 프로퍼티를 생성할 때 프로퍼티의 상태를 나타내는 프로퍼티 어트리뷰트를 기본값으로 자동 정의한다.
  • 프로퍼티 어트리뷰트 : 자바스크립트 엔진이 관리하는 내부 상태 값 내부슬롯
    • [[Value]] : 값
    • [[Writable]] : 값의 갱신 여부
    • [[Enumerable]] : 열거 가능 여부
    • [[Configurable]] : 재정의 가능 여부
  • 직접 접근 할 수 없지만 Object.getOwnPropertyDescriptor 메서드를 사용하여 간접적으로 확인할 수 있다.
  • Object.getOwnPropertyDescriptor(객체, 프로퍼티 키)를 사용하면, 프로퍼티 디스크립터 객체를 반환한다. 존재하지 않는 프로퍼티나 상속받는 프로퍼티에 대한 프로퍼티 디스크립터를 요구하면 undefined가 반환된다.
const person = {
    name: 'Lee',
};
// 프로퍼티 동적 생성
person.age = 20;
console.log(Object.getOwnPropertyDescriptor(person, 'name'));
// {"value": "Lee", "writable": true, "enumerable": true, "configurable": true}

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

  • 프로퍼티의 종류
    • 데이터 프로퍼티 : 키와 값으로 구성된 일반적인 프로퍼티. 대부분 모든 프로퍼티는 데이터 프로퍼티다.
    • 접근자 프로퍼티 : 자체적으로 값을 갖지 않고 다른 데이터 프로퍼티의 값을 읽거나 저장할 때 호출되는 접근자 함수로 구성된 프로퍼티다.

16.3.1 데이터 프로퍼티

  • 프로퍼티 어트리뷰트
    • [[Value]]
      • 프로퍼티 키를 통해 프로퍼티 값에 접근하면 반환되는 값.
      • 프로퍼티 키를 통해 프로퍼티 값을 변경하면 [[Value]]에 값을 재할당함. 이때 프로퍼티가 없으면 프로퍼티를 동적 생성하고 생성된 프로퍼티의 [[Value]]에 값을 저장함.
    • [[Writable]]
      • 프로퍼티 값의 변경 가능 여부, 불리언 값.
      • [[Writable]]의 값이 false이면, 해당 프로퍼티의 [[Value]]의 값을 변경할 수 없는 읽기 전용 프로퍼티가 된다.
    • [[Enumerable]]
      • 프로퍼티의 열거 가능 여부, 불리언 값.
      • [[Enumerable]]의 값이 false이면, 해당 프로퍼티는 for...in 문이나 Object.keys 메서드 등으로 열거할 수 없다
    • [[Configurable]]
      • 프로퍼티의 재정의 가능 여부, 불리언 값.
      • [[Configurable]]의 값이 false이면, 해당 프로퍼티의 삭제, 값변경이 금지된다. 단, [[Writable]]이 true일때, [[Value]]의 변경과 [[Writable]]을 false로 변경하는 거슨 허용 된다.
  • 프로퍼티가 생성될때, [[Value]]의 값은 프로퍼티 값으로 초기화, [[Writable]], [[Enumerable]], [[Configurable]]의 값은 true로 초기화 된다. 동적 추가시에도 동일하다.

16.3.2 접근자 프로퍼티

  • 프로퍼티 어트리뷰트
    • [[Get]]
      • 접근자 프로퍼티를 통해 데이터 프로퍼티의 값을 읽을 때 호출되는 접근자 함수.
      • 접근자 프로퍼티 키로 프로퍼티 값에 접근하면 프로퍼티 어트리뷰트 [[Get]]의 값, 즉 getter 함수가 호출되고 그 결과가 프로퍼티 값으로 반환됨.
    • [[Set]]
      • 접근자 프로퍼티를 통해 데이터 프로퍼티의 값을 저장할 때 호출되는 접근자 함수
      • 접근자 프로퍼티 키로 프로퍼티 값을 저장하면 프로퍼티 어트리뷰트 [[Set]]의 값, 즉 setter 함수가 호출되고 그 결과가 프로퍼티 값으로 저장됨
    • [[Enumerable]]
      • 프로퍼티의 열거 가능 여부, 불리언 값
      • 데이터 프로퍼티의 [[Enumerable]]와 같다.
    • [[Configurable]]
      • 프로퍼티의 재정의 가능 여부, 불리언 값
      • 데이터 프로퍼티의 [[Configurable]]와 같다.
  • 접근자 함수는 getter/setter 함수라고도 부른다.
  • getter/setter 함수는 모두 정의할 수도 있고, 하나만 정의할 수도 있다
const person = {
  // 데이트 프로퍼티
  firstName: 'Ungmo',
  lastName: 'Lee',

  // getter 함수
  get fullName() {
    return `${this.firstName} ${this.lastName}`;
  }

  // setter 함수
  set fullName(name) {
    [this.firstName, this.lastName] = name.split(' ');
  }
}
// 데이터 프로퍼티를 통한 프로퍼티 값 참조
console.log(person.firstName + ' ' + person.lastName); // Ungmo Lee

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

// getter를 통한 프로퍼티 값 참조
console.log(person.fullName); // Heegun Lee
  • 접근자 프로퍼티는 자체적으로 값을 가지지 않으며, 다만 데이터 프로퍼티의 값을 읽거나 저장할 때 관여한다.
  • 접근자 프로퍼티와 데이터 프로퍼티를 구별하는 방법은 Object.getOwnPropertyDescriptor 메서드를 이용하는 것이다. 반환하는 값을 보면 구별이 가능하다.
Object.getOwnPropertyDescriptor(Object.prototype, '__proto__');
// {get: f, set: f, enumerable: false, configurable: true}
Object.getOwnPropertyDescriptor(function() {}, 'prototype');
// {value: {...}, writable: true, enumerable: false, configurable: false}

16.4 프로퍼티 정의

  • 프로퍼티 정의 : 프로퍼티의 값을 갱신 가능하도록 할 것인지, 프로퍼티를 열거 가능하도록 할 것인지, 프로퍼티를 재정의 가능하도록 할 것인지 정의하는 것.
  • Object.defineProperty(객체, 데이터 프로퍼티 키, 프로퍼티 디스크립터 객체) 메서드를 사용하면 프로퍼티의 어트리뷰트를 정의할 수 있음.
    • 디스크립터 객체의 프로퍼티를 누락시키면 undefined, false가 기본값이다.
const person = {};

// 데이터 프로퍼티 정의
Object.defineProperty(person, 'firstName', {
  value: 'Ungmo',
  writable: true,
  enumerable: true,
  configurable: true,
})
Object.defineProperty(person, 'LastName', {
  value: 'Lee',
})

console.log(Object.getOwnPropertyDescriptor(person, 'firstName'));
// firstName {value: 'Ungmo', writable: true, enumerable: true, configurable: true}

console.log(Object.getOwnPropertyDescriptor(person, 'lastName'));
// lastName {value: 'Lee', writable: false, enumerable: false, configurable: false}

// 접근자 프로퍼티 정의
Object.defineProperty(person, 'fullName', {
  // getter 함수
  get() {
    return `${this.firstName} ${this.lastName}`;
  }

  // setter 함수
  set(name) {
    [this.firstName, this.lastName] = name.split(' ');
  },
  enumerable: true,
  configurable: true,
})

console.log(Object.getOwnPropertyDescriptor(person, 'fullName'));
// fullName {get: f, set: f, enumerable: true, configurable: true}
  • Object.defineProperties 메서드를 사용하면 여러 개의 프로퍼티를 한 번에 정의할 수 있다.
const person = {};
Object.defineProperties(person, {
  // 데이터 프로퍼티 정의
  firstName: {
    value: 'Ungmo',
    writable: true,
    enumerable: true,
    configurable: true,
  },
  lastName: {
    value: 'Lee',
    writable: true,
    enumerable: true,
    configurable: true,
  },
  // 접근자 프로퍼티 정의
  fullName: {
    // getter 함수
    get() {
      return `${this.firstName} ${this.lastName}`;
    }

    // setter 함수
    set(name) {
      [this.firstName, this.lastName] = name.split(' ');
    },
    enumerable: true,
    configurable: true,
  }
});

person.fullName = 'Heegun Lee';
console.log(person); // {firstName: 'Heegun', lastName: 'Lee'}

16.5 객체 변경 방지

  • 자바스크립트는 객체의 변경을 방지하는 메서드를 제공한다

16.5.1 객체 확장 금지

  • Object.preventExtensions 메서드 : 객체의 확장 금지(= 프로퍼티 추가 금지)
  • 프로퍼티 동적 추가와 Object.defineProperty 메서드로 추가할수 있는데, 이 두가지 모두 금지 된다.
  • Object.isExtensible 메서드로 확장이 가능한 객체인지 확인할 수 있다.
const person = {name: 'Lee'};

console.log(Object.isExtensible(person)); // true

Object.preventExtensions(person); // 추가 금지
console.log(Object.isExtensible(person)); // false

person.age = 20; // 무시.
console.log(person); // {name: 'Lee'}

delete person.name;// 삭제는 가능
console.log(person); // {}

16.5.2 객체 밀봉

  • Object.seal 메서드 : 프로퍼티 추가, 삭제, 프로퍼티 어트리뷰트 재정의 금지. 읽기와 쓰기만 가능
  • Object.isSealed 메서드로 밀복된 객체인지 확인할 수 있다.
  • 밀봉된 객체는 configurable이 false다.
const person = {name: 'Lee'};

console.log(Object.isSealed(person)); // false

Object.seal(person); // 객체 밀봉
console.log(Object.isSealed(person)); // true

person.age = 20; // 프로퍼티 추가 금지 / 무시.
console.log(person); // {name: 'Lee'}

delete person.name // 프로퍼티 삭제 금지 / 무시
console.log(person); // {name: 'Lee'}

person.name = 'Kim'; // 프로퍼티 값 갱신은 가능
console.log(person); // {name: 'Kim'}

Object.defineProperty(person, 'name', {configurable: true}); // TypeError

16.5.3 객체 동결

  • Object.freeze 메서드 : 프로퍼티 추가, 삭제, 프로퍼티 어트리뷰트 재정의 금지, 프로퍼티 값 갱신 금지. 읽기만 가능
  • Object.isFrozen 메서드로 동결된 객체인지 확인할 수 있다.
  • 동결된 객체는 writable과 configurable가 false다
const person = {name: 'Lee'};

console.log(Object.isFrozen(person)); // false

Object.freeze(person); // 객체 동결
console.log(Object.isFrozen(person)); // true


person.age = 20; // 프로퍼티 추가 금지 / 무시.
console.log(person); // {name: 'Lee'}

delete person.name // 프로퍼티 삭제 금지 / 무시
console.log(person); // {name: 'Lee'}

person.name = 'Kim'; // 프로퍼티 값 갱신 금지 / 무시
console.log(person); // {name: 'Lee'}

Object.defineProperty(person, 'name', {configurable: true}); // TypeError

16.5.4 불변 객체

  • 지금까지 살펴본 변경 방지 메서드들은 얕은 변경 방지로, 중첩 객체까지는 영향을 주지 못한다.
  • 객체의 중첩 객체까지 동결하여 변경이 불가능한 읽기 전용의 불변 객체를 구현하려면 객체를 값으로 갖는 모든 프로퍼티에 대해 재귀적으로 Object.freeze 메서드를 호출해야 한다.
function deepFreeze(target) {
  if(target && typeof target === 'object' && !Object.isFrozen(target)) {
    Object.freeze(target);

    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'}}

0개의 댓글