[JS] Property Attribute

돗개·2022년 11월 10일
0

crack JavaScript

목록 보기
13/18

내부 슬롯과 내부 메서드

자바스크립트 엔진의 구현 알고리즘을 설명하기 위해 ECMAScript 사양에서 사용하는 의사 프로퍼티와 의사 메서드.
이중 대괄호 [[...]] 로 감싼 이름들로, 자바스크립트 엔진의 내부 로직이므로 직접 접근/호출할 수 있는 방법을 제공하지 않는다. (일부 내부 슬롯/메서드에 한해서만 가능)

  • 모든 객체는 [[Prototype]]이라는 내부 슬롯을 갖는다.
  • [[Prototype]]의 내부 슬롯의 경우, __proto__를 통해 간접적으로 접근 가능.
const o = {};
o.[[Prototype]]  // Uncaught SyntaxError: Unexpected token '['
o.__proto__  // Object.prototype

Property Attribute

자바스크립트 엔진이 관리하는 내부 상태 값(meta-property)인 내부 슬롯으로, 프로퍼티의 상태를 나타낸다. (프로퍼티의 값, 값의 갱신 가능 여부, 열거 가능 여부, 재정의 가능 여부)
자바스크립트 엔진은 프로퍼티를 생성할 때, 프로퍼티 어트리뷰트를 기본값으로 자동 정의한다.

  • [[Value]] - 프로퍼티의 값
  • [[Writable]] - 값의 갱신 가능 여부
  • [[Enumerable]] - 열거 가능 여부
  • [[Configurable]] - 재정의 가능 여부
  • 프로퍼티 어트리뷰트에 직접 접근할 수 없지만, Object.getOwnPropertyDescriptor 메서드를 사용해 간접적으로 확인할 수는 있다.
const person = {
  name: 'Lee'
}
person.age = 20;
console.log(Object.getOwnPropertyDescriptors(person, 'name'));
// {value: "Lee", writable: true, enumerable: true, configurable: true}
console.log(Object.getOwnPropertyDescriptors(person));
// {value: "Lee", writable: true, enumerable: true, configurable: true}
// {value: 20, writable: true, enumerable: true, configurable: true}         

Data property & Accessor property

프로퍼티는 데이터 프로퍼티와 접근자 프로퍼티로 구분할 수 있다.

데이터 프로퍼티

: 키와 값으로 구성된 일반적인 프로퍼티. (지금까지 살펴본)

  • [[Value]] - value - 프로퍼티 키를 통해 프로퍼티 값에 접근하면 반환되는 값. 키를 통해 프로퍼티 값을 변경하면 여기에 값을 재할당. 프로퍼티가 없으면 프로퍼티를 동적 생성하고 값을 저장.
  • [[Writable]] - writable - 프로퍼티 값의 갱신 가능 여부. false인 경우 읽기 전용 프로퍼티.
  • [[Enumerable]] - enumerable - 열거 가능 여부. false인 경우 for...in문이나 Object.keys 메서드 등 사용 불가.
  • [[Configurable]] - configurable - 재정의 가능 여부. false인 경우 해당 프로퍼티의 삭제, 프로퍼티 어트리뷰트 값의 변경이 금지됨.
  • 프로퍼티 생성 시, value는 프로퍼티 값으로, 나머지는 true로 초기화된다.

접근자 프로퍼티

: 자체적으로 값([[Value]])을 갖지 않고, 다른 데이터 프로퍼티의 값을 읽거나 저장할 때 호출되는 접근자 합수로 구성된 프로퍼티.

  • [[Get]] - get - 접근자 프로퍼티를 통해 데이터 프로퍼티의 값을 읽을 때 호출되는 접근자 함수. (접근자 프로퍼티 키로 프로퍼티 값에 접근하면 getter 함수가 호출되고, 그 결과가 프로퍼티 값으로 반환됨)
  • [[Set]] - set - 접근자 프로퍼티를 통해 데이터 프로퍼티의 값을 저장할 때 호출되는 접근자 함수. (접근자 프로퍼티 키로 프로퍼티 값을 저장하면 setter 함수가 호출되고, 그 결과가 프로퍼티 값으로 저장됨)
  • 데이터 프로퍼티와 같은 [[Enumerable]] [[Configurable]]도 존재.
  • 접근자 함수는 getter/setter 함수라고도 부름. 접근자 프로퍼티는 getter/setter 함수를 정의할 수 있다.
const person = {
  // data property
  firstName: 'Juno',
  lastName: 'Kim',
  
  // accessor property
  // getter function
  get fullName() {
    return `${this.firstName} ${this.lastName}`;
  },
  // setter function
  set fullName(name) {
    [this.firstName, this.lastName] = name.split(' ');
  }
};

// data property를 통한 프로퍼티 값 참조
console.log(person.firstName, person.lastName);  // Juno, Kim
// accessor property를 통한 프로퍼티 값 저장
person.fullName = 'Suhyeon Park';
console.log(person);  // {firstName: 'Suhyeon', lastName: 'Park'}
// accessor property를 통한 프로퍼티 값 참조
console.log(person.fullName);  // 'Suhyeon Park'

// firstName is data property
console.log(Object.getOwnPropertyDescriptor(person, 'firstName'));
// {value: 'Suhyeon', writable: true, enumerable: true, configurable: true}
console.log(Object.getOwnPropertyDescriptor(person, 'fulllName'));
// {get: f, set: f, enumerable: true, configurable: true}

// 일반 객체의 __proto__는 접근자 프로퍼티
Object.getOwnPropertyDescriptor(Object.prototype, '__proto__');
// 함수 객체의 prototype은 데이터 프로퍼티
Object.getOwnPropertyDescriptor(function() {}, 'prototype')

프로퍼티 정의

새로운 프로퍼티를 추가하면서 프로퍼티 어트리뷰트를 명시적으로 정의하거나, 기존 프로퍼티의 프로퍼티 어트리뷰트를 재정의하는 것.
Object.defineProperty 메서드를 사용하면 프로퍼티의 어트리뷰트를 정의할 수 있다.

const person = {};
// 데이터 프로퍼티 정의
Object.defineProperty(person, 'firstName', {
  value: 'Suhyeon',
  writable: true,
  enumerable: true,
  configurable: true
});
Object.defineProperty(person, 'lastName', {
  value: 'Park'  // value를 정의하지 않을 경우 undefined
  // 나머지 속성을 정의하지 않으면 기본값 false
});

Object.getOwnPropertyDescriptor(person, 'firstName');
// {value: 'Suhyeon', writable: true, enumerable: true, configurable: true}
Object.getOwnPropertyDescriptor(person, 'lastName');
// {value: 'Park', writable: false, enumerable: false, configurable: false}
Object.keys(person);  // ['firstName']
person.lastName = 'Kim';  // 변경되지 않고 무시됨
delete person.lastName;  // Uncaught TypeError: Cannot redefine property: lastName

// 접근자 프로퍼티 정의
Object.defineProperty(person, 'fullName', {
  // getter function
  get() {
    return `${this.firstName} ${this.lastName}`
  },
  // setter function
  set(name) {
    [this.firstName, this.lastName] = name.split(' ');
  },
  enumerable: true,
  configurable: true
});

// 여러 프로퍼티를 한번에 정의하려면
Object.defineProperties(person, {
  firstName: {...},
  lastName: {...},
  fullName: {
    get() {},
    set() {},
  }
});

객체 변경 방지

자바스크립트는 객체의 변경을 방지하는 다양한 메서드를 제공. (변경을 금지하는 강도가 다름)

  • Object.preventExtensions (객체 확장 금지): 프로퍼티 추가 금지.
  • Object.seal (객체 밀봉): 프로퍼티 추가/삭제 및 어트리뷰트 재정의 금지. (읽기와 쓰기만 가능)
  • Object.freeze (객체 동결): 프로퍼티 추가/삭제 및 프로퍼티 어트리뷰트 재정의 금지, 프로퍼티 값 갱신 금지. (읽기만 가능)
  • 위의 메서드들은 얕은 변경 방지로 직속 프로퍼티만 변경이 되고, 중첩 객체까지는 영향을 주지 못하므로, 중첩 객체까지 동결하려면 재귀적으로 메서드를 호출해야 함.
const person = { 
  name: 'Lee',
  address: { city: 'seoul' }
};

// 1) 객체 확장 금지
Object.isExtensible(person);  // true
Object.preventExtensions(person);
person.age = 20;  // 무시 (프로퍼티 추가 금지)
delete person.name;  // 프로퍼티 삭제는 가능

// 2) 객체 밀봉
Object.isSealed(person);  // false
Object.seal(person);
person.age = 20;  // 무시 (프로퍼티 추가 금지)
delete person.name;  // 무시 (프로퍼티 삭제 금지)
person.name = 'Park';  // 프로퍼티 값 갱신은 가능
Object.defineProperty(person, 'name', { configurable: true });  // 어트리뷰트 재정의 금지 (TypeError)

// 3) 객체 동결
Object.isFreeze(person);  // false
Object.freeze(person);
person.age = 20;  // 무시 (프로퍼티 추가 금지)
delete person.name;  // 무시 (프로퍼티 삭제 금지)
person.name = 'Park';  // 무시 (프로퍼티 값 갱신 금지)
Object.defineProperty(person, 'name', { configurable: true });  // 어트리뷰트 재정의 금지 (TypeError)
// 주의: 중첩 객체는 동결되지 않음!!
Object.isFreeze(person.address);  // false
person.address.city = 'tokyo';
console.log(person.address.city);  // tokyo
profile
울보 개발자(멍.. 하고 울어요)

0개의 댓글