16장 프로퍼티 어트리뷰트
(1) 내부 슬롯과 내부 메서드
- 프로퍼티 어트리뷰트 이해하기 위해 필요한 개념
- 자바스크립트 엔진의 구현 알고리즘을 설명하기 위해 ECMA 사양에서 사용하느 의사 프로퍼티와 의사 메서드
- ECMAScript 사양에 등장하는 이중 대괄호로 감싼 이름들
[[ . . . ]]
- ECMAScript 사양에 정의된 대로 구현 → 자바스크립트 엔진에서 실제로 동작
🔔 개발자가 직접 접근할 수 있도록 공개된 객체의 프로퍼티 ❌
- 내부 슬롯과 내부 메서드는 자바스크립트 엔진의 내부 로직 ⇒ 원칙적으로 직접 접근 · 호출 방법 제공 ❌
- 일부 내부 슬롯과 내부 메서드에 한하여 간접적 접근 수단 제공
- (ex) 모든 객체는
[[Prototype]]
이라는 내부 슬롯 가짐- 내부 슬롯은 자바스크립트 엔진의 내부 로직이므로 원칙적으로 접근 불가능하지만
[[Prototype]]
내부 슬롯의 경우,__proto__
를 통해 간접적 접근 가능const o = {}; o.[[[prototype]]; // Uncaught SyntaxError: Unexpected token '[' o.__proto__ // Object.prototype
- 내부 슬롯은 자바스크립트 엔진의 내부 로직이므로 원칙적으로 접근 불가능하지만
(2) 프로퍼티 어트리뷰트와 프로퍼티 디스크립터 객체
- 프로퍼티 어트리뷰트 값 직접 접근 불가능하지만
Object.getOwnPropertyDescriptor
메서드 통해 간접적 확인 가능const person = { name: 'Lee' }; // 프로퍼티 어트리뷰트 정보를 제공하는 프로퍼티 디스크립터 객체를 반환함 console.log(Object.getOwnPropertyDescriptor(person, 'name')); // {value: "Lee", writable: true, enumerable: true, configurable: true} persob.age = 20; console.log(Object.getOwnPropertyDescriptor(person)); /* { name: { value: "Lee", writable: true, enumerable: true, configurable: true }, age: { value: 20, writable: true, enumerable: true, configurable: true } } */
- Object.getOwnPeropertyDescriptor 메서드 호출시
- 첫 번째 매개변수 = 객체의 참조
- 두 번째 매개변수 = 프로퍼티 키 (문자열)
- 반환 = 프로퍼티 디스크립터 객체 (프로퍼티 어트리뷰트 정보 제공)
- 존재하지 않는 프로퍼티 · 상속받은 프로퍼티에 대한 프로퍼티 디스크립트 요구⇒ undefined 반환
- 기존에는 하나의 프로퍼티에 대해 프로퍼티 디스크립터 객체 반환
- ES8 : 모든 프로퍼티의 프로퍼티 어트리뷰트 정보 제공하는 프로퍼티 디스크립터 객체 반환
- Object.getOwnPeropertyDescriptor 메서드 호출시
(3) 데이터 프로퍼티와 접근자 프로퍼티
- 프로퍼티 종류
- 데이터 프로퍼티키와 값으로 구성된 일반적인 프로퍼티
- 접근자 프로퍼티자체적으로는 값을 갖지 않고 다른 데이터 프로퍼티의 값을 읽거나 저장시 호출되는 접근자 함수로 구성된 프로퍼티
1️⃣ 데이터 프로퍼티
- 데이터 프로퍼티의 프로퍼티 어트리뷰트
- 자바스크립트 엔진이 프로퍼티 생성시 기본값으로 자동 정의
- 프로퍼티 어트리뷰트 / 프로퍼티 디스크립터 객체의 프로퍼티
[[Value]]
/value
- 프로퍼티 키를 통해 프로퍼티 값에 접근시 반환되는 값
- 프로퍼티 키를 통해 프로퍼티 값 변경시 [[Value]]에 값 재할당
- 프로퍼티 값 없으면 프로퍼티 동적 생성 후 생성된 프로퍼티의 [[Value]]에 값 저장
[[Writable]]
/writable
- 프로퍼티 값의 변경 가능 여부를 나타냄 (Boolean)
- false인 경우, 해당 프로퍼티의 [[Value]]의 값을 변경할 수 없는 읽기 전용 프로퍼티
- 프로퍼티 값의 변경 가능 여부를 나타냄 (Boolean)
[[Enumarable]]
/enumerable
- 프로퍼티의 열거 가능 여부 나타냄 (Boolean)
- false인 경우 해당 프로퍼티는
for … in문
이나Object.keys
메서드 등으로 열거 불가능
- false인 경우 해당 프로퍼티는
- 프로퍼티의 열거 가능 여부 나타냄 (Boolean)
[[Configura]]
/configurable
- 프로퍼티의 재정의 가능 여부 나타냄 (Boolean)
- false인 경우 해당 프로퍼티의 삭제, 프로퍼티 어트리뷰 값의 변경 금지
- [[Writable]]이 true인 경우, [[Value]]의 변경과 [[Writable]]을 false로 변경하는 것은 허용
- false인 경우 해당 프로퍼티의 삭제, 프로퍼티 어트리뷰 값의 변경 금지
- 프로퍼티의 재정의 가능 여부 나타냄 (Boolean)
- 예
const person = { name: 'Lee' }; persob.age = 20; console.log(Object.getOwnPropertyDescriptor(person)); /* { name: { value: "Lee", writable: true, enumerable: true, configurable: true }, age: { value: 20, writable: true, enumerable: true, configurable: true } } */
- value 프로퍼티의 값은 ‘Lee’와 20⇒ [[Value]]의 값이 ‘Lee’와 20
- writable, enumarable, configurable 프로퍼티의 값 모두 true⇒ [[Writable]], [[Enumarable]], [[Configurable]] 값 모두 true
- 프로퍼티 생성시 [[Value]]의 값은 프로퍼티 값으로 초기화
- [[Writable]], [[Enumarable]], [[Configurable]] 값은 true로 초기화
2️⃣ 접근자 프로퍼티
자체적으로 값을 갖지 않고 다른 데이터 프로퍼티의 값을 갖거나 저장시 사용하는 접근자 함수로 구성된 프로퍼티
- 프로퍼티 어트리뷰트 / 프로퍼티 디스크립터 객체의 프로퍼티
[[Get]]
/get
- 접근자 프로퍼티를 통해 데이터 프로퍼티의 값을 읽을 때 호출되는 접근자 함수
- 접근자 프로퍼티로 키로 값 접근
- 프로퍼티 어트리뷰터 [[Get]]의 값인 getter 함수 호출됨
- 프로퍼티 값 반환
- 접근자 프로퍼티를 통해 데이터 프로퍼티의 값을 읽을 때 호출되는 접근자 함수
[[Set]]
/set
- 접근자 프로퍼티를 통해 데이터 프로퍼티의 값을 저장할 때 호출되는 접근자 함수
- 접근자 프로퍼티 키로 프로퍼티 값 저장
- 프로퍼티 어트리뷰트 [[Set]]의 값인 setter 함수 호출됨
- 그 결과가 프로퍼티 값으로 저장됨
- 접근자 프로퍼티를 통해 데이터 프로퍼티의 값을 저장할 때 호출되는 접근자 함수
[[Enumarable]]
/enumarable
- 데이터 프로퍼티 [[Enumarable]]과 같음
[[Configurable]]
/configurable
- 데이터 프로퍼티 [[Configurable]]과 같음
- 접근자 함수는 getter/setter 함수라고도 부름
- 접근자 프로퍼티는 getter와 setter함수를 모두 정의 가능
- 하나만 정의할 수도 있음
const person = {
// 데이터 프로퍼티
firstName: "Gaeun",
lastName: "Lee",
// 접근자 프로퍼티 (접근자 함수로 구성됨)
// getter 함수
get fullName() {
return `${this.firstName} ${this.lastName}`;
},
// setter 함수
set fullName(name) {
[this.firstName, this.lastName] = name.split(" ");
},
};
// 데이터 프로퍼티를 통한 프로퍼티 값의 참조
console.log(person.firstName + " " + person.lastName); // Gaeun Lee
// 접근자 프로퍼티를 통한 프로퍼티 값의 저장
person.fullName("Chaeun Lee");
console.log(person); // {firstName: 'Chaeeun', LastName: 'Lee'}
// 접근자 프로퍼티를 통한 프로퍼티 값의 참조
console.log(person.fullName); // Chaeeun Lee
// firstName은 데이터 프로퍼티
let descriptor = Object.getOwnPropertyDescriptor(person, "firstName");
console.log(descriptor);
// {value: "Chaeeun Lee", writable: true, enumerable: true, configurable: true}
// fullName은 접근자 프로퍼티
descriptor = Object.getOwnPropertyDescriptor(person, fullName);
console.log(descriptor);
// {get: f, set: f, enumerable: true, configurable: true}
- person 객체
- 데이터 프로퍼티
firstName
,lastName
- 접근자 프로퍼티
fullName
get
과set
이 붙은 메서드 ⇒ getter / setter 함수
- 자체적으로 값을 가지지 않으며 데이터 프로퍼티의 값을 읽거나 저장시 관여
- 내부 슬롯 / 메서드 관점
- 접근자 프로퍼티 fullName으로 프로퍼티 값에 접근 → 내부적으로 [[Get]] 내부 메서드 호출되어 다음과 같이 동작
- 프로퍼티 키가 유효한지 확인프로퍼티 키는 문자열 또는 심벌이어야 함
- 프로토타입 체인에서 프로퍼티 검색person객체에 fullName 프로퍼티 존재
- 검색된 fullName 프로퍼티가 데이터 프로퍼티인지 접근자 프로퍼티인지 확인fullName 프로퍼티는 접근자 프로퍼티임
- 접근자 프로퍼티 fullName의 프로퍼티 어트리뷰트 [[Get]]의 값인 getter 함수를 호출하여 그 결과 반환= Object.getOwnPropertyDescriptor 메서드가 반환하는 프로퍼티 디스크립터 객체의 get 프로퍼티 값
- 접근자 프로퍼티 fullName으로 프로퍼티 값에 접근 → 내부적으로 [[Get]] 내부 메서드 호출되어 다음과 같이 동작
- 데이터 프로퍼티
접근자 프로퍼티와 데이터 프로퍼티 구별 방법
- 프로퍼티 디스크립터 객체의 프로퍼티가 다름
// 일반 객체의 __proto__는 접근자 프로퍼티임 Object.getOwnPropertyDescriptor(Object.prototype, '__proto__'); // {get: f, set: f, enumerable: true, configurable: true} // 함수 객체의 prototype은 데이터 프로퍼티임 Object.getOwnPropertyDescriptor(function() {}, 'prototype'); // {value: {...}, writable: true, enumerable: true, configurable: true}
(4) 프로퍼티 정의
= 새로운 프로퍼티를 추가하면서 프로퍼티 어트리뷰트를 명시적으로 정의하거나, 기존 프로퍼티의 프로퍼티 어트리뷰트를 재정의하는 것
- ex
- 프로퍼티 값을 갱신 가능하도록 할 것인지
- 프로퍼티 값을 열거 가능하도록 할 것인지
- 프로퍼티를 재정의 가능하도록 할 것인지
Object.defineProperty
메서드 사용시 프로퍼티 어트리뷰트 정의 가능- 인수 :
객체의 참조
와 데이터 프로퍼티의 키인문자열
과프로퍼티 디스크립터 객체
- 프로퍼티 디스크립터 객체의 프로퍼티를 일부 생략 가능
- 생략된 어트리뷰트는 다음과 같이 기본값 적용됨
프로퍼티 디스크립터 객체의 프로퍼티
/대응하는 프로퍼티 어트리뷰트
/생략했을 때의 기본값
value
/[[Value]]
/undefined
get
/[[Get]]
/undefined
set
/[[Set]]
/undefined
writable
/[[Writable]]
/false
enumerable
/[[Enumerable]]
/false
configurable
/[[Configurable]]
/false
- 생략된 어트리뷰트는 다음과 같이 기본값 적용됨
- 한 번에 하나의 하나의 프로퍼티만 정의 가능
- Object.defineProperties 메서드 사용시 여러 개의 프로퍼티 한 번에 정의 가능
- 인수 :
(5) 객체 변경 방지
객체는 변경 가능한 값 → 재할당 없이 직접 변경 가능
- 프로퍼티 추가 / 삭제 가능
- 프로퍼티 값 갱신 가능
Object.defineProperty
/Object.defineProperties
메서드 사용하여 프로퍼티 어트리뷰트 재정의 가능
객체의 변경을 방지하는 다양한 메서드(각각 객체의 변경을 금지하는 강도 다름)
(1) 객체 확장 금지
Object.preventExtensions(object)
- 객체의 확장을 금지함= 프로퍼티 추가 금지
- 프로퍼티 동적 추가와 Object.defineProperty 메서드 사용 불가
- 확장 가능한 객체인지 여부
Object.isExtensible(object)
메서드 통해 확인
(2) 객체 밀봉
Object.seal(object)
- 프로퍼티 추가 및 삭제와 프로퍼티 어트리뷰트 재정의 금지 의미
- 밀봉된 객체인지 여부
Object.isSealed(object)
(3) 객체 동결
Object.freeze(object)
- 동결된 객체는 읽기만 가능
- 동결된 객체인지 여부
Object.isFrozen(object)
⇒ 위 메서드들 = 얕은 변경 방지
- 직속 프로퍼티만 변경 방지
- 중첩 객체까지 영향 주지 못함
불변 객체
객체의 중첩 객체까지 동결하여 변경이 불가능한 읽기 전용의 객체
⇒ 객체를 값으로 갖는 모든 프로퍼티에 대해 재귀적으로 Object.freeze 메서드 호출해야 함