[모던자바스크립트] 16.프로퍼티 어트리뷰트
1. 내부 슬롯과 내부 메서드
내부 슬롯과 내부 메서드는 자바스크립트 엔진의 구현 알고리즘을 설명하기 위해 ECMAScript 사양에서 사용하는 의사 프로퍼티와 의사 메서드다.
이중대활호([[..]])로 감싼 이름들을 말하며, 직접적 접근이나 호출이 불가능하다.
(내부 슬롯의 경우 던더프로토(__proto__)를 통해 간접적으로 접근 할 수 있다.
2. 프로퍼티 어트리뷰트 프로퍼티 디스크립터 객체
자바스크립트 엔진은 프로퍼티를 생성할 때 프로퍼티 어트리뷰트(프로퍼티의 상태를 나타냄)를 기본값으로 자동정의한다.
프로퍼티 어트리뷰트(프로퍼티의 상태) : 프로퍼티의 값([[Value]]), 값의 갱신가능 여부([[Writable]]), 열거 가능여부([[Enumerable]]), 재정의 가능 여부([[Configurable]])이다.
프로퍼티 어트리뷰트에 직접 접근 할 수는 없지만 Object.getOwnPropertyDescriptor 메서드를 사용하여 간접적으로 확인 할 수 있다.
Object.getOwnPropertyDescriptor : 프로퍼티 디스크립터 객체를 반환한다.
3. 데이터 프로퍼티와 접근자 프로퍼티
3-1. 데이터 프로퍼티
-> 키와 값으로 구성된 일반적인 프로퍼티
[[Value]], [[Writable]], [[Enumerable]], [[Configurable]]
3-2. 접근자 프로퍼티
-> 자체적으로 값을 갖지 않고 다른 데이터 프로퍼티의 값을 읽거나 저장할때 호출되는 접근자 함수로 구성된 프로퍼티이다.
[[Get]], [[Set]], [[Enumerable]], [[Configurable]].
접근자 함수는 getter/setter 함수라고도 부른다.
접근자 프로퍼티는 자체적으로 값([[Value]])를 가지지 않으며 다만 데이터 프로퍼티의 값을 읽거나 저장할 때 관여할 뿐이다.
const person = {
firstName: 'Subin',
lastName: 'Lee',
get fullName() {
return `${this.firstName} ${this.lastName}`;
}
set fullName() {
[this.firstName, this.lastName] = name.split(' ');
}
}
// setter 호출됨.
person.fullName = 'Subin Lee';
// getter 호출됨.
console.log(person.fullName);
Object.get
✅접근자 프로퍼티와 데이터 프로퍼티를 구별하는 방법
// 일반 객체의 __proto__ 는 접근자 프로퍼티이다.
Object.getOwnPropertyDescriptor(Object.prototype, '__proto__');
// {get: f, set: f, emi,erab;e: false, configuralbe: true}
// 함수 객체의 prototype은 데이터 프로퍼티이다.
Object.getOwnPropertyDescriptor(function() {}, 'prototype');
// {value: {...}, writable: true, enumerable: false, configurable: false}
4.프로퍼티 정의
프로퍼티 정의 : 새로운 프로퍼티를 추가하면서 프로퍼티 어트리뷰트를 명시적으로 정의하거나 기존 프로퍼티와 프로퍼티 어트리뷰트를 재정의하는 것.
프로퍼티 추가 : 프로퍼티를 동적으로 추가하거나 Object.defineProperty 메서드로 추가
const person = {};
// doc
defineProperty<T>(o: T, p: PropertyKey, attributes: PropertyDescriptor & ThisType<any>): T;
// 1. 데이터 프로퍼티 정의
Object.defineProperty(person, 'firstName', {
value: 'first',
writable: true, //false일때, [[Value]]의 값을 변경할 수 없음
enumerable: true, // false일때, for...in, Object.keys 등으로 열거할 수 없음
configurable: true // false일때, 해당 프로퍼티 재정의, 삭제 할 수 없음.
});
// 디스크립터 객체의 프로퍼티를 누락시키면 undefined, false가 기본값
Object.defineProperty(person, 'lastName', {
value: 'last',
});
// 2. 접근자 프로퍼티 정의
Object.defineProperty(person, 'fullName', {
get() {
return `${this.firstName} ${this.lastName}`;
},
set(name) {
[this.firstName, this.lastName] = name.split(' ');
},
enumerable: true,
configurable: true
});
5. 객체 변경 방지.
5-1. 객체 확장 금지
- Object.preventExtension : 객체의 확장 금지. 확장 금지 되면 프로퍼티 추가 금지됨
- Object.isExtensible : 확장 가능한 객체인지 확인
5-2. 객체 밀봉
- Object.seal : 객체 밀봉. 프로퍼티 추가, 삭제, 프로퍼티 어트리뷰트 재정의 금지. 읽기와 쓰기만 가능
- Object.isSealed : 밀봉된 객체인지 여부 확인
5-3. 객체 동결
- Object.freeze : 객체 동결. 프로퍼티 추가, 삭제, 프로퍼티 어트리뷰트 재정의 금지. 프로퍼티 값 갱신 금지. 읽기만 가능
얕은 변경 방지로 직속 프로퍼티만 변경 방지되며 중첩객체는 동결되지 않는다. - Object.isFrozen : 동결된 객체인지 여부 확인
5-4. 불변 객체
객체의 중첩 객체까지 동결해 변경 불가능한 읽기 전용 불변 객체로 구현시 객체를 값으로 갖는 모든 프로퍼티에 대해 재귀적으로 Object.freeze 메서드를 호출해야한다.
// Object.freeze로 객체를 동결하여도 중첩 객체까지는 동결할 수 없다.
const person = {
name: 'Lee',
address: { city: 'Seoul' }
}
Object.freeze(person);
Object.isFrozen(person); // true
Object.isFrozen(person.address); // false
person.address.city = 'Busan';
// 중첩 객체까지 동결하여 변경이 불가능한 읽기 전용의 불변 객체를 구현하려면
// 객체를 값으로 갖는 모든 프로퍼티에 대해 재귀적으로 Object.freeze 를 호출해야 한다.
function deepfreeze(target) {
if (target && typeof target === 'object' && !Object.isFrozen(target)) {
Object.freeze(target);
Object.ketys(target).forEach(key => deepFreeze(target[key]));
}
return target;
}