모던 자바 스크립트 Deep Dive 를 읽고 정리한 글 입니다.
16. 1 내부 슬롯과 내부 메서드
자바스크립트 엔진의 구현 알고리즘을 설명하기 위해 EXMAScript 사양에서 사용하는 의사 프로퍼티와 의사 메소드
자바스크립트 엔진의 내부 로직이므로 일부를 제외하면 직접적으로 접근, 호출 불가능
const O = {};
o.[[Prototype]] // => Uncaught SyntaxError: Unexpexted token '['
o.__proto__ // => Object.prototype
16. 2 프로퍼티 어트리뷰트와 프로퍼티 디스크립터 객체
자바스크립트 엔진은 프로퍼티를 생성할 때 프로퍼티의 상태를 나타내는 프로퍼티 어트리뷰트를 기본값으로 자동 정의
프로퍼티의 상태
프로퍼티의 값, 값의 갱신 가능 여부, 열거 가능 여부, 재정의 가능 여부
프로퍼티 어트리뷰트
자바스크립트 엔진이 관리하는 내부 상태 값인 내부 슬롯
직접 접근 할 수는 없지만 Object.getOwnPropertyDescriptoer 메서드를 사용하여 간접적으로 확인 가능
[[Value]]
[[Writable]]
[[Enumerable]]
[[Configurable]]
const person = {
name: 'Lee'
};
// 프로퍼티 어트리뷰트 정보를 제공하는 프로퍼티 디스크립터 객체를 반환
console.log(Object.getOunPropertyDescriptor(person, 'name'));
// => {value: "Lee", writable: true, enumerable: true, configurable: ture}
프로퍼티 디스크립터 객체
프로퍼티 어트리 뷰트 정보 제공
Object.getOwnPropertyDescriptoer ( 객체의 참조 전달, 프로퍼티 키 문자열로 전달)
=> 프로퍼티 디스크립터 객체 반환
=> 존재하지 않는 프로퍼티, 상속받은 프로퍼티에 대한 프로퍼티 디스크립터 요구시 undefined 반환
16. 3 데이터 프로퍼티와 접근자 프로퍼티
데이터 프로퍼티
키와 값으로 구성된 일반적 프로퍼티
프로퍼티 어트리뷰트 |
프로퍼티 디스크립터 객체의 프로퍼티 |
설명 |
[[Value]] | value | 프로퍼티 키를 통해 프로퍼티 값에 접근하면 반환되는 값 프로퍼티 키를 통해 프로퍼티 값을 변경 -> [[Value]]에 값 재할당 -> 프로퍼티가 없다면 프로퍼티 동적 생성하고 생성된 프로퍼티의 [[Value]]에 값 저장 |
[[Writable]] | Writable | 프로퍼티 값의 변경 가능 여부 불리언 값 [[Writable]]의 값이 false인 경우 -> 해당 프로퍼티의 [[Value]]의 값을 변경 할 수 없는 읽기 전용 프로퍼티가 됌 |
[[Eumerable]] | Eumerable | 프로퍼티 열거 가능 여부 불리언 값 [[Eumerable]] 값이 false 인 경우 -> 해당 프로퍼티는 for...in문, Object.key 메서드 등으로 열거 불가 |
[[Configurable]] | Configurable | 프로퍼티 재정의 가능 여부 불리언 값 [[Configurable]] 값이 false 인 경우 -> 해당 프로퍼티의 삭제, 프로퍼티 어트리뷰트 값의 변경 금지 -> 단 [[Writable]]이 true인 경우 [[Value]] 의 변경과 [[Writable]]를 false로 전환 허용 |
프로퍼티가 생성될 때 / 프로퍼티 동적 추가
[[Vale]]의 값은 프로퍼티 값으로 초기화
[[Writable]], [[Eumerable]], [[Configurable]] 의 값은 true로 초기화
const person = {
name = 'Lee'
};
// 프로퍼티 동적 생성
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}
접근자 프로퍼티
자체적으로 값을 갖지 않음.
다른 데이터 프로퍼티의 값을 읽거나 저장할 때 호출되는 접근자 함수로 구성된 프로퍼티
프로퍼티 어트리뷰트 |
프로퍼티 디스크립터 객체의 프로퍼티 |
설명 |
[[Get]] | get | 접근자 프로퍼티를 통해 데이터 프로퍼티의 값을 읽을 때 호출되는 접근자 함수 |
[[Set]] | set | 접근자 프로퍼티를 통해 데이터 프로퍼티의 값을 저장 할 때 호출되는 접근자 함수 접근자 프로퍼티 키로 프로퍼티 값 저장 => 프로퍼티 어트리뷰트 [[Set]] 값 = setter 함수 호출 => 결과 프로퍼티 값으로 저장 |
[[Eumerable]] | enumerable | 데이터 프로퍼티와 동일 |
[[Configurable]] | configurable | 데이터 프로퍼티와 동일 |
접근자 함수
= getter / setter 함수
접근자 프로퍼티는 모두 정의할 수도 하나만 정의할 수도 있음
const person = {
// 데이터 프로퍼티
firstName: 'Ungmo',
lastName: 'Lee',
// fullName은 접근자 함수로 구성된 접근자 프로퍼티
// getter 함수
get fullName() {
return '${this.firstName} ${this.lastName}';
},
// setter 함수
set fullName(name) {
// 배열 디스터럭처링 할당: " 31.1 배열 디스트럭처링 할당" 참고
[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 = Objedt.getOwnPropertyDescriptor(person, 'firstName');
console.log(descriptor);
// => {value: "Heegun", writable: true, enumerable: true. configurable: true}
// fullName은 접근자 프로퍼티
descriptor = Objedt.getOwnPropertyDescriptor(person, 'fullName');
console.log(descriptor);
// => {get: f. set: f, enumerable: true. configurable: true}
접근자 프로퍼티 값으로 fullName으로 프로퍼티 값에 접근
1. 프로퍼티 유효한지 확인 (프로퍼티 키는 문자열, 심벌이어야 한다)
2. 프로토타입 체인에서 프로퍼티 검색
3. 검색된 프로퍼티가 데이터 프로퍼티 / 접근자 프로퍼티인지 확인
4. 프로퍼티 어트리뷰트 [[Get]]의 값은 Objedt.getOwnPropertyDescriptor 메서드가 반환하는 프로퍼티 디스크립터 객체의 get 프로퍼티 값과 동일
프로토타입
어떤 객체의 상위 객체의 역할을 하는 객체
하위 객체에게 자신의 프로퍼티와 메서드를 상속
상속받은 하위 객체는 자유롭게 활용 가능
프로토타입 체인
프로토타입이 단방향 링크드 리스트 형태로 연결되어 있는 상속 구조
접근자 프로퍼티와 데이터 프로퍼티 구별
프로퍼티 디스크립터 객체가 다름
// 일반 객체의 __proto__는 접근자 프로퍼티
Objedt.getOwnPropertyDescriptor(Object.prototype, '__proto__');
// => {get: f. set: f, enumerable: false, configurable: true}
// 함수 객체의 __prototype__은 데이터 프로퍼티
Objedt.getOwnPropertyDescriptor(function() {}, 'prototype');
// => {value: {...}, writable: true, enmerable: false, configurable: false}
16. 4 프로퍼티의 정의
새로운 프로퍼티를 추가하면서 프로퍼티 어트리뷰트를 명시적으로 정의
기존 프로퍼티의 프로퍼티 어트리뷰트 재정의
Object.defineProperty 메서드
인수 : 객체의 참조, 데이터 프로퍼티의 키인 문자열, 프로퍼티 디스크립터 객체 전달
프로퍼티 디스크립터 객체의 프로퍼티 | 대응하는 프로퍼티 어트리뷰트 | 생략했을 때의 기본값 |
value | [[Value]] | undefined |
get | [[Get]] | undefined |
set | [[Set]] | undefined |
writable | [[Writable]] | false |
enumerable | [[Enumerable]] | false |
configurable | [[Configurable]] | false |
const person = {};
// 데이터 프로퍼티 정의
Object.defineProperty(person, 'firstName', {
Value: 'Ungmo',
writable: true,
enumerable: true,
configurable: true
});
Object.defineProperty(person, 'lastName', {
value: 'Lee'
});
Object.defineProperty 메서드 : 한번에 하나의 프로퍼티만 정의
Object.defineProperties 메서드 : 여러 개의 프로퍼티를 한 번에 정의
16. 5 객체 변경 방지
구분 | 메서드 | 프로퍼티 추가 |
프로퍼티 삭제 |
프로퍼티 값 읽기 |
프로퍼티 값 쓰기 |
프로퍼티 어트리뷰트 재정의 |
객체 확장 금지 | Object.preventExtensions | X | O | O | O | O |
객체 밀봉 | Object.seal | X | X | O | O | X |
객체 동결 | Object.freeze | X | X | O | X | X |
객체 확장 금지
프로퍼티 추가를 금지
확장이 금지된 객체는 프로퍼티 추가 금지
Object.preventExtensions 메서드는 객체 확장 금지
확장이 가능 여부 확인 : Object.isExtensible 메서드
const person = { name: 'Lee'};
// person객체는 확장이 금지된 객체가 아님
console.log(Object.isExtensible(person)); // => ture
// person객체는 확장이 금지, 프로퍼티 추가 금지
Object.preventExtensions(person);
// 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.isSealed 메서드
const person = { name: 'Lee'};
// person객체는 밀봉된 객체가 아님
console.log(Object.isSealed(person)); // => false
// person객체는 밀봉하여 프로퍼티 추가, 삭제, 재정의 금지
Object.seal(person);
// person객체는 밀봉된 객체
console.log(Object.isSealed(person)); // => true
// 프로퍼티 추가 금지
person.age = 20 // => 무시
console.log(person); // => {name: "Lee"}
// 프로퍼티 삭제 금지
delete person.name;
console.log(person); // => {name: "Lee"}
// 프로퍼티 값 갱신 가능
person.name = 'Kim';
console.log(person); // => {name: "Kim"}
// 프로퍼티 어트리뷰트 재정의 금지
Object.defineProperty(person, 'name', { configurable: true });
// => TypeError: Cannot redefine property: name
객체 동결
프로퍼티 추가 및 삭제와 프로퍼티 어트리뷰트 재정의 금지, 프로퍼티 값, 갱신 금지
동결된 객체는 읽기만 가능
동결된 객체 여부 확인 : Object.isFrozen 메서드
const person = { name: 'Lee'};
// person객체는 동결된 객체가 아님
console.log(Object.isFrozen(person)); // => false
// person객체는 동결하여 프로퍼티 추가, 삭제, 재정의, 쓰기 금지
Object.Frozen(person);
// person객체는 동결된 객체
console.log(Object.isFrozen(person)); // => true
// 프로퍼티 추가 금지
person.age = 20 // => 무시
console.log(person); // => {name: "Lee"}
// 프로퍼티 삭제 금지
delete person.name; // => 무시
console.log(person); // => {name: "Lee"}
// 프로퍼티 값 갱신 금지
person.name = 'Kim'; // => 무시
console.log(person); // => {name: "Lee" }
// 프로퍼티 어트리뷰트 재정의 금지
Object.defineProperty(person, 'name', { configurable: true });
// => TypeError: Cannot redefine property: name
불변 객체
객체의 중첩 객체까지 동결
변경불가능한 읽기 전용의 불변 객체
객체를 값으로 갖는 모든 프로퍼티에 대해 재귀적으로 Object.freeze 메서드 호출
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)); // => true
// 중첩객체까지 동결
console.log(Object.isFrozen(person.address)); // => true
person.address.city = 'Busan';
console.log(person); // => {name: Lee". address: {sity: "Seoul"}}