https://www.mobiinside.co.kr/2021/06/07/developer-certificate/
내부 슬롯과 내부 메서드란?
자바스크립트 엔진의 구현 알고리즘을 설명하기 위해 ECMAScript 사양에서 사용하는 의사 프로퍼티와 의사 메서드
([[...]])로 감싼 이름들이 내부 슬롯과 내부 메서드다
const o = {};
//내부 슬롯은 내부 로직으로 접근 불가능
o.[[Prototype]]// 문법 에러
//일부 내부 슬롯과 메서드에 한해 간접적 접근 수단 제공
o.__proto__ //Object.prototype
자바스크립트 엔진은 프로퍼티를 생성할 때 프로퍼티의 상태를 나타내는 프로퍼티 어트리뷰트를 기본값으로 자동 정의.
프로퍼티 상태
- 프로퍼티의 값 (value)
- 값의 갱신 가능 여부 (writable)
- 열거 가능 여부 (enumerable)
- 재정의 가능 여부 (configuarble)
프로퍼티 어트리뷰트
Object.getOwnPropertyDescriptor 메서드를 호출할 때
Object.getOwnPropertyDescriptor 메서드는 프로퍼티 어트리뷰트 정보를 제공하는 프로퍼티 디스크립터 객체 반환
만약 존재하지 않는 프로퍼티나 상속받은 프로퍼티에 대한 프로퍼티 디스크립터를 요구하면 undefined
const person ={name: "Lee"}
//프로퍼티 어트리뷰트 정보를 제공하는 프로퍼티 디스크립터 객체 반환
console.log(Object.getOwnPropertyDescriptor(person, 'name'))
//{value: "Lee", writable:true, enumeralbe: true, configurable: true}
const person = {
name : 'Lee'
}
//프로퍼티 어트리뷰트 정보를 제공하는 프로퍼티 디스크립터 객체 취득
console.log(Object.getOwnPropertyDescriptor(person, 'name'))
//{value: "Lee", writable: true, enumberable: true, configurable: true}
접근자 함수는 getter/setter함수라고도 부른다. 접근자 프로퍼티는 getter와 setter함수를 모두 정의할 수 있고 하나만 정의할 수도 있다.
const person={
//데이터 프로퍼티
firstName: "Ungmo",
lastName: "Lee",
//fullName은 접근자 함수로 구성된 접근자 프로퍼티다
//getter함수
get fullName(){
return `${this.firstName} ${this.lastName}`
}
//setter 함수
set fullName(name){
//구조 분해 할당
[this.firstName, this.lastName] = name.split(' ')
}
//데이터 프로퍼티를 통한 프로퍼티 값의 참조
console.log(person.firstName + '' + person.lastName);
//접근자 프로퍼티를 통한 프로퍼티 값의 저장
person.fullName = 'Heegun Lee';
console.log(person) //{firstName: 'Heegun', lastName: 'Lee'}
//접근자 프로퍼티를 통한 프로퍼티 값의 참조
//접근자 프로퍼티 fullName에 접근하면 getter함수가 호출된다
console.log(person.fullName); //Heegun Lee
//firstName은 데이터 프로퍼티다
//데이터 프로퍼티는 [[Value]], [[Writable]], [[Enumerable]],[[Configurable]]
}
//프로퍼티 어트리뷰트를 갖는다
let descriptor = Object.getOwnPropertyDescriptor(person, 'firstName');
console.log(descriptor)
person 객체의 firstName과 lastName 프로퍼티는 일반적인 데이터 프로퍼티다.
메서드 앞에 get, set이 붙은 메서드가 있는데 이것들이 바로 getter, setter함수이고 getter/setter 함수의 이름 fullName이 접근자 프로퍼티다.
접근자 프로퍼티는 자체적으로 값(프로퍼티 어트리뷰트[[Value]])을 가지지 않으며 다만 데이터 프로퍼티의 값을 읽거나 저장할 때 관여할 뿐이다.
이를 내부 슬롯/메서드 관점에서 설명하면 다음과 같다.
접근자 프로퍼티 fullName으로 프로퍼티 값에 접근하면 내부적으로 [[Get]] 내부 메서드가 호출되어 다음과 같이 동작한다.
- 프로퍼티 키가 유효한지 확인.
: 키는 문자열 또는 심벌이어야 한다.
ex. 프로퍼티 키 'fullName'은 문자열이므로 유효한 프로퍼티 키다- 프로토타입 체인에서 프로퍼티를 검색한다.
: person 객체에 fullName 프로퍼티가 존재한다- 검색된 fullName 프로퍼티가 데이터 프로퍼티인지 접근자 프로퍼티인지 확인한다. fullName 프로퍼티는 접근자 프로퍼티다.
- 접근자 프로퍼티 fullName의 프로퍼티 어트리뷰트 [[Get]]의 값, 즉 getter 함수를 호출하여 그 결과를 반환한다.
프로퍼티 fullName의 프로퍼티 어트리뷰트 [[Get]]의 값은 Object.getOwnPropertyDescriptor 메서드가 반환하는 프로퍼티 디스크립터 객체의 get 프로퍼티 값과 같다
cf. 프로토타입
- 여러 객체의 상위(부모) 객체의 역할을 하는 객체
- 하위(자식) 객체에게 자신의 프로퍼티와 메서드를 상속한다
- 프로토타입 객체의 프로퍼티나 메서드를 상속받은 하위 객체는 자신이 프로퍼티 또는 메서드인 것처럼 자유롭게 사용할 수 잇다
cf. 프로토타입 체인
프로토타입이 단방향 링크드 리스트 형태로 연결되어 있는 상속 구조
객체의 프로퍼티나 메서드에 접근하려고 할 대 해당 객체에 접근하려는 프로퍼티 또는 메서드가 없다면 프로토타입 체인을 따라 프로토타입의 프로퍼티나 메서드를 차례대로 검색한다
접근자 프로퍼티와 데이터 프로퍼티를 구별하는 방법
//일반 객체의 __proto__는 접근자 프로퍼티
Object.getOwnPropertyDescriptor(Object.prototype, '__proto__')
//{get: f, set:f, enumerable: false, configurable: true}
//함숫 객체의 prototype은 데이터 프로퍼티다
Object.getOwnPropertyDescriptor(function(){}, 'prototype');
//{value: {...}, writable: true, enumerable: false, configurable:false}
Object.getOwnPropertyDescriptor 메서드가 반환한 프로퍼티 어트리뷰트를 객체로 표현한 프로퍼티 디스크립터 객체를 보면 구분할 수 있다
프로퍼티 정의란?
객체의 프로퍼티가 어떻게 동작해야 하는지 명확히 정의할 수 있다
const person={};
//데이터 프로퍼티 정의
Object.defineProperty(person, {
firstName:{
value: 'Ungmo',
writable: true,
enumerable: true,
configurable: true
},
lastName: {
value: 'Lee',
writable: true,
enumerable: true,
configurable: true
},
//접근자 프로퍼티 정의
fullName:{
//getter 함수
get(){
return `${this.firstName} ${this.lastName}`
},
//setter 함수
set(name){
[this.firstName, this.lastName] = name.split(' ')
},
enumerable : true,
configurable : true
}
}
person.fullName = 'Heegun Lee'
console.log(person);
자바스크립트는 객체의 변경을 방지하는 다양한 메서드를 제공한다
객체 변경 방지 메서드들은 객체의 변경을 금지하는 강도가 다르다
Object.preventExtensions 메서드
객체의 확장을 금지한다
객체 확장 금지란?
const person = {name : 'Lee'};
//객체 확장 금지된 객체 X
console.log(Object.isExtensible(person)); //true
//확장 금지하여 프로퍼티 추가 금지
Object.preventExtension(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
Object.seal 메서드
const person = {name: 'Lee}
//밀봉된 객체가 아니다
console.log(Object.isSealed(person))//false
//밀봉하여 추가, 삭제, 재정의 금지
Object.seal(person)
//밀봉된 객체다
console.log(Object.isSealed(person));//true
console.log(Object.getOwnPropertyDescriptors(person))l
/{name: {value: 'Lee', writable: true, enumerable:true, configurable:false}}
//프로퍼티 추가 금지
person.age = 20; //무시
console.log(person) //{name: "Lee"}
//프로퍼티 삭제 금지
delete person.name//무시
console.log(person)
//프로퍼티 값 갱신 가능
person.name = 'Kim'
console.log(person) //{name: "Kim"}
//프로퍼티 어트리뷰트 재정의 금지
Object.defineProperty(person, 'name', {configurable:true});
//typeError
const person = {name: 'Lee'}
console.log(Object.isFrozen(person));
변경 방지로 직속 프로퍼티만 변경이 방지되고 중첩 객체까지느 영향을 주지는 못한다
따라서 freeze메서드로 객체를 동결해도 중첩 객체까지 동결할 수 없다