내부 슬롯(internal slot)과 내부 메서드(internal method)는 자바스크립트 엔진의 구현 알고리즘을 설명하기 위해 ECMAScript 사양에서 사용하는 이중 대괄호([[...]])로 감싼 이름들인 의사 프로퍼트 (pseudo property)와 의사 메서드(pseudo method)이다.
내부 슬롯, 내부 메서드는 자바스크립트 엔진 내부 로직으로, 직접 접근할 수 있도록 외부로 공개된 객체의 프로퍼티가 아니다. 하지만 일부 내부 슬롯과 내부 메서드에 한해서 간접적으로 접근할 수 있는 수단을 제공한다.
그 중 하나가 [[Prototype]]
이라는 내부 슬롯인데, __proto__
라는 프로퍼티를 통해 간접적인 접근을 허용한다.
ES6에서 표준화 되어있기 때문.
근데 mdn에서는 deprecated라고 표시함. 두둥.
ES6가 도입되기 이전에 브라우저들이 자의적으로 만든 프로퍼티 접근방식인데, 비표준인데도 너무 많이 퍼져있다보니 ES6에서 일단 허용해줬다고 함. 그래서 일단 표준이긴한데, 없앨 예정이고, 없애려고 하는 중.
그래서 Object.getPrototypeOf
라는 새로운 메서드를 제공해줌. 가급적 이걸 사용하는게 좋음!
자바스크립트 엔진을 프로퍼티를 생성할 때, 프로퍼티 상태를 나타내는 프로퍼티 어트리뷰트를 기본값으로 자동 정의한다.
프로퍼티 어트리뷰트에는 자바스크립트 엔진이 관리하는 내부 상태 값(meta-property)인 내부슬롯들로,
1. 프로퍼티의 값 ( [[Value]])
2. 갱신 가능 여부 ( [[Writable]])
3. 열거 가능 여부( [[Enumerable]])
4. 재정의 가능 여부( [[Configurable]])
을 포함한다.
아래 사진과 같이, 내부 상태 값들이므로 직접 접근은 불가능하지만, Object.getOwnPropertyDescriptor
메서드를 사용해 간접적으로 확인할수는 있다.
메서드의 첫번째 매개변수에 객체의 참조를 전달하고,
두번째 매개변수에 프로퍼티 키를 문자열로 전달하면,
프로퍼티 어트리뷰트 정보를 제공하는 프로퍼티 디스크립터 (Property Descriptor)객체를 반환한다.
만약 존재하지 않는 프로퍼티나 상속받은 프로퍼티에 대한 프로퍼티 디스크립터를 요구하면, undefined가 반환된다.
Object.getOwnPropertyDescriptor : 1개의 프로퍼티에 대해 프로퍼티 디스크립터 객체 반환
ES8 : Object.getOwnPropertyDescriptors : 모든 프로퍼티에 대해 프로퍼티 디스크립터 객체들을 반환
프로퍼티 종류 | 데이터 프로퍼티 | 접근자 프로퍼티 |
---|---|---|
data property | accessor property | |
설명 | 키와 값으로 구성된 일반적인 프로퍼티 | 자체적으로 값을 갖지 않고, 다른 데이터 프로퍼티의 값을 읽거나 저장할 때 호출되는 접근자 함수 (accessor function)으로 구성된 프로퍼티 |
예시 | name: 'Lee' | get fullName(){return '${this.name}'} |
프로퍼티는 위와 같이 두개로 구분할 수 있다.
데이터 프로퍼티는 4개의 프로퍼티 어트리뷰트를 갖는다.
(프로퍼티 어트리뷰트 - 프로퍼티 디스크립터 객체의 프로퍼티 순서로 기재)
- [[Value]] - value
- 프로퍼티 키를 통해 프로퍼티 값에 접근하면 반환되는 값.
- 프로퍼티 키를 통해 프로퍼티 값을 변경하면 [[Value]]에 값을 재할당.
이때 프로퍼티가 없으면, 프로퍼티를 동적생성하고 [[Value]]에 값을 저장
- [[Writable]] - writable
- 프로퍼티의 값의 변경 가능 여부를 나타내고, 불리언 값을 가짐.
- [[Writable]]값이 false인 경우, 해당 프로퍼티의 [[Value]]의 값을 변경할 수 없는 읽기 전용 프로퍼티가 됨.
- [[Enumerable]] - enumerable
- 프로퍼티의 열거 가능 여부를 나타내고, 불리언 값을 가짐.
- [[Enumerable]]값이 false인 경우, 해당 프로퍼티는 for..in문이나, Object.keys메서드 등으로 열거 불가능.
- [[Configurable]] - configurable
- 프로퍼티의 재정의 가능 여부를 나타내고, 불리언 값을 가짐.
- [[Configurable]]값이 false인 경우, 해당 프로퍼티의 삭제, 프로퍼티 어트리뷰트 값의 변경이 금지됨.
- But, [[Writable]]이 true인경우, [[Value]]의 변경과 [[Writable]]을 false로 변경하는 것이 허용된다.
프로퍼티가 생성될 때 (동적생성을 포함) [[Value]]의 값은 프로퍼티의 값으로 초기화되고,
[[Writable]], [[Enumerable]], [[Configurable]]의 값은 true로 초기화된다.
접근자 프로퍼티도 4개의 프로퍼티 어트리뷰트를 갖는다. 데이터 프로퍼티와는 다른게 2개 있다.
- [[Get]] - get
- 접근자 프로퍼티를 통해 데이터 프로퍼티의 값을 읽을 때 호출되는 접근자함수.
- 접근자 프로퍼티 키로 프로퍼티 값에 접근하면, 프로퍼티 어트리뷰터 [[Get]]의 값인 getter 함수가 호출되고, 그 결과가 프로퍼티 값으로 반환됨.
- [[Set]] - set
- 접근자 프로퍼티를 통해 데이터 프로퍼티의 값을 저장할 때 호출되는 접근자함수.
- 접근자 프로퍼티 키로 프로퍼티 값에 접근하면, 프로퍼티 어트리뷰터 [[Set]]의 값인 setter함수가 호출되고, 그 결과가 프로퍼티 값으로 저장됨.
- [[Enumerable]] - enumerable
- 데이터 프로퍼티의 프로퍼티 어트리뷰트와 동일
- [[Configurable]] - configurable
- 데이터 프로퍼티의 프로퍼티 어트리뷰트와 동일
접근자 함수는 getter/setter라고도 부르는데, 접근자 프로퍼티는 getter, setter를 두개 다 정의할 수도 있고, 하나만 정의할 수도 있다.
👇 아래는 접근자 프로퍼티 예시👇person객체 생성
person 객체의 fullName setter, getter 호출
person 객체의 데이터 프로퍼티, 접근자 프로퍼티의 프로퍼티 어트리뷰트 확인
내부 슬롯/ 내부 메서드 관점에서 보는
접근자 프로퍼티 fullName으로 프로퍼티 값에 접근했을 때
내부적으로 [[Get]]내부 메서드가 호출되고 동작하는 방식은 ?
3번의 데이터 프로퍼티/ 접근자 프로퍼티를 구별하는 방법은 프로퍼티 어트리뷰트를 객체로 표현한 프로퍼티 디스크립터 객체의 프로퍼티가 다른것을 보고 알 수 있다.
= 즉, value, writable을 가지고 있는 객체인지, get, set을 가지고 있는 객체인지 확인하면 됨.
프로퍼티 정의 = 새로운 프로퍼티를 추가하면서, 프로퍼티 어트리뷰트를 명시적으로 정의하거나 / 기존 프로퍼티의 프로퍼티 어트리뷰트를 재정의하는 것.
= writable, enumerable, configurable의 불리언 값을 정의 / 재정의하는것
메서드의 첫번째 매개변수로 객체의 참조를 전달하고,
두번째 매개변수로 데이터 프로퍼티의 키인 문자열을 전달하고,
세번째 매개변수로 프로퍼티 디스크립터 객체를 전달한다.
const person = {};
Object.defineProperty(
person,
'firstName',
{ value : 'Ungmo', writable : true, enumerable : true, configurable : true }
); // 1번 방식
Object.defineProperty(
person,
'lastName',
{ value : 'lee' }
); // 2번 방식
Object.defineProperty 메서드로 프로퍼티를 정의할 때, 2번 방식 처럼 프로퍼티 디스크립터 객체의 프로퍼티를 일부 생략할 수 있다.
이 경우에는
value, get, set 은 undefined,
writable, enumerable, configurable 은 false
의 기본값이 적용된다.
enumerable, writable, configurable이 false인 lastName프로퍼티에 대한 변경을 시도했을 경우 모두 무시됨.
configurable 속성이 false이므로, 프로퍼티 어트리뷰트에 대한 재정의는 에러가 발생한다.
👇 아래는 접근자 프로퍼티를 Object.defineProperty한 결과 👇fullName 접근자 함수로 변경을 시도해도, lastName은 변경이 되지 않음!
Object.defineProperty : 한번에 1개의 프로퍼티를 정의
Object.defineProperties : 여러개의 프로퍼티를 한번에 정의가능
객체는 변경 가능한 값이므로 재할당 없이 직접 변경가능하다.
프로퍼티 값 갱신뿐만 아니라, 프로퍼티 어트리뷰터를 재정의할 수도 있다.
객체에서 변경가능한 부분은 5가지로 정리로,
프로퍼티 추가, 프로퍼티 삭제, 프로퍼티 값 읽기, 프로퍼티 값 쓰기, 프로퍼티 어트리뷰터 재정의가 있다.
따라서, 자바스크립트는 객체의 변경을 방지하는
1. Object.preventExtensions
2. Object.seal
3. Object.freeze
3 개의 메소드를 제공하며, 각각 객체의 변경을 금지하는 강도가 다르다.
메소드 | Object.preventExtensions | Object.seals | Object.freeze |
---|---|---|---|
구분 | 객체 확장 금지 | 객체 밀봉 | 객체 동결 |
금지강도 | 프로퍼티 추가만 불가 (프로퍼티 동적 추가, Object.defineProperty메소드 둘 다 금지) | 프로퍼티 값 읽기 / 쓰기만 허용 | 프로퍼티 값 읽기만 허용 |
객체 확인 메서드 | Object.isExtensible | Object.isSealed | Object.isFrozen |
메서드의 반환값 = false | 확장 가능 | 밀봉되어 있지 않음 | 동결되어 있지 않음 |
객체 변경 방지 메서드를 사용하더라도 중첩 객체에 대해서는 동결되지 않는다.
얕은 변경 방지로 동작하기 때문에 직속 프로퍼티에 대해서만 변경이 방지되기 때문이다.
따라서, 변경이 불가능한 읽기전용의 불변객체를 구현하기 위해 중첩객체에 대해서도 변경방지를 원할경우 (깊은 변경 방지) 객체를 값으로 갖는 모든 프로퍼티에 대해 재귀적으로 메서드를 호출해야한다.
(불변객체는 일반적으로 자바스크립트에서 객체를 다루는 기법인데, 책에서는 바꿀수 없는 객체를 일컫고 있음.)