자바스크립트 엔진의 구현 알고리즘을 설명하기 위해 ECMAScript 사양에서 사용하는 의사 프로퍼티와 의사 메서드.
이중 대괄호[[...]]
로 감싼 이름들로, 자바스크립트 엔진의 내부 로직이므로 직접 접근/호출할 수 있는 방법을 제공하지 않는다. (일부 내부 슬롯/메서드에 한해서만 가능)
[[Prototype]]
이라는 내부 슬롯을 갖는다.[[Prototype]]
의 내부 슬롯의 경우, __proto__
를 통해 간접적으로 접근 가능.const o = {};
o.[[Prototype]] // Uncaught SyntaxError: Unexpected token '['
o.__proto__ // Object.prototype
자바스크립트 엔진이 관리하는 내부 상태 값(meta-property)인 내부 슬롯으로, 프로퍼티의 상태를 나타낸다. (프로퍼티의 값, 값의 갱신 가능 여부, 열거 가능 여부, 재정의 가능 여부)
자바스크립트 엔진은 프로퍼티를 생성할 때, 프로퍼티 어트리뷰트를 기본값으로 자동 정의한다.
[[Value]]
- 프로퍼티의 값[[Writable]]
- 값의 갱신 가능 여부[[Enumerable]]
- 열거 가능 여부[[Configurable]]
- 재정의 가능 여부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}
프로퍼티는 데이터 프로퍼티와 접근자 프로퍼티로 구분할 수 있다.
: 키와 값으로 구성된 일반적인 프로퍼티. (지금까지 살펴본)
[[Value]]
- value
- 프로퍼티 키를 통해 프로퍼티 값에 접근하면 반환되는 값. 키를 통해 프로퍼티 값을 변경하면 여기에 값을 재할당. 프로퍼티가 없으면 프로퍼티를 동적 생성하고 값을 저장.[[Writable]]
- writable
- 프로퍼티 값의 갱신 가능 여부. false인 경우 읽기 전용 프로퍼티.[[Enumerable]]
- enumerable
- 열거 가능 여부. false인 경우 for...in문이나 Object.keys 메서드 등 사용 불가.[[Configurable]]
- configurable
- 재정의 가능 여부. false인 경우 해당 프로퍼티의 삭제, 프로퍼티 어트리뷰트 값의 변경이 금지됨.: 자체적으로 값([[Value]]
)을 갖지 않고, 다른 데이터 프로퍼티의 값을 읽거나 저장할 때 호출되는 접근자 합수로 구성된 프로퍼티.
[[Get]]
- get
- 접근자 프로퍼티를 통해 데이터 프로퍼티의 값을 읽을 때 호출되는 접근자 함수. (접근자 프로퍼티 키로 프로퍼티 값에 접근하면 getter 함수가 호출되고, 그 결과가 프로퍼티 값으로 반환됨)[[Set]]
- set
- 접근자 프로퍼티를 통해 데이터 프로퍼티의 값을 저장할 때 호출되는 접근자 함수. (접근자 프로퍼티 키로 프로퍼티 값을 저장하면 setter 함수가 호출되고, 그 결과가 프로퍼티 값으로 저장됨)[[Enumerable]]
[[Configurable]]
도 존재.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() {},
}
});
자바스크립트는 객체의 변경을 방지하는 다양한 메서드를 제공. (변경을 금지하는 강도가 다름)
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