16장 프로퍼티 어트리뷰트
1. 내부 슬롯과 내부 메서드
- 내부슬롯과 내부 메서드는 자바스크립트 엔진의 구현 알고리즘을 설명하기 위해 ECMAScripts 사양에서 사용하는 의사 프로퍼티와 의사 메서드이다.
- ECMAScript는 말 그대로 Ecma라는 기관이 만든 script 언어이며, ECMA-262 표준를 따르고 있다.
- ES5, ES6, ES2020등은 ECMAScript가 배포된 버전이다.
- 자바스크립트 엔진에서 실제로 동작하지만 개발자가 직접 접근할 수 있도록 공개된 객체 프로퍼티는 아니다.
- 몇몇 프로퍼티는 간접적으로 접근할 수 있다.
2. 프로퍼티 어트리뷰트와 프로퍼티 디스크립터 객체
- 프로퍼티 어트리뷰트 : 프로퍼티의 상태를 나타낸다.
- 프로퍼티의 상태: 프로퍼티의 값, 값의 갱신 가능 여부, 열거 가능 여부, 재정의 가능 여부
- 프로퍼티를 생성할 때, 엔진은 프로퍼티 어트리뷰트를 기본값으로 자동 정의한다.
- 프로퍼티 어트리뷰트는 내부 슬롯 [[Value]],[[Writable]],[[Enumerable]],,[[Configurable]] 이다.
- 따라서 프로퍼티 어트리뷰트는 직접 접근할 수 없지만 메서드를 이용하여 간접적으로 확인할 수 있다.
const person = {
name: "Lee",
};
console.log(Object.getOwnPropertyDescriptor(person, "name"));
- Object.getOwnPropertyDescriptor 메서드는 어트리뷰트의 정보를 담아 반환하였다. 이를 프로퍼티 디스크립터 객체라 한다.
- 존재하지 않거나 상속받은 프로퍼티는 undefined를 반환한다.
- Object.getOwnPropertyDescriptors 메서드는 객체의 모든 프로퍼티의 프로퍼티 디스크립터 객체를 반환다.
3. 데이터 프로퍼티와 접근자 프로퍼티
- 프로퍼티는 데이터 프로퍼티와 접근자 프로퍼티로 나뉜다.
- 데이터 프로퍼티 : 키와 값으로 구성된 일반적인 프로퍼티.
- 접근자 프로퍼티 : 자체적으로는 값을 갖지 않고 다른 데이터 프로퍼티 값을 읽거나 저장할 때 호출되는 접근자 함수로 구성된 프로퍼티
데이터 프로퍼티
- 데이터 프로퍼티는 Value, Writable, Enumerable, Conmfigurable 네가지의 프로퍼티 어트리뷰트를 갖는다.
- Value : 프로퍼티 키를 통해 프로퍼티 값에 접근하면 반환되는 값
- Writable : 프로퍼티 값의 변경 가능 여부를 나타내며 불리언 값이다.
- Enumerable : 프로퍼티의 열거 가능 여부를 나타내며 불리언 값이다. false 이면 for in 이나 object.keys 등으로 열거될 수 없다.
- Configurable : 재정의 가능여부를 나타내며 불리언 값이다. false 이면 프로퍼티의 삭제, 값의 변경이 금지된다. 단, writable이 true 이면 value의 변경과 writable을 false로 바꾸는 것은 허용
- writable, enumerable, configurable 세가지는 default 값이 true이다.
접근자 프로퍼티
- 접근자 프로퍼티는 자체적으로 값을 갖지 않고 다른 데이터 프로퍼티의 값을 읽거나 저장할 때 사용하는 접근자 함수로 구성된 프로퍼티다.
- 접근자 프로퍼티는 Get, Set, Enumerable, Configurable 네가지의 프로퍼티 어트리뷰트를 갖는다.
- Get : 접근자 프로퍼티를 통해 데이터 프로퍼티 값을 읽을 때 호출되는 접근자 함수다.
- Set : 접근자 프로퍼티를 통해 데이터 프로퍼티의 값을 저장할 때 호출되는 접근자 함수다.
- 접근자 함수는 getter/setter 함수라고 불린다.
const person = {
firstName: "hanmin",
lastName: "Yim",
get fullName() {
return `${this.firstName} ${this.lastName}`;
},
set fullName(name) {
[this.firstName, this.lastName] = name.split(" ");
},
};
console.log(person.firstName + " " + person.lastName);
console.log(person.fullName);
person.fullName = "hanmin ss";
console.log(person.fullName);
console.log(Object.getOwnPropertyDescriptor(person, "firstName"));
console.log(Object.getOwnPropertyDescriptor(person, "fullName"));
- fullName 프로퍼티가 접근자 프로퍼티가 되고 getter와 setter를 같은 식별자로 선언해 주어야 한다.
4. 프로퍼티 정의
- 프로퍼티 정의: 새로운 프로퍼티를 추가하면서 프로퍼티 어트리뷰트를 명시적으로 정의하거나 기존 프로퍼티의 프로퍼티 어트리뷰트를 재정의 하는 것
Object.defineProperty
메서드를 사용하여 프로퍼티의 어트리뷰트를 정의할 수 있다.
const person = {};
Object.defineProperty(person, "firstName", {
value: "hanmin",
writable: true,
enumerable: true,
configurable: true,
});
Object.defineProperty(person, "lastName", {
value: "Yim",
});
let propertyAtt = Object.getOwnPropertyDescriptors(person);
console.log(propertyAtt);
console.log(Object.keys(person));
person.lastName = "kim";
delete person.lastName;
Object.defineProperty(person, "fullName", {
get() {
return `${this.firstName} ${this.lastName}`;
},
set(name) {
[this.firstName, this.lastName] = name.split(" ");
},
enumerable: true,
configurable: true,
});
console.log(person.fullName);
person.fullName = "한민 임";
console.log(person.fullName);
Object.defineProperties
를 이용하면 한번에 여러가지 프로퍼티를 정의할 수 있다.
const person = {};
Object.defineProperties(person, {
firstName: {
value: "hanmin",
writable: true,
enumerable: true,
configurable: true,
},
lastName: {
value: "Yim",
writable: true,
enumerable: true,
configurable: true,
},
fullName: {
get() {
return `${this.firstName} ${this.lastName}`;
},
set(name) {
[this.fullName, this.lastName] = name.split(" ");
},
enumerable: true,
configurable: true,
},
});
5. 객체 변경 방지
- 자바스크립트는 객체 변경을 방지하는 다양한 메서드를 제공한다.
구분 |
메서드 |
pp 추가 |
pp 삭제 |
pp 값 읽기 |
pp 값 쓰기 |
pp 어트리뷰트 재정의 |
객체 확장 금지 |
Object.preventExtensions |
X |
O |
O |
O |
O |
객체 밀봉 |
Object.seal |
X |
X |
O |
O |
X |
객체 동결 |
Object.freeze |
X |
X |
O |
X |
X |
객체 확장 금지
- 객체 확장 금지란, 객체의 프로퍼티를 추가하는 것을 금지하는 것이다.
- 프로퍼티 동적추가 방법과 object.defineProperty 방법이 모두 금지된다.
Object.preventExtensions(obj)
를 이용하여 객체 확장금지를 한다.
- 확장 가능한 객체인지는
Object.isExtensible
메서드로 확인할 수 있다.
const dog = {
name: "cream",
};
console.log(Object.isExtensible(dog));
Object.preventExtensions(dog);
console.log(Object.isExtensible(dog));
객체 밀봉
- 객체 밀봉은 프로퍼티 추가 및 삭제, 프로퍼티 어트리뷰트 재정의를 금지한다.
- 즉, 밀봉된 개체는 읽기와 쓰기만 가능하다.
Object.seal(obj)
를 이용하여 객체 밀봉을 한다.
Object.isSealed
함수로 밀봉되었는지 확인할 수 있다.
const dog = {
name: "cream",
};
console.log(Object.isSealed(dog));
Object.seal(dog);
console.log(Object.isSealed(dog));
console.log(Object.getOwnPropertyDescriptors(dog));
객체 동결
- 객체 동결은 프로퍼티 추가, 삭제, 프로퍼티 어트리뷰트 재정의금지, 값 갱신 금지를 의미한다.
- 즉, 읽기만 가능하다.
Object.freeze
를 이용하여 객체를 동결시킨다.
- 동결된 객체는
Object.isFrozen
으로 확인할 수 있다.
const dog = {
name: "cream",
};
console.log(Object.isFrozen(dog));
Object.freeze(dog);
console.log(Object.isFrozen(dog));
console.log(Object.getOwnPropertyDescriptors(dog));
불변 객체
- 동결까지가면 완벽한것 같지만 사실 중첩 객체에는 영향을 주지 못한다.
- 따라서 재귀함수를 구현하여 중첩 객체까지 변경이 불가능한 읽기 전용의 불변 객체를 만들 수 있다.
const dog = {
name: "cream",
age: {
age: 30,
},
};
Object.freeze(dog);
console.log(Object.isFrozen(dog));
console.log(Object.isFrozen(dog.age));
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 dog = {
name: "cream",
age: {
age: 30,
},
};
deepFreeze(dog);
console.log(Object.isFrozen(dog));
console.log(Object.isFrozen(dog.age));