내부 슬롯과 내부 메서드
자바스크립트 에진의 구현 알고리즘을 설명하기 위해 ECMAScript사양에서 사용하는 의사 프로퍼티(내부 슬롯)와 의사 메서드(내부 메서드)이다.
정의된 대로 구현되어 JS 엔진에서 실제로 동작하지만, 개발자가 직접 접근할 수 있도록 공개된 프로퍼티는 아니다.
원칙적으로는 내부슬롯과 내부 메서드에 직접 접근하거나 호출할 수 있는 방법을 제공하지 않는다.
다만, 일부 는 제공한다.
모든 객체는 [[Prototype]]이라는 내부 슬롯을 갖는다.
이를 .proto를 통해 간접적으로 접근할 수 있다.
const o={};
o.__proto__;
프로퍼티 어트리뷰트와 프로퍼티 디스크립터 객체
JS엔진은 프로퍼티를 생성할 때 프로퍼티의 상태를 나타내는 프로퍼티 어트리뷰트를 기본값으로 자동 정의한다.
프로퍼티 상태
- 프로퍼티 값
- 값의 갱신 여부
- 열거 가능 여부
- 재정의 가능 여부
프로퍼티 어트리뷰트
JS엔진이 관리하는 내부 상태 값인 내부슬롯
[[Value]], [[Writable]], [[Enumerable]], [[Configurable]]이다.
프로퍼티 어트리뷰트에 직접 접근할 수는 없지만 Object.getOwnPropertyDescriptor 메서드를 사용하여 간접적으로 확인할 수 있다.
const person = {
name: 'lee',
};
console.log(Object.getOwnPropertyDescriptor(person, 'name'));
//{ value: 'lee', writable: true, enumerable: true, configurable: true }
getOwnPropertyDescriptor
첫번째 매개변수는 객체의 참조(식별자), 두번째에는 키 문자열
반환값은 프로퍼티 디스크립터객체이다.
두번째 매개변수가 없는 경우, 해당 객체의 모든 프로퍼티 디스크립터 객체를 반환한다.(ES8에서 도입)
console.log(Object.getOwnPropertyDescriptor(person));
데이터 프로퍼티와 접근자 프로퍼티
프로퍼티의 종류
- 데이터 프로퍼티
- 키와 값으로 구성된 일반적인 프로퍼티
- 접근자 프로퍼티
- 자체적 값은 갖지 않으나 다른 데이터 프로퍼티의 값을 읽거나 저장할 때 호출되는 접근자 함수로 구성된 프로퍼티
데이터 프로퍼티
키 값으로 이뤄진 프로퍼티
프로퍼티 어트리뷰트
프로퍼티 어트리뷰트 | 프로퍼티 디스크립터 객체의 프로퍼티 | 내용 |
---|---|---|
[[Value]] | value | 프로퍼티 키를 통해 값에 접근하면 반환되는 값 |
프로퍼티 키를 통해 프로퍼티 값을 변경하면 [[Value]]에 값을 재할당한다. 프로퍼티가 없으면 프로퍼티를 동적 생성하고 생성된 프로퍼티에 저장한다. | ||
[[Writable]] | writable | 프로퍼티 값의 변경 가능 여부를 나타내며 불리언값이다. |
false인 경우 읽기 전용이다. | ||
[[Enurmerable]] | enurmerable | 프로퍼티의 열거 가능 여부를 나타내며 불리언 값이다. |
false인경우 for…in, Object.keys를 사용할 수 없다. | ||
[[Configurable]] | configurable | 프로퍼티의 재정의 가능 여부를 나타내며 불리언 값이다. |
false인 경우 프로퍼티의 삭제, 프로퍼티 어트리뷰트 값의 변경이 금지된다. |
접근자 프로퍼티
프로퍼티 어트리뷰트 | 프로퍼티 디스크립터 객체의 프로퍼티 | 설명 |
---|---|---|
[[Get]] | get | 접근자 프로퍼티를 통해 데이터 프로퍼티의 값을 읽을 때 호출되는 접근자 함수 |
접근자 프로퍼티로 프로퍼티 값에 접근하면 프로퍼티 어트리뷰트 함수가 호출되고 그 결과가 프로퍼티 값으로 반환된다. | ||
[[Set]] | set | 접근자 프로퍼티를 통해 데이터 프로퍼티의 값을 저장할 때 호출되는 함수 |
[[Enumerable]] | enumerable | 데이터 프로퍼티와 동일 |
[[Configurable]] | configurable | 데이터 프로퍼티와 동일 |
const person = {
firstName: 'Ungmo',
lastName: 'Lee',
get fullName() {
return `${this.firstName} ${this.lastName}`;
},
set fullName(name) {
[this.firstName, this.lastName] = name.split(' ');
},
};
person.fullName = 'Donggil Lee';
console.log(person.fullName);
let descripter = Object.getOwnPropertyDescriptor(person, 'firstName');
console.log(descripter);
//{
// value: 'Donggil',
// writable: true,
// enumerable: true,
// configurable: true
// }
descripter = Object.getOwnPropertyDescriptor(person, 'fullName');
console.log(descripter);
//{
// get: [Function: get fullName],
// set: [Function: set fullName],
// enumerable: true,
// configurable: true
//}
프로토타입
어떤 객체의 상위 객체 역할을 하는 객체이다.
프로토타입은 하위 객체에게 자신의 프로퍼티와 메서드를 상속한다.
프로토타입 체인은 프로토타입이 단방향 링크드 리스트 형태로 연결되어있는 상속구조를 말한다.객체의 프로퍼티나 메서드에 접근하려 할때 해당 객체에 없으면 프로토타입 체인을 따라 프로토타입의 프로퍼티나 메서드를 차례대로 검색한다.
프로퍼티 정의
새로운 프로퍼티를 추가하면서 프로퍼티 어트리뷰트를 명시적으로 정의하거나 기존 어트리뷰트를 재정의하는것
Object.defineProperty
프로퍼티의 어트리뷰트를 정의할 수 있음
인수로는 객체 식별자와 데이터 프로퍼티의 키인 문자열, 프로퍼티 디스크립터 객체이다.
const person = {};
Object.defineProperty(person, 'firstName', {
value: 'gildong',
writable: true,
enumerable: true,
configurable: true,
});
Object.defineProperty(person, 'lastName', {
value: 'Lee',
//속성 누락시, value는 undefined
// 다른 속성은 false가 기본값이다.
});
let d = Object.getOwnPropertyDescriptor(person, 'firstName');
console.log('firstName', d);
d = Object.getOwnPropertyDescriptor(person, 'lastName');
console.log('lastName', d);
// 열거 시도
console.log(Object.keys(person)); //lastName은 열거되지않음
// 값 변경 시도
person.lastName = 'kim';
console.log(person.lastName); //Lee 오류 없이 바뀌지 않음(Writable이 false)
// 삭제시도
delete person.lastName;
console.log(person.lastName); //삭제 되지않음(configurable이 false)
Object.defineProperty(person, 'fullName', {
get() {
return `${this.firstName} ${this.lastName}`;
},
set(name) {
[this.firstName, this.lastName] = name.split(' ');
},
enumerable: true,
configurable: true,
});
d = Object.getOwnPropertyDescriptor(person, 'fullName');
console.log(d);
person.fullName = 'donggil Kim'; //Kim은 반영되지 않음
console.log(person.fullName);
Object.defineProperties
⇒ 여러 프로퍼티를 한번에 정의 가능함
객체 변경 방지
구분 | 메서드 | 프로퍼티 추가 | 프로퍼티 삭제 | 프로퍼티 값 읽기 | 프로퍼티 값 쓰기 | 프로퍼티 어트리뷰트 재정의 |
---|---|---|---|---|---|---|
객체 확장금지 | Object.preventExtensions | X | O | O | O | O |
객체 밀봉 | Object.seal | X | X | O | O | X |
객체 동결 | Object.freeze | X | X | O | X | X |
객체 확장 금지
Object.preventExtensions
프로퍼티 추가가 금지됨
프로퍼티 동적추가(일반적은 속성추가)와 defineProperty 메서드로 추가하는 방식 모두 금지
확장 여부 확인은 Object.isExtensible 메서드로 확인 가능하다.
const person = { name: 'lee' };
console.log(Object.isExtensible(person)); //true
Object.preventExtensions(person);
console.log(Object.isExtensible(person)); //false
person.age = 20; //무시, strict 모드에서는 에러를 일으킴
Object.defineProperty(person, 'age', {
value: 20,
}); //TypeError
객체 밀봉
Object.seal 메서드는 객체를 밀봉한다.
프로퍼티 추가, 삭제, 어트리뷰트 재정의를 금지한다.
읽기와 쓰기만 가능해짐
밀봉 여부는 isSealed 메서드로 확인가능하다.
const person = {
name: 'lee',
};
console.log(Object.isSealed(person));
Object.seal(person);
console.log(Object.isSealed(person));
person.age = 20; //무시, strict 모드에서는 에러
console.log(person);
person.name = 'Kim'; //잘 실행됨
console.log(person);
객체 동결
Object.freeze로 객체를 동결함
동결된 객체는 읽기만 가능하다.
Object.isFrozen 메서드로 여부 확인이 가능하다.
const person = {
name: 'lee',
};
console.log(Object.isFrozen(person));
Object.freeze(person);
console.log(Object.isFrozen(person));
불변객체
객체를 동결해도, 중첩 객체를 동결할 수는 없다.
이를 해결하기 위해서는 재귀적으로 모든 프로퍼티에 대해 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 obj = {
name: 'lee',
address: {
city: 'seoul',
},
};
deepFreeze(obj);
console.log(Object.isFrozen(obj));
console.log(Object.isFrozen(obj.address));