본문 바로가기
Javascript

[Javascript] Javascript 프로퍼티 서술자 (Getter/Setter)

by parkjp 2017. 8. 31.

 

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쪽

반응형