내부 슬롯과 내부 메서드
- 내부 슬롯과 내부 메서드는 ECMA Script 사양에 정의된 대로 구현되어 자바스크립트 엔진에서 실제로 동작
- 개발자가 직접 접근할 수 있도록 외부로 공개된 객체의 프로퍼티는 아님
- 내부 슬롯과 내부 메서드는 자바스크립트 엔진의 내부 로직
- 일부 내부 슬롯과 내부 메서드에 한하여 간접적으로 접근할 수 있는 수단 제공
- [[prototype]] 내부 슬롯의 경우 __proto__를 통해 간접적으로 접근 가능
프로퍼티 어트리뷰트와 프로퍼티 디스크립터 객체
자바스크립트 엔진은 프로퍼티를 생성할 때 프로퍼티의 상태를 나타내는 프로퍼티 어트리뷰트를 기본 값으로 자동 정의
프로퍼티 상태 : 프로퍼티의 값, 값의 갱신 가능 여부, 열거 가능 여부, 정의 가능 여부를 말함
프로퍼티 어트리뷰트 : 자바스크립트 엔진이 관리하는 내부 상태 값인 내부 슬롯
(Object.getOwnPropertyDescriptor 메서드를 사용하여 간접적으로 확인 가능)
프로퍼티 디스크립터 객체 : Object.getOwnPropertyDescriptor 메서드가 제공하는 프로퍼티 어트리뷰트 정보
const person = {
name : "Echichi"
}
// 프로퍼티 동적 생성
person.age = 100;
// 모든 프로퍼티의 프로퍼티 어트리뷰트 정보를 제공하는 프로퍼티 디스크립터 객체 반환
console.log(Object.getOwnPropertyDescriptors(person));
/*
{
age : {value: 100, writable: true, enumerable: true, configurable: true}
name : {value: 'Echichi', writable: true, enumerable: true, configurable: true}
}
*/
데이터 프로퍼티와 접근지 프로퍼티
- 데이터 프로퍼티 : 키와 값으로 구성된 일반적인 프로퍼티. 지금까지 살펴본 모든 프로퍼티는 데이터 프로퍼티
- 접근자 프로퍼티 : 자체적으로는 값을 갖지 않고 다른 데이터 프로퍼티의 값을 읽거나 저장할 때 호출되는 접근자 함수로 구성된 프로퍼티
데이터 프로퍼티
자바스크립트 엔진이 프로퍼티를 생성할 때 기본값으로 자동 정의
| 프로퍼티 어트리뷰트 | 프로퍼티 디스크립터 객체의 프로퍼티 | 설명 |
| [[Value]] | value | 프로퍼티 키를 통해 프로퍼티 값에 접근하면 반환되는 값 |
| [[Writable]] | writable | 프로퍼티 값의 변경 가능 여부를 나타내며 불리언 값을 갖는다 |
| [[Enumerable]] | enumerable | 프로퍼티의 열거 가능 여부를 나타내며 불리언 값을 갖는다 |
| [[Configurable]] | configurable | 프로퍼티의 재정의 기능 여부를 나타내며 불리언 값을 갖는다 |
프로퍼티 생성 시 [[value]] 값은 프로퍼티 값으로 초기화, [[Writable]], [[Enumerable]], [[Configurable]] 은 true로 초기화
접근자 프로퍼티
자체적으로 값을 갖지 않고 다른 데이터 프로퍼티의 값을 읽거나 저장할 때 사용하는 접근자 함수로 구성된 프로퍼티
| 프로퍼티 어트리뷰트 | 프로퍼티 디스크립터 객체의 프로퍼티 | 설명 |
| [[Get]] | get | 데이터 프로퍼티의 값을 읽었을 때 호출되는 접근자 함수 (getter) |
| [[Set]] | set | 데이터 프로퍼티 값을 저장할 때 호출되는 접근자 함수 (setter) |
| [[Enumerable]] | enumerable | 데이터 프로퍼티의 [[Enumerable]]과 같음 |
| [[Configurable]] | configurable | 데이터 프로퍼티의 [[Configurable]]과 같음 |
const person = {
firstName: "chichi",
lastName: "E",
get fullName() {
return `${this.firstName} ${this.lastName}`;
},
set fullName(name) {
[this.firstName, this.lastName] = name.split(" ");
},
};
// 데이터 프로퍼티를 통한 프로퍼티 값의 참조
console.log(person.firstName + " " + person.lastName); // chichi E
// 접근자 프로퍼티를 통한 프로퍼티 값의 저장 setter 함수 호출
person.fullName = "chichi E";
console.log(person); // { firstName: 'chichi', lastName: 'E' }
// 접근자 프로퍼티를 통한 프로퍼티 값의 참조 getter 함수 호출
console.log(person.fullName); // chichi E
// firstName 데이터 프로퍼티
// 데이터 프로퍼티는 [[Value]], [[Writable]], [[Enumerable]], [[Configurable]] 프로퍼티 어트리뷰트를 가짐
let descriptor = Object.getOwnPropertyDescriptor(person, "firstName");
console.log(descriptor);
/*
{
value: 'chichi',
writable: true,
enumerable: true,
configurable: true
}
*/
// fullName은 접근자 프로퍼티
// 접근자 프로퍼티는 [[Get]], [[Set]], [[Enumerable]], [[Configurable]] 프로퍼티 어트리뷰트를 가짐
descriptor = Object.getOwnPropertyDescriptor(person, "fullName");
console.log(descriptor);
/*
{
get: [Function: get fullName],
set: [Function: set fullName],
enumerable: true,
configurable: true
}
*/
접근자 프로퍼티 fullName 으로 프로퍼티 값에 접근하면 내부적으로 [[Get]] 내부 메서드가 호출되어 다음과 같이 동작
- 프로퍼티 키가 유효한지 확인한다. 프로퍼티 키는 문자열 또는 심벌이여야 한다. 프로퍼티 키 'fullName'은 문자열이므로 유효한 프로퍼티 키이다.
- 프로토타입 체인에서 프로퍼티를 검색한다. person객체에 fullName 프로퍼티가 존재한다.
- 검색된 fullName프로퍼티가 데이터 프로퍼티인지 접근자 프로퍼티인지 확인한다. fullName 프로퍼티는 접근자 프로퍼티다.
- 접근자 프로퍼티 fullName프로퍼티 어트리뷰트 [[Get]]의 값은 Object.getOwnPropertyDescriptor 메서드가 반환하는 프로퍼티 디스크립터 객체의 get 프로퍼티 값과 같다.
프로퍼티 정의
프로퍼티 정의 : 프로퍼티를 추가하면서 프로퍼티 어트리뷰트를 명시적으로 정의하거나 기존 프로퍼티의 프로퍼티 어트리뷰트를 재정의하는 것 (Object.defineProperty 메서드를 사용하면 프로퍼티의 어트리뷰트 정의 가능)
// 객체에 프로퍼티 하나 정의
Object.defineProperty(객체, 추가할 프로퍼티, {
value: 값,
writable: boolean,
enumerable: boolean,
configurable: boolean
})
// 객체에 프로퍼티 여러 개 정의
Object.defineProperties(객체, {
데이터 프로퍼티 1: {
value: 값,
writable: boolean,
enumerable: boolean,
configurable: boolean
},
데이터 프로퍼티 2: {
value: 값,
writable: boolean,
enumerable: boolean,
configurable: boolean
},
...
접근자 프로퍼티 1: {
get() { ... },
set() { ... },
enumerable: boolean,
configurable: boolean
},
접근자 프로퍼티 2: {
get() { ... },
set() { ... },
enumerable: boolean,
configurable: boolean
}
})
객체 변경 방지
객체는 변경 가능한 값으로 재할당 없이 직접 변경할 수 있기 때문에 무분별한 객체 변경을 방지하는 다양한 메서드 제공
구분메서드프로퍼티 추가프로퍼티 삭제프로퍼티 값 읽기프로퍼티 값 쓰기프로퍼티 어트리뷰트 재정의
| 구분 | 메서드 | 프로퍼티 추가 | 프로퍼티 삭제 | 프로퍼티 값 읽기 | 프로퍼티 값 쓰기 | 프로퍼티 어트리뷰트 재정의 |
| 객체 확장 금지 | Object.preventExtensions | X | O | O | O | O |
| 객체 밀봉 | Object.seal | X | X | O | O | X |
| 객체 동결 | Object.freeze | X | X | O | X | X |
1. 객체 확장 금지
확장이 금지된 객체는 프로퍼티 추가 금지 (동적 추가, Object.definedProperty 금지)
Object.preventExtensions(객체);
// 확장 가능한 객체인지 판단 메서드
Object.isExtensible(객체);
2. 객체 밀봉
밀봉된 객체는 읽기와 쓰기만 가능
Object.seal(객체);
// 밀봉된 객체인지 판단 메서드
Object.isSealed(객체);
3. 객체 동결
동결된 객체는 읽기만 가능
Object.freeze(객체);
// 동결된 객체인지 판단 메서드
Object.isFrozen(객체);
4. 불변 객체
변경 방지 메서드들은 얕은 변경 방지로 직속 프로퍼티만 변경이 방지되고 중첩 객체까지는 영향을 주지 못함
→ 객체의 중첩 객체까지 동결하여 변경이 불가능한 읽기 전용의 불변 객체를 구현하려면 객체를 값으로 갖는 모든 프로퍼티에 대해 재귀적으로 Object.freeze 메서드 호출
function deepFreezen(target) {
if (target && typeof target === "object" && !Object.isFrozen(target)) {
// 동결되지 않은 객체 동결
Object.freeze(target);
// 모든 프로퍼티를 순회하며 재귀적으로 동결
// Object.keys 메서드 : 객체 자신의 열거 가능한 프로퍼티 키를 배열로 반환
// forEach 로 배열을 순회하며 배열의 각 요소에 대하여 콜백 함수 실행
Object.keys(target).forEach((key) => deepFreezen(target[key]));
}
}
const person = {
name: "Echichi",
age: 100,
address: {
city: "Daejeon",
},
};
// 깊은 객체 동결
deepFreezen(person);
console.log(Object.isFrozen(person)); // true
console.log(Object.isFrozen(person.address)); // true
person.address.city = "Busan";
console.log(person); // { name: 'Echichi', age: 100, address: { city: 'Daejeon' } }
'Javascript' 카테고리의 다른 글
| [모던 자바스크립트 Deep Dive] 15. 함수와 일급 객체 (0) | 2023.11.11 |
|---|---|
| [모던 자바스크립트 Deep Dive] 14. 생성자 함수에 의한 객체 생성 (0) | 2023.11.10 |
| [모던 자바스크립트 Deep Dive] 12. let, const 키워드와 블록 레벨 스코프 (0) | 2023.11.09 |
| [모던 자바스크립트 Deep Dive] 11. 전역 변수의 문제점 (0) | 2023.11.08 |
| [모던 자바스크립트 Deep Dive] 10. 스코프 (0) | 2023.11.08 |