내부 슬롯, 내부 메서드는 자바스크립트 엔진의 구현 알고리즘을 설명하기 위해 ECMAScript 사양에서 사용하는 의사 프로퍼티와 의사 메서드
직접 접근할 수 있도록 외부로 공개된 객체의 프로퍼티는 아님
모든 객체는 [[prototype]]
라는 내부 슬롯을 갖지만 접근할 수 없음
__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'));
// {vale: "Lee", writable: true, enumerable: true, configurable: true}
- 데이터 프로퍼티
: 키와 값으로 구성된 일반적인 프로퍼티
- 접근자 프로퍼티
: 자체적으로 값을 가지 않고 다른 데이터 프로퍼티의 값을 읽거나 저장할 때 호출되는 접근자 함수로 구성된 프로퍼티
프로퍼티 어트리뷰트 | 프로퍼티 디스크립터 객체의 프로퍼티 |
---|---|
[[Value]] | value |
[[Writable]] | writable |
[[Enumerable]] | enumerable |
[[Configurable]] | configurable |
[[Value]] - value
- 프로퍼티 키를 통해 프로퍼티 값에 접근하면 반환되는 값
- 프로퍼티 키를 통해 프로퍼티 값을 변경하면 [[Value]]에 값을 재할당
- 이때 프로퍼티가 없으면 프로퍼티를 동적 생성하고 생성된 프로퍼티의 [[Value]] 값을 저장
[[Writable]] - writable
const person = {
name: 'Lee'
};
console.log(Object.getOwnPropertyDescriptor(person, 'name'));
// {vale: "Lee", writable: true, enumerable: true, configurable: true}
const person = {
name: 'Lee'
};
person.age = 20;
console.log(Object.getOwnPropertyDescriptor(person));
/*
name: {vale: "Lee", writable: true, enumerable: true, configurable: true},
age: {vale: 20, writable: true, enumerable: true, configurable: true}
*/
프로퍼티 어트리뷰트 | 프로퍼티 디스크립터 객체의 프로퍼티 |
---|---|
[[Get]] | get |
[[Set]] | set |
[[Enumerable]] | enumerable |
[[Configurable]] | configurable |
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
// 접근자 프로퍼티 fullName에 값을 저장하면 setter 함수 호출
person.fullName = 'Heegun Lee';
console.log(person); // {firstName: "Heegun", lastName: "Lee"}
// 접근자 프로퍼티 fullName에 접근하면 getter 함수 호출
console.log(person.fullName); // Heegun Lee
// firstName은 데이터 프로퍼티
let descriptor = Object.getOwnPropertyDescriptor(person, 'firstName');
console.log(descriptor); // {value: "Heegun", writable: true, enumerable: true, configurable: true}
// fullName은 접근자 프로퍼티
descriptor = Object.getOwnPropertyDescriptor(person, 'fullName');
console.log(descriptor); // {get: f, set: f, enumerable: true, configurable: true}
프로퍼티 정의란❓
새로운 프로퍼티를 추가하면서 프로퍼티 어트리뷰트를 명시적으로 정의하거나, 기존 프로퍼티의 프로퍼티 어트리뷰트를 재정의하는 것
Object.defineProperty
메서드를 사용하여 프로퍼티의 어트리뷰트 정의const person = {};
Object.defineProperty(person, 'firstName', {
value: 'Ungmo',
writable: true,
enumerable: true,
configurable: true
});
Object.defineProperty(person, 'lastName', {
vale: 'Lee'
});
let descriptor = Object.getOwnPropertyDescriptor(person, 'firstName');
console.log('firstName', descriptor);
descriptor = Object.getOwnPropertyDescriptor(person, 'lastName');
console.log('lastName', descriptor);
console.log(Object.keys(person));
person.lastName = 'Kim';
delete person.lastName;
descriptor = Object.getOwnPropertyDescriptor(person, 'lastName');
console.log('lastName', descriptor);
Object.defineProperty(person, 'fullName', {
get() {
return `${this.firstName} ${this.lastName}`;
},
set(name) {
[this.firstName, this.lastName] = name.split(' ');
},
enumerable: true,
configurable: true
});
descriptor = Object.getOwnPropertyDescriptor(person, 'fullName');
console.log('fullName', descriptor);
person.fullName = "Heegun Lee";
console.log(person);
프로퍼티 디스크립터 객체의 프로퍼티 | 대응하는 프로퍼티 어트리뷰트 | 생략했을 때의 기본 값 |
---|---|---|
value | [[Value]] | undefined |
get | [[Get]] | undefined |
set | [[Set]] | undefined |
writable | [[Writable]] | false |
enumerable | [[Enumerable]] | false |
configurable | [[Configurable]] | false |
Object.defineProperty
메서드는 한번에 하나의 프로퍼티만 정의
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: {
get() {
return `${this.firstName} ${this.lastName}`;
},
set(name) {
[this.firstName, this.lastName] = name.split(' ');
},
enumerable: true,
configurable: true
}
});
person.fullName = "Heegun Lee";
console.log(person);
객체는 변경 가능한 값이므로 재할당 없이 직접 변경 가능
즉, 프로퍼티를 추가, 삭제, 값 갱신 가능
Object.defineProperty
, Object.defineProperties
메서드를 사용하여 프로퍼티 어트리뷰트 재정의 가능
자바스크립트는 객체의 변경을 방지하는 다양한 메서드 제공
구분 | 메서드 | 추가 | 삭제 | 값 읽기 | 값 쓰기 | 어트리뷰트 재정의 |
---|---|---|---|---|---|---|
객체 확장 금지 | Object.preventExtensions | X | O | O | O | O |
객체 밀봉 | Object.seal | X | X | O | O | X |
객체 동결 | Object.freeze | X | X | O | X | X |
Object.preventExtensions
메서드는 객체의 확장을 금지
→ 확장이 금지된 객체는 프로퍼티 추가가 금지됨
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); // {}
// 프로퍼티 정의에 의한 프로퍼티 추가도 금지됨
Object.defineProperty(person, 'age', { value: 20 }); // TypeError: Cannot define property age, object is not extensible
Object.seal
메서드는 객체를 밀봉
객체 밀봉 : 프로퍼티 추가 및 삭제와 프로퍼티 어트리뷰트 재정의 금지
→ 밀봉된 객체는 읽기와 쓰기만 가능
const person = { name: 'Lee' };
console.log(Object.isSealed(person)); // false
// 객체 밀봉 -> 프로퍼티 추가, 삭제, 재정의 금지
Object.seal(person);
console.log(Object.isSealed(person)); // true
console.log(Object.getOwnPropertyDescriptors(person));
person.age = 20;
console.log(person);
delete person.name;
console.log(person);
person.name = 'Kim';
console.log(person);
Object.defineProperty(person, 'name', { configurable: true });
Object.freeze
메서드는 객체를 동결
객체 동결 : 프로퍼티 추가 및 삭제와 프로퍼티 어트리뷰트 재정의 금지, 프로퍼티 값 갱신 금지
→ 동결된 객체는 읽기만 가능
const person = { name: 'Lee' };
console.log(Object.isFrozen(person));
Object.freeze(person);
console.log(Object.isFrozen(person));
console.log(Object.getOwnPropertyDescriptors(person));
person.age = 20;
console.log(person);
delete person.name;
console.log(person);
person.name = 'Kim';
console.log(person);
Object.defineProperty(person, 'name', { configurable: true });
객체의 중첩 객체까지 동결하여 변경이 불가능한 읽기 전용의 불변 객체를 구현하려면 객체를 값으로 갖는 모든 프로퍼티에 대해 재귀적으로
Object.freeze
메서드를 호출해야 함
const person = {
name: 'Lee',
address: { city: 'Seoul' }
};
Object.freeze(person);
console.log(Object.isFrozen(person));
console.log(Object.isFrozen(person.address));
person.address.city = 'Busan';
console.log(person);
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));
console.log(Object.isFrozen(person.address));
person.address.city = 'Busan';
console.log(person);