내부 슬롯 & 내부 메서드
내부 슬롯과 내부 메서드는 자바스크립트 엔진의 구현 알고리즘을 설명하기 위해
ECMAScript 사양에서 사용하는 의사 프로퍼티와 의사 메서드다.
ECMAScript 사양에 등장하는 이중 대괄호로 감싼 이름들이 바로 그것들이다.
내부 슬롯과 내부 메서드는 자바스크립트 엔진에서 실제로 동작은 하나 개발자들이 직접 사용할 수 있도록
공개된 프로퍼티는 아니다.
원칙적으로 내부 슬롯과 내부 메서드에 직접적으로 접근하거나 호출할 수 있는 방법을 제공하지는 않는다.
하지만 일부에 한해 간접적으로 접근할 수 있는 수단을 제공한다.
모든 객체는 [[Prototype]] 이라는 내부 슬롯을 갖고,
이는 __proto__ 를 통해 간접적으로 접근할 수 있다.
사실 이 언더스코더 2개를 통해 호출하는 프로퍼티 혹은 함수는 파이썬에서 처음 접했다.
파이썬에서 저러한 형태의 메소드를 매직메소드라고 부르는데,
파이썬에서는 이 매직메소드를 적극적으로 이용하고 다루는 것을 파이써닉하다고 좋게 본다.
나도 파이썬 공부할 때 이 매직메소드를 이용하여 객체를 깊게 다뤘던 것 같다.
프로퍼티 어트리뷰트와 프로퍼티 디스크립터 객체
자바스크립트 엔진은 프로퍼티를 생성할 때
프로퍼티의 상태를 나타내는 프로퍼티 어트리뷰트를 기본값으로 자동 정의한다.
- 프로퍼티 값 [[Value]]
- 값의 갱신 가능 여부 [[Writable]]
- 열거 가능 여부 [[Enumerable]]
- 재정의 가능 여부 [[Configurable]]
이 어트리뷰트에 직접 접근할 수는 없으나
Object.getOwnPropertyDescriptor 메서드를 사용해 간접적으로 확인할 수 있다.
Object.getOwnPropertyDescriptor 메서드가 반환하는 것은 프로퍼티 디스크립터 객체이다.
존재하지 않는 프로퍼티나 상속받은 프로퍼티에 대한 프로퍼티 디스크립터를 요구하면 undefined 가 반환된다.
해당 메서드에 s 가 붙은 Object.getOwnPropertyDescriptors 메서드는
모든 프로퍼티의 프로퍼티 어트리뷰트 정보를 제공한다.
데이터 프로퍼티 vs 접근자 프로퍼티
프로퍼티는 데이터 프로퍼티와 접근자 프로퍼티로 나뉜다.
데이터 프로퍼티
: 키와 값으로 구성된 일반적인 프로퍼티.
접근자 프로퍼티
: 자체적으로는 값을 지니지않고 다른 데이터 프로퍼티의 값을 읽거나 저장할 때 호출되는 접근자 함수로 구성된 프로퍼티.
이제 이 데이터 프로퍼티와 접근자 프로퍼티에 대해 좀 더 자세히 알아보자.
데이터 프로퍼티
프로퍼티 어트리뷰트 | 프로퍼티 디스크립터 객체 프로퍼티 |
[[Value]] | value |
[[Writable]] | writable |
[[Enumerable]] | enumerable |
[[Configurable]] | configurable |
프로퍼티가 생성될 때 [[Value]] 의 값은 프로퍼티 값으로 초기화되며
[[Writable]], [[Enumerable]], [[Configurable]] 의 값은 true 로 초기화된다.
접근자 프로퍼티
접근자 프로퍼티는 값을 갖지 않고
다른 데이터 프로퍼티의 값을 읽거나 저장할 때 사용하는 접근자 함수로 구성된 프로퍼티다.
프로퍼티 어트리뷰트 | 프로퍼티 디스크립터 객체의 프로퍼티 |
[[Get]] | get |
[[Set]] | set |
[[Enumerable]] | enumerable |
[[Configurable]] | configurable |
접근자 프로퍼티 키로 프로퍼티 값에 접근하면 getter 함수 호출,
접근자 프로퍼티 키로 프로퍼티 값을 저장하면 setter 함수 호출한다 라는게 데이터 프로퍼티와는 다른 점이고,
Enumerable과 Configurable 은 데이터 프로퍼티와 동일하다.
getter/setter 함수라고 생각하면 된다.
getter와 setter 함수를 모두 정의할 수도 있고, 하나만 정의할 수도 있다.
이는 예제가 필요할 것 같아서 써놓는다.
const box = {
color: 'red',
size: 'small',
get colorSize() {
return `${this.color} ${this.size}`;
},
set colorSize(kind) {
[this.color, this.size] = kind.split(' ');
}
};
이렇게 객체의 프로퍼티에 get과 set 메서드를 할당할 수 있다!
프로퍼티 정의
새로운 프로퍼티를 추가하면서 프로퍼티 어트리뷰트를 정의하거나
기존 프로퍼티 어트리뷰트를 재정의하고 싶으면 어떻게 해야할까?
Object.defineProperty 메서드를 이용하면 프로퍼티 어트리뷰트를 정의할 수 있다.
Object.defineProperty(box, 'color', {
value: 'red',
writable: true,
enumerable: true,
configurable: true
});
프로퍼티 어트리뷰트를 정의할 때 value 를 지정하지 않는다면 기본으로 undefined 를,
나머지 writable, enumerable, configurable 을 지정하지 않는다면 기본으로 false를 준다.
위의 예제에서는 데이터 프로퍼티인 'color' 를 예시로 들었으나,
getter/setter 인 colorSize 접근자 프로퍼티 같은 경우 지정하지 않으면 기본으로 undefined 를 적용한다.
Object.defineProperty 는 프로퍼티 하나를 정의하지만
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 메서드를 이용한다.
말 그대로 객체를 확장하는 프로퍼티 추가가 금지된다.
프로퍼티는 프로퍼티 동적 추가와 Object.defineProtperty 메서드로 추가할 수 있는데,
이 두 가지 방법 다 금지된다.
확장이 가능한 객체인지 Object.isExtensible 메서드로 확인 가능하다.
일반적으로 생성된 객체는 확장이 가능함을 알 수 있다.
Object.preventExtensions 메서드를 이용하면 Object.isExtensible 메서드 결과 값이 false 로 변하는 걸 확인했다.
이때 프로퍼티를 추가하면 에러는 나지 않고 무시된다.
엄격 모드에서는 에러가 난다.
프로퍼티 추가만 안되는 것이지 삭제, 변경은 가능하다.
객체 밀봉
Object.seal 메서드를 이용하면 객체가 밀봉된다.
이는 프로퍼티 추가 및 삭제, 프로퍼티 어트리뷰트 재정의 금지를 의미한다.
밀봉된 객체는 읽기와 쓰기만 가능하다.
밀봉된 객체 여부는 Object.isSealed 메서드로 확인할 수 있다.
객체 동결
Object.freeze 메서드로 객체를 동결할 수 있다.
이는 객체를 읽기만 가능하게 한다.
동결된 객체인지 여부는 Object.isFrozen 메서드로 확인할 수있다.
불변 객체
위의 객체 확장 금지, 객체 밀봉, 객체 동결은 얕은 변경 방지로
직속 프로퍼티만 변경이 방지되고 중첩 객체는 영향을 주지 않는다.
따라서 Object.freeze 메서드로 객체를 동결해도 중첩 객체까지 동결하지는 못한다.
Object.freeze 를 했을 때 box 객체는 동결됨을 확인 가능하다.
중첩 객체인 size 는 false 임을 확인할 수 있다.
객체의 중첩객체까지 동결하여 완벽한 불변객체를 구현하려면
객체를 값으로 갖는 모든 프로퍼티에 대해 재귀적으로 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 box = {
color: 'red',
size: {
s: 'small'
}
};
deepFreeze(box);
'Archive > Develop' 카테고리의 다른 글
[ 모던 자바스크립트 스터디 ] 함수와 일급 객체 (1) | 2022.09.25 |
---|---|
[ 모던 자바스크립트 스터디 ] 생성자 함수에 의한 객체 생성 (1) | 2022.09.24 |
[ PostgreSQL ] CASE WHEN ~ END 와 비교연산(<>) (0) | 2022.09.21 |
구조적 서브타이핑 / 잉여 속성 체크 (0) | 2022.09.21 |
[ 모던 자바스크립트 스터디 ] let, const 키워드와 블록 레벨 스코프 (0) | 2022.09.20 |