내부 슬롯과 내부 메서드는 자바스크립트 엔진의 구현 알고리즘을 설명하기 위해 사용한다.
자바스크립트 엔진에서 실제로 동작하지만 개발자가 직접 접근할 수 있도록 외부로 공개된 객체의 프로퍼티는 아니다.
원칙적으로 직접적으로 접근하거나 호출할 수 있는 방법을 제공하지 않는다.
단, 일부 간적접으로 접근할 수 있는 수단을 제공하기는 한다.
내부 슬롯의 경우, _ _proto__를 통해 간접적으로 접근할 수 있다.
const o = {};
o.[[Prototype]] // Uncaught SyntaxError: Unexpected token '['
o.__proto__ // Object.prototype
프로퍼티 어트리뷰트는 자바스크립트 엔진이 관리하는 내부 상태 값인 내부 슬롯이다.
프로퍼티 어트리뷰트에 직접 접근할 수 없지만 Object.getOwnPropertyDescriptor 메서드를 사용하여 간접적으로 확인할 수는 있다.
const person = {
name: 'Lee'
};
console.log(Object.getOwnPropertyDescriptor(person, 'name'));
// { value: "Lee", writable: true, enumerable: true, configurable: true}
// value 이외의 값은 아래에 설명 예정
만약 값이 존재하지 않는 프로퍼티나 상속받은 프로퍼티에 대한 프로퍼티 디스크립터를 요구하면 undefined가 반환된다.
const person2 = {
name: "Lee2"
}
console.log(Object.getOwnPropertyDescriptor(person2, 'name'))
// { value: "Lee", writable: true, enumerable: true, configurable: true}
console.log(Object.getOwnPropertyDescriptor(person123, name'))
// Uncaught ReferenceError: person123 is not defined
console.log(Object.getOwnPropertyDescriptor(person2, 'name2'))
// undefined
ES8에서 도입된 Object.getOwnPropertDescriptors 메서드는 모든 프로퍼티의 프로퍼티 어트리뷰트 정보를 제공하는 프로퍼티 디스크립터 객체들을 반환한다.
const person = {
name: "Lee",
isMale: true,
}
person.age = 20;
console.log(Object.getOwnPropertyDescriptors(person));
/*
{
name: { value: "Lee", writable: true, enumerable: true, configurable: true },
isMale: { value: true, wirtable: true, enumerable: true, configurable: true },
age: { value: 20, writable: true, enumerable: true, configurable: true }
}
*/
const person = {
name: 'Lee'
};
console.log(Object.getOwnPropertyDescriptor(person, 'name'));
// { value: "Lee", writable: true, enumerable: true, configurable: true}
const person2 = {
name: "Lee2",
isMale: true,
}
person2.age = 21;
console.log(Object.getOwnPropertyDescriptors(person2));
/*
{
name: { value: "Lee2", writable: true, enumerable: true, configurable: true },
isMale: { value: true, wirtable: true, enumerable: true, configurable: true },
age: { value: 21, writable: true, enumerable: true, configurable: true }
}
*/
const person = {
firstName: 'Ungmo',
lastName: 'Lee',
get fullName() {
return this.firstName + ' ' + this.lastName;
},
set fullName(name) {
this.firstName = name.split(' ')[0];
this.lastName = name.split(' ')[1];
}
};
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
let descriptor = Object.getOwnPropertyDescriptor(person, 'firstName');
console.log(descriptor);
// { value: "Heegun", writable: true, enumerable: true, configurable: true }
descriptor = Object.getOwnPropertyDescriptor(person, 'fullName');
console.log(descriptor);
// {get: f, set: f, enumerable: true, configurable: true }
const person = {};
Object.defineProperty(person, 'firstName', {
value: 'Ungmo',
writable: true,
enumerable: true,
configurable: true,
});
Object.defineProperty(person, 'lastName', {
value: 'Lee'
});
let descriptor = Object.getOwnPropertyDescriptor(person, 'firstName');
console.log('firstName', descriptor);
// firstName { value: "Ungmo", writable: true, enumerable: true, configurable: true }
descriptor = Object.getOwnPropertyDescriptor(person, 'lastName');
console.log(descriptor);
// lastName { value: "Lee", writable: false, enumerable: false, configurable: false }
console.log(Object.keys(person)); // ["firstName"]
person.lastName = 'Kim';
delete person.lastName;
descriptor = Object.getOwnPropertyDescriptor(person, 'lastName')
console.log('lastName', descriptor);
// lastName { value: "Lee", writable: false, enumerable: false, configurable: false }
Object.defineProperty(person, 'fullName', {
get() {
return this.firstName + ' ' + this.lastName;
},
set(name) {
this.firstName = name.split(' ')[0];
this.lastName = name.split(' ')[1];
},
enumerable: true,
configurable: true
});
descriptor = Object.getOwnPropertyDescriptor(person, 'fullName');
console.log('fullName', descriptor);
// fullName { get: f, set: f, enumerable: true, configurable: true }
person.fullName = 'Heegun Lee';
console.log(person); // { firstName: "Heegun", lastName: "Lee" }
Object.defineProperty 메서드로 프로퍼티를 정의할 때 일부 생략 할 수 있다.
생략했을 때의 기본값은 아래와 같다.
Object.defineProperty는 하나의 프로퍼티만 정의할 수 있다. Object.defineProperties를 사용하면 여러 개의 프로퍼티를 한 번에 정의할 수 있다.
객체의 확장을 금지한다.
확장이 금지된 객체는 프로퍼티 추가가 금지된다.
const person = [ name: 'Lee', isMale: true };
const isExtensible1 = Object.isExtensible(person) // true
Object.preventExtensions(person);
const isExtensible2 = Object.isExtensible(person) // false
person.age = 20 // 무시. strict 모드에서는 에러
console.log(person); // { name: "Lee", isMale: true }
delete person.name;
console.log(person); // { isMale: true }
Object.defineProperty(person, 'age', { value: 20 });
// TypeError: Cannot define property age, object is not extensible
const person = { name: 'Lee', isMale: true };
const isSealed1 = Object.isSealed(person) // false
Object.seal(person);
const isSealed2 = Object.isSealed(person); // true
console.log(Object.getOwnPropertyDescriptors(person)); // configurable이 false다.
/*
{
name: { value: "Lee", writable: true, enumerable: true, configurable: false }
isMale: { value: true, writable: true, enumerable: true, configurable: false }
}
*/
person.age = 20; // 무시. strict 모드에서는 에러
console.log(person); // { name: "Lee", isMale: true }
delete person.name // 무시. strict 모드에서는 에러
console.log(person); // { name: "Lee", isMale: true }
person.name = 'Kim'; // 값 갱신은 가능하다.
console.log(person); // { name: "Kim", isMale: true }
Object.defineProperty(person, 'name', { configurable: true });
// TypeError: Cannot redefine property: name
const person = { name: 'Lee' };
const isFrozen1 = Object.isFrozen(person); // false
Object.freeze(person);
const isFrozen2 = Object.isFrozen(person); // true
console.log(Object.getOwnPropertyDescriptors(person)); // writable, configurable이 false다
/*
{
name: { value: "Lee", writable: false, enumerable: true, configurable: false },
isMale: { value: true, writable: false, enumerable: true, configurable: false }
}
*/
person.age = 20; // 무시. strict 모드에서는 에러
console.log(person); // { name: "Lee", isMale: true }
delete person.name; // 무시. strict 모드에서는 에러
console.log(person); // { name: "Lee", isMale: true }
person.name = 'Kim'; // 무시. strict 모드에서는 에러
console.log(person); // { name: "Lee", isMale: true }
Object.defineProperty(person, 'name', { configurable: true });
// TypeError: Cannot redefine property: name
const person = {
name: 'Lee',
isMale: true,
address: {
city: 'Seoul'
}
};
Object.freeze(person);
const isFrozenPerson = Object.isFrozen(person); // true
const isFrozenAddress = Object.isFrozen(person.address); // false
person.address.city = 'Busan';
console.log(person); // { name: "Lee", isMale: true, address: { city: "Busan" }}
const person = {
name: 'Lee',
isMale: true,
address: {
city: 'Seoul'
}
}
function deepFreeze(target) {
if (target && typeof target === 'object' && !Object.isFrozen(target)) {
Object.freeze(target);
const keys = Object.keys(target); // ['name', 'isMale', 'address']
const keyLength = key.length; // 3
for (let i = 0; i < keyLength; i++) {
deepFreeze(target[key]));
}
}
return target;
}
deepFreeze(person);
const isFrozenPerson = Object.isFrozen(person); // true
const isFrozenAddress = Object.isFrozen(person.address); // true
person.address.city = 'Busan';
console.log(person); // { name: "Lee", isMale: true, address: { city: "Seoul" }}
참고
- 모던 자바스크립트 Deep Dive - 16장 프로퍼티 어트리뷰트