[JS] 모던 자바스크립트 - 11. 프로퍼티 어트리뷰트
프로퍼티 어트리뷰트와 프로퍼티 디스크립터 객체
프로퍼티 어트리뷰트
- 프로퍼티의 상태를 나타내는 것
- 프로퍼티의 상태는 프로퍼티의 값(value), 값의 갱신 가능 여부(writable), 열거 가능 여부(enumerable), 재정의 가능 여부(configurable)을 말한다
- 자바스크립트 엔진이 프로퍼티를 생성할 때 프로퍼티 어트리뷰트를 기본값으로 자동 정의함
- 자바스크립트 엔진이 관리하는 내부슬롯이므로 직접 접근 불가능
프로퍼티 디스크립터 객체
- Object.getOwnPropertyDescriptor 메서드, Object.getOwnPropertyDescriptors 메서드로 간접적으로 프로퍼티 어트리뷰트를 확인 가능함
- 위 메서드는 프로퍼티 어트리뷰트 정보를 제공하는 프로퍼티 디스크립터 객체를 반환
- 존재하지 않는 프로퍼티나 상속받은 프로퍼티에 대한 프로퍼티 디스크립터를 요구 시 undefined 반환
const person = {
name : 'Lee'
};
// 프로퍼티 어트리뷰트 정보를 제공하는 프로퍼티 디스크립터 객체를 1개를 반환
console.log(Object.getOwnPropertyDescriptor(person,'name'));
// { value : "Lee", writable : true, enumerable : true, configurable : true }
person.age = 20;
// 모든 프로퍼티의 프로퍼티 어트리뷰트 정보를 제공하는 프로퍼티 디스크립터 객체들을 반환
console.log(Object.getOwnPropertyDescriptors(person));
/*
{
name : { value : "Lee", writable : true, enumerable : true, configurable : true },
age : { value : 20, writable : true, enumerable : true, configurable : true }
}
*/
데이터 프로퍼티와 접근자 프로퍼티
- 프로퍼티는 데이터 프로퍼티와 접근자 프로퍼티로 구분
- 데이터 프로퍼티와 접근자 프로퍼티와를 구분하는 방법은 Object.getOwnPropertyDescriptor 메서드를 사용해 반환한 프로퍼티 디스크립터 객체를 확인하여 구분
// 일반 객체의 __proto__는 접근자 프로퍼티
Object.getOwnPropertyDescriptor(Object.prototype, '__proto__');
// { get : f, set : f, enumerable: false, configurable: true}
// 함수 객체의 prototype은 데이터 프로퍼티
Object.getOwnPropertyDescriptor(function() {}, 'prototype');
// { value: {...}, writble: true, enumerable: false, configurable: false};
데이터 프로퍼티
- 키와 값으로 구성된 일반적인 프로퍼티
- 프로퍼티 생성 시 [[Value]] 값은 프로퍼티 값으로 초기화, 나머지는 true로 초기화
프로퍼티 어트리뷰트 | 프로퍼티 디스크립터 객체의 프로퍼티 | 설명 |
[[Value]] | value | - 프로퍼티 키로 프로퍼티 값에 접근하면 반환되는 값 - 프로퍼티 값 변경 시 [[Value]] 값 변경 |
[[Writable]] | writable | - 프로퍼티 값의 변경 가능 여부 - 불리언 값 - false인 경우 해당 프로퍼티의 [[Value]]의 값을 변경할 수 없는 읽기전용 프로퍼티가 됨 |
[[Enumerable]] | enumerable | - 프로퍼티의 열거 가능 여부 - 불리언 값 - false인 경우 해당 프로퍼티는 for...in문이나 Object.keys 메서드 등으로 열거X |
[[Configurable]] | configurable | - 프로퍼티의 재정의 가능 여부 - 불리언 값 - false인 경우 해당 프로퍼티의 삭제, 프로퍼티의 어트리뷰트 값 변경 금지 - writable이 true인 경우 [[Value]]의 변경과 [[Writable]]을 false로 변경은 허용 |
접근자 프로퍼티
- 자체적인 값을 갖지 않고, 접근자 함수로 구성된 프로퍼티
- 접근자 함수는 다른 데이터의 프로퍼티의 값을 읽거나 저장할 때 사용하는 함수
- getter/setter 함수라고도 부름
- 메서드 앞에 get/set을 붙여서 사용
프로퍼티 어트리뷰트 | 프로퍼티 디스크립터 객체의 프로퍼티 | 설명 |
[[Get]] | get | - 데이터 프로퍼티의 값을 읽을 때 호출되는 접근자 함수 |
[[Set]] | set | - 데이터 프로퍼티의 값을 저장할 때 호출되는 접근자 함수 |
[[Enumerable]] | enumerable | 데이터 프로퍼티의 [[Enumerable]]과 같음 |
[[Configurable]] | configurable | 데이터 프로퍼티의 [[Configurable]]과 같음 |
const person = {
// 데이터 프로퍼티
fistName : 'Heywon',
lastName : 'Lee'
// 접근자 프로퍼티
// getter 함수
get fullName() {
return `${this.firstName} ${this.lastName}`;
},
// setter 함수
set fullName(name) {
[this.firstName, this.lastName] = name.split('');
}
};
// 접근자 프로퍼티로 프로퍼티 값 저장
person.fullName = 'Hyewon Lee';
console.log(person); // {fistName: 'Hyewon', lastName: 'Lee'}
// 접근자 프로퍼티로 프로퍼티 값 참조
console.log(person.fullName); // Hyewon Lee
프로퍼티 정의
- 새로운 프로퍼티를 추가하면서 프로퍼티 어트리뷰트를 명시적으로 정의하는 것
- 기존 프로퍼티의 프로퍼티 어트리뷰트를 재정의하는 것
- Object.defineProperty 메서드로 프로퍼티 어트리뷰트 정의 가능
const person = {};
// 데이터 프로퍼티 정의
Object.defineProperty(person, 'firstName', {
value : 'Hyewon',
writable : true,
enumerable : true,
configurable : true
});
// 접근자 프로퍼티 정의
Object.defineProperty(person, 'fullName', {
// getter 함수
get() {
return `${this.firstName} ${this.lastName}`;
},
// setter 함수
set(name) {
[this.firstName, this.lastName] = name.split('');
},
enumerable : true,
configurable : true
});
- Object.defineProperty 메서드로 프로퍼티를 정의할 때 프로퍼티 디스크립터 객체의 프로퍼티를 일부 생략 할 수 있음
- 생략할 경우 기본값(value, get, set은 undefined / writable, enumerable, configurable은 false)으로 적용
- Object.defineProperties 메서드는 한번에 여러 개의 프로퍼티를 정의할 수 있음
const person = {};
Object.defineProperties(person, {
// 데이터 프로퍼티 정의
firstName: {
value : 'Hyewon',
writable : true,
enumerable : true,
configurable : true
},
lastName: {
value : 'Lee',
writable : true,
enumerable : true,
configurable : ture
},
// 접근자 프로퍼티 정의
fullName : {
// getter 함수
get() {
return `${this.firstName} ${this.lastName}`;
},
// setter 함수
set(name) {
[this.firstName, this.lastName] = name.split('');
},
enumerable : true,
configurable : true
}
});
객체 변경 금지
- 객체는 변경 가능한 값이므로 재할당 없이 직접 변경 가능
- 프로퍼티 추가 삭제 가능, 프로퍼티 값 갱신 가능, 프로퍼티 어트리뷰트 재정의 가능
- 이를 막기 위한 다양한 메서드가 있으며, 각각의 메서드는 객체의 변경을 금지하는 강도가 다름
Object.preventExtensions
- 객체 확장 금지
- 프로퍼티 추가 금지
- Object.defineProperty 메서드와 프로퍼티 동적 추가 방법 모두 금지
- 확장 가능 객체 여부는 Object.isExtensible 메서드로 확인 가능
- 추가는 금지되지만 프로퍼티 삭제는 가능
- 확장 금지 객체에 프로퍼티를 추가하면 에러 없이 무시됨, strict mode에서는 에러
Object.seal
- 객체 밀봉
- 프로퍼티 추가 및 삭제와 프로퍼티 어트리뷰트 재정의 금지
- 읽기와 쓰기만 가능
- 밀봉 객체 여부는 Object.isSealed 메서드로 확인 가능
- 프로퍼티 값 갱신은 가능 (writable : true)
- 밀봉 객체에 프로퍼티를 추가 혹은 삭제 시 에러 없이 무시됨, strict mode에서는 에러
- 프로퍼티 어트리뷰트 재정의 시 에러
Object.freeze
- 객체 동결
- 프로퍼티 추가 및 삭제와 프로퍼티 어트리뷰트 재정의 금지, 프로퍼티 값 갱신 금지
- 읽기만 가능 (writable : false, enumerable : true, configurable : false)
- 동결 객체 여부는 Object.isFrozen 메서드로 확인 가능
- 동결 객체에 프로퍼티 추가,삭제, 값 갱신 시 에러 없이 무시 됨, strict mode에서는 에러
- 프로퍼티 어트리뷰트 재정의 시 에러
불변 객체
- 위의 메서드들은 얕은 변경 방지로 직속 프로퍼티만 변경이 방지되고 중첩 객체까지는 영향을 주지 못함
- 중첩 객체까지 동결하여 변경이 불가능한 읽기 전용의 불변 객체를 구현하려면 객체를 값으로 갖는 모든 프로퍼티에 대해 재귀적으로 Object.freeze 메서드를 호출해야함
function deepFreeze(target) {
// 객체가 아니거나 이미 동결된 객체는 무시, 객체이고 동결되지 않은 객체만 동결
if (target && type of target === 'object' && !Object.isFrozen(target)) {
Object.freeze(target);
Object.keys(targert).forEach(key => deepFreeze(target[key]));
}
return target;
}
출처
- 모던 자바스크립트 Deep Dive - 자바스크립트의 기본 개념과 동작 원리