모던 자바스크립트 Deep Dive - 16장 프로퍼티 어트리뷰트
Study
JavaScript
2024.01.23
16.1 내부 슬롯과 내부 메서드
- 자바스크립트 엔진의 구현 알고리즘을 설명하기 위해 ECMAScript 사양에서 사용하는 의사 프로퍼티 (pseudo property), 의사 메서드 (pseudo method)
- ECMAScript 사양에서 등장하는 이중 대괄호(
[[...]]
)로 감싼 이름이 내부 슬롯과 내부 메서드임 - 자바스크립트 엔진에서 실제로 동작하지만 개발자가 직접 접근할 수 있도록 외부로 공개된 객체의 프로퍼티는 아님
- 일부 내부 슬롯과 내부 메서드에 한하여 간접적으로 접근할 수 있는 수단 제공
- ex) 모든 객체는
[[Prototype]]
내부 슬롯을 가지며,__proto__
를 통해 간접적으로 접근 가능
- ex) 모든 객체는
- 일부 내부 슬롯과 내부 메서드에 한하여 간접적으로 접근할 수 있는 수단 제공
16.2 프로퍼티 어트리뷰트와 프로퍼티 디스크럽터 객체
- 자바스크립트 엔진은 프로퍼티를 생성할 때 프로퍼티의 상태를 나타내는 프로퍼티 어트리뷰트를 기본 값으로 자동 정의함
- 프로퍼티의 상태 4가지
- 프로퍼티 값:
value
- 값의 갱신 여부:
writable
- 열거 가능 여부:
enumerable
- 재정의 가능 여부:
configurable
- 프로퍼티 값:
- 프로퍼티 어트리뷰트는 자바스크립트 엔진이 관리하는 내부 상태 값 (meta-property) 인 내부 슬롯
[[Value]]
,[[Writable]]
,[[Enumerable]]
,[[Configurable]]
을 뜻함.- (단일 프로퍼티)
Object.getOwnPropertyDescriptor
, (모든 프로퍼티)Object.getOwnPropertyDescriptors
메서드로 간접적으로 확인 가능const person = { name: 'Son' }; // 프로퍼티 어트리뷰트 정보를 제공하는 프로퍼티 디스크립터 객체를 반환한다. console.log(Object.getOwnPropertyDescriptor(person, 'name')); // {value: "Son", writable: true, enumerable: true, configurable: true} // 프로퍼티 동적 생성 person.age = 20; // 모든 프로퍼티의 프로퍼티 어트리뷰트 정보를 제공하는 프로퍼티 디스크립터 객체들을 반환한다. console.log(Object.getOwnPropertyDescriptors(person)); /* { name: {value: "Son", writable: true, enumerable: true, configurable: true}, age: {value: 20, writable: true, enumerable: true, configurable: true} } */
- (단일 프로퍼티)
- 프로퍼티의 상태 4가지
16.3 데이터 프로퍼티와 접근자 프로퍼티
- 데이터 프로퍼티 (data property): 키와 값으로 구성된 프로퍼티
- 접근자 프로퍼티 (accessor property): 자체적으로 값을 갖지 않고 다른 데이터 프로퍼티의 값을 읽거나 저장할 때 호출되는 접근자 함수로 구성된 프로퍼티
16.3.1 데이터 프로퍼티
- 데이터 프로퍼티 명세
프로퍼티 어트리뷰트 프로퍼티 디스크립터 객체의 프로퍼티 설명 [[Value]]
value
- 프로퍼티 키를 통해 프로퍼티 값에 접근하면 반환되는 값이다.
- 프로퍼티 키를 통해 프로퍼티 값을 변경하면
[[Value]]
에 값을 재할당한다.
이때 프로퍼티가 없으면 프로퍼티를 동적 생성하고 생성된 프로퍼티의[[Value]]
에 값을 저장한다.
[[Writable]]
writable
- 프로퍼티 값의 변경 가능 여부를 나타내며 불리언 값을 갖는다.
[[Writable]]
의 값이false
인 경우 해당 프로퍼티의[[Value]]
의 값을 변경할 수 없는 읽기 전용 프로퍼티가 된다.
[[Enumerable]]
enumerable
- 프로퍼티의 열거 가능 여부를 나타내며 불리언 값을 갖는다.
[[Enumerable]]
의 값이false
인 경우 해당 프로퍼티는for...in
문이나Object.keys
메서드 등으로 열거할 수 없다.
[[Configurable]]
configurable
- 프로퍼티의 재정의 가능 여부를 나타내며 불리언 값을 갖는다.
[[Configurable]]
의 값이 false인 경우 해당 프로퍼티의 삭제, 프로퍼티 어트리뷰트 값의 변경이 금지된다. 단,[[Writable]]
이true
인 경우[[Value]]
의 변경과[[Writable]]
을false
로 변경하는 것은 허용된다.
- 프로퍼티가 생성될 때
[[Value]]
의 값은 프로퍼티 값으로 초기화되며[[Writable]]
,[[Enumerable]]
,[[Configurable]]
의 값은true
로 초기화
16.3.2 접근자 프로퍼티
- 접근자 프로퍼티 명세
프로퍼티 어트리뷰트 프로퍼티 디스크립터 객체의 프로퍼티 설명 [[Get]]
get
접근자 프로퍼티를 통해 데이터 프로퍼티의 값을 읽을 때 호출되는 접근자 함수다. 즉, 접근자 프로퍼티 키로 프로퍼티 값에 접근하면 프로퍼티 어트리뷰트 [[Get]]
의 값, 즉 getter 함수가 호출되고 그 결과가 프로퍼티 값으로 반환된다.[[Set]]
set
접근자 프로퍼티를 통해 데이터 프로퍼티의 값을 저장할 때 호출되는 접근자 함수다. 즉, 접근자 프로퍼티 키로 프로퍼티 값을 저장하면 프로퍼티 어트리뷰트 [[Set]]
의 값, 즉 setter 함수가 호출되고 그 결과가 프로퍼티 값으로 저장된다.[[Enumerable]]
enumerable
데이터 프로퍼티의 [[Enumerable]]
과 같다.[[Configurable]]
configurable
데이터 프로퍼티의 [[Configurable]]
과 같다.- 접근자 함수는 getter/setter 라고도 부름.
- 코드 예시
const person = { // 데이터 프로퍼티 firstName: 'Hoyoung', lastName: 'Son', // 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); // Hoyoung Son // 접근자 프로퍼티를 통한 프로퍼티 값의 저장 // 접근자 프로퍼티 fullName에 값을 저장하면 setter 함수가 호출된다. person.fullName = 'Hoyoung Son'; console.log(person); // {firstName: "Hoyoung", lastName: "Son"} // 접근자 프로퍼티를 통한 프로퍼티 값의 참조 // 접근자 프로퍼티 fullName에 접근하면 getter 함수가 호출된다. console.log(person.fullName); // Hoyoung Son
- 접근자 프로퍼티
fullName
으로 프로퍼티 값에 접근하면 내부적으로[[Get]]
내부 메서드 호출
👇 세부 동작 설명- 프로퍼티 키가 유효한지 확인한다. 프로퍼티 키는 문자열 또는 심벌이어야 한다. 프로퍼티 키 "fullName"은 문자열이므로 유효한 프로퍼티 키다.
- 프로토타입 체인에서 프로퍼티를 검색한다.
person
객체에fullName
프로퍼티가 존재한다. - 검색된
fullName
프로퍼티가 데이터 프로퍼티인지 접근자 프로퍼티인지 확인한다.fullName
프로퍼티는 접근자 프로퍼티다. - 접근자 프로퍼티
fullName
의 프로퍼티 어트리뷰트[[Get]]
의 값, 즉getter
함수를 호출하여 그 결과를 반환한다.
프로퍼티fullName
의 프로퍼티 어트리뷰트[[Get]]
의 값은Object.getOwnPropertyDescriptor
메서드가 반환하는 프로퍼티 디스크립터(PropertyDescriptor) 객체의get
프로퍼티 값과 같다.
- 접근자 프로퍼티
+ 접근자 프로퍼티와 데이터 프로퍼티 구별법
Object.getOwnPropertyDescriptor
메서드가 반환한 프로퍼티 어트리뷰트를 객체로 표현한 프로퍼티 디스크립터 객체 비교하면 됨// 일반 객체의 __proto__는 접근자 프로퍼티다. Object.getOwnPropertyDescriptor(Object.prototype, '__proto__'); // {get: ƒ, set: ƒ, enumerable: false, configurable: true} // 함수 객체의 prototype은 데이터 프로퍼티다. Object.getOwnPropertyDescriptor(function() {}, 'prototype'); // {value: {...}, writable: true, enumerable: false, configurable: false}
16.4 프로퍼티 정의
- 새로운 프로퍼티를 추가하면서 프로퍼티 어트리뷰트를 명시적으로 정의하거나, 기존 프로퍼티의 프로퍼티 어트리뷰트를 재정의하는 것을 말함
- (단일 프로퍼티)
Object.defineProperty
, (다수 프로퍼티)Object.defineProperties
로 프로퍼티 어트리뷰트 정의 가능- 단일 프로퍼티 어트리뷰트 정의
// 데이터 프로퍼티 정의 Object.defineProperty(person, 'firstName', { value: 'Hoyoung', writable: true, enumerable: true, configurable: true, }); // 접근자 프로퍼티 정의 Object.defineProperty(person, 'fullName', { // getter 함수 get() { return `${this.firstName} ${this.lastName}`; }, // setter 함수 set(name) { [this.firstName, this.lastName] = name.split(' '); }, enumerable: true, configurable: true, });
- 다수 프로퍼티 어트리뷰트 정의
const person = {}; Object.defineProperties(person, { // 데이터 프로퍼티 정의 firstName: { value: 'Hoyoung', writable: true, enumerable: true, configurable: true, }, lastName: { value: 'Son', 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, }, });
- 단일 프로퍼티 어트리뷰트 정의
- 프로퍼티 어트리뷰트 정의 시 생략된 어트리뷰트의 기본값
프로퍼티 디스크립터 객체의 프로퍼티 대응하는 프로퍼티 어트리뷰트 생략했을 때의 기본값 value
[[Value]]
undefined
get
[[Get]]
undefined
set
[[Set]]
undefined
writable
[[Writable]]
false
enumerable
[[Enumerable]]
false
configurable
[[Configurable]]
false
- (단일 프로퍼티)
16.5 객체 변경 방지
- 객체 변경 방지 메서드들
구분 메서드 프로퍼티
추가프로퍼티
삭제프로퍼티 값
읽기프로퍼티 값
쓰기프로퍼티 어트리뷰트
재정의객체 확장 금지 Object.preventExtensions
X O O O O 객체 밀봉 Object.seal
X X O O X 객체 동결 Object.freeze
X X O X X
16.5.1 객체 확장 금지
Object.preventExtensions(<객체>)
: 특정 객체 확장 금지 (프로퍼티 추가 금지)Object.isExtensible(<객체>)
: 확장 가능한 객체인지 체크
16.5.2 객체 밀봉
Object.seal(<객체>)
: 특정 객체 밀봉 (프로퍼티 추가 및 삭제, 프로퍼티 어트리뷰트 재정의 금지)- 읽기, 쓰기만 가능
Object.isSealed(<객체>)
: 밀봉된 객체인지 체크
16.5.3 객체 동결
Object.freeze(<객체>)
: 객체 동결 (프로퍼티 추가 및 삭제, 프로퍼티 어트리뷰트 재정의 금지, 프로퍼티 값 갱신 금지)- 읽기만 가능
Object.isFrozen(<객체>)
: 동결된 객체인지 체크
16.5.4 불변 객체
- 변경 방지 메서드들은 얕은 변경 방지 (shallow only) 직속 프로퍼티만 변경 방지 가능
- 중첩 객체는 영향을 주지 못하므로 재귀 함수를 구현하여 처리
// 예시 함수) 모든 프로퍼티를 순회하며 재귀적으로 동결함 function deepFreeze(target) { if (target && typeof target === 'object' && !Object.isFrozen(target)) { Object.freeze(target); Object.keys(target).forEach(key => deepFreeze(target[key])); } return target; }
- 중첩 객체는 영향을 주지 못하므로 재귀 함수를 구현하여 처리