1. Javascript의 프로퍼티 서술자
단순하게 말하면 데이터를 담아두는 Property (속성)이다.
ES5부터 모든 프로퍼티는 프로퍼티 서술자 (Property Descriptor)로 표현된다.
var exObject = {
a : 2
};
Object.getOwnPropertyDescriptor(exObject, "a");
/*
* {
* value : 2,
* writable : true,
* enumerable : true,
* configurable : true
* }
*/
위 예제를 보면 값 (value) 말고도 writable, enumerable, configurable의 세 가지 특성이 더 있다. 이렇게 프로퍼티 생성 시 프로퍼티 서술자에 담긴 기본 특성값을 확인 할 수 있는데, Object.defineProperty(..)로 새로운 프로퍼티를 추가하거나 기존 프로퍼티의 특성을 원하는 대로 수정(configurable이 true일 시) 할 수 있다.
- 쓰기 가능
프로프티 값의 쓰기 가능 여부는 writable로 조정한다. 아래 예제를 그대로 실행하면 조용히 실패하고 엄격 모드에서는 에러가 난다. ( "use strict"; )
var exObject = {};
Object.defineProperty(exObject, "a", {
value : 2,
writable : false, // 쓰기 금지
enumerable : true,
configurable : true
});
exObject.a = 3; // 엄격모드에서는 TypeError
exObject.a; // 2
- 설정 가능
프로퍼티가 설정 가능(configurable)하면 defineProperty(..)로 프로퍼티 서술자를 변경 할 수 있다. 아래의 예제를 보면 설정 불가한 프로퍼티의 서술자를 변경 하려고 하니 두 번째 defineProperty(..) 메서드 호출 시 에러가 난다. configurable은 일단 false가 되면 돌아올 수 없는 강을 건너게 되니 유의한다.
** configurable : false인 프로퍼티라도 writable : true -> false로 에러 없이 변경은 가능하지만 이것 또한 한번 false가 되면 다시 true로 되돌릴 수 없다.
var exObject = {};
Object.defineProperty(exObject, "a", {
value : 2,
writable : true,
enumerable : true,
configurable : false // 설정 불가
});
exObject.a = 3;
exObject.a; // 3
Object.defineProperty(exObject, "a", {
value : 2,
writable : true,
enumerable : true,
configurable : true
}); // TypeError
위 예제처럼 configurable : false로 설정하면 delete 연산자로 존재하는 프로퍼티의 삭제도 금지된다. 여기서 delete를 할당된 메모리를 해제할 때 쓰는 도구로 생각하면 안된다. 자바스크립트에서 delete는 객체 프로퍼티를 날려버리는 기능이 고작이다.
var exObject = {};
Object.defineProperty(exObject, "a", {
value : 2,
writable : true,
enumerable : true,
configurable : false // 설정 불가
});
exObject.a; // 2
delete exObject.a;
exObject.a; // 2
- 열거 가능성
enumerable은 for..in루프처럼 객체 프로퍼티를 열거하는 구문에서 해당 프로퍼티의 표출여부를 나타낸다. enumerable : false로 지정된 프로퍼티는 접근할 수는 있지만 루프 구문에서 감춰지게 된다.
2. Javascript Getter/Setter
A. [[Get]]
var exObject = {
a : 2
};
exObject.a; // 2
exObject.a는 누가 봐도 프로퍼티 접근이지만, 보이는 것처럼 a란 이름의 프로퍼티를 exObject에서 찾지 않는다. 명세에 따르면 실제로 이 코드는 exObject에 대해 [[Get]] 연산 ([[Get]] () 같은 함수 호출)을 한다.
기본으로 [[Get]] 연산은 주어진 이름의 프로퍼티를 먼저 찾아보고 있으면 그 값을 반환한다. 만약 프로퍼티를 찾아보고 없으면 [[prototype]] 링크를 따라가면서 수색 작전을 벌인다. 값을 어떻게 해도 찾을 수 없으면 undefined를 반환한다.
B. [[Put]]
[[put]]을 실행하면 내부적으로 이미 존재하는 프로퍼티에 대해 대략 다음의 확인 절차를 밟는다.
1) 프로퍼티가 접근 서술자 (accessor descriptor)인가? 맞으면 세터를 호출한다.
2) 프로퍼티가 writable : false인 데이터 서술자 (data descriptor)인가? 맞으면 비엄격 모드에서 조용히 실패하고 엄격모드에서는 TypeError를 발생한다.
3) 이외에는 프로퍼티에 값을 세팅한다.
객체에 존재하지 않는 프로퍼티라면 [[put]]은 더 미묘하고 복잡해진다.
여기서 접근 서술자란 프로퍼티가 게터 또는 세터 어느 한쪽이거나 동시에 게터/세터가 될 수 있게 정의한 것을 말한다. 접근 서술자에서는 프로퍼티의 값과 writable 속성은 무시되며 대신 프로퍼티의 겟/셋 속성이 중요하다.
아래 예제는 게터와 세터를 정의하는 방법이다.
var exObject = {
get a() { // a의 게터를 정의한다.
return 2;
}
};
Object.defineProperty(exObject, "b", {
get: function () { // b의 게터를 정의한다.
return this.a * 2;
},
enumerable : true
});
exObject.a; // 2
exObject.b; // 4
여기서 a의 게터가 정의되어 있으므로 exObject.a = 3 같이 할당문으로 값을 세팅하려고 하면 에러 없이 조용히 무시된다. 짐작하겠지만 프로퍼티 단위로 기본 [[put]] 연산을 오버라이드하는 세터가 정의돼야 한다. 게터와 세터는 항상 둘 다 선언하는 것이 좋다.
var exObject = {
get a() { // a의 게터를 정의한다.
return this.r;
},
set a(value) { // a의 세터를 정의한다.
this.r = value * 2;
}
};
exObject.a = 2;
exObject.a; // 4
참조 저서 : 카일 심슨, You Don't Know JS this & Object Prototypes, 한빛미디어, 71쪽
'Javascript' 카테고리의 다른 글
[Javascript] Javascript 프로토타입 (Prototype) (0) | 2017.08.31 |
---|---|
[Javascript] Javascript의 확장 금지, 봉인, 동결 (0) | 2017.08.31 |
[Javascript] Javascript의 실행 컨텍스트와 스코프 (0) | 2017.08.30 |
[Javascript] 함수 호이스팅 (Function Hoisting) (0) | 2017.08.29 |
[Javascript] 함수의 생성 (0) | 2017.08.29 |