[es5] 읽기전용으로 키를 설정하기

개요

es5의 Object.defineProperty를 이용해 특정 오브젝트의 키를 읽기전용으로 만드는 건 어렵지 않습니다만, 크로스브라우저 이슈 때문에 선뜻 쓰게 되지 않습니다.
IE의 경우 11은 거의 완벽한 es5머신이지만 10 이하는 호환성에 큰 문제가 있기 때문에 이를 고려하지 않을 수 없습니다.
따라서 보다 부드러운 폴리필을 처리해야 할 필요가 있습니다.

우선 날로 쓸 수는 없으니 어디에 폴리필용 함수를 정의할지 생각해봐야 합니다.
아무 객체의 아무 속성이나 읽기 전용으로 하기 위한 적당한 위치는 실로 다양합니다.
전역함수, Object의 커스텀 메소드 정의 등이 후보가 될 수 있습니다.

헌데 this컨텍스트의 사용을 용이하게 하기 위해 좀 과격하고 올드한 느낌이 있습니다만 Object.prototype에 직접 정의하는 방법을 쓸까합니다. 우선 이 방법을 익히고 나면 이후 다른 저장소에 구현하는 것은 문제가 되지 않을 것입니다.

함수작성

우선 Object.defineProperty 메소드가 존재한다면 읽기전용속성을 정의할 수 있다고 가정해도 좋겠죠(아닌 경우가 있긴 합니다만 >.<) 따라서 다음과 같은 사전 디텍팅을 정의합니다.

var es5 = Object.defineProperty ? 1 : 0;

이제 디텍팅 결과에 따라 반응하는 readOnly함수를 작성해보죠.

var readOnly = function(k, v){
  if(es5){ //es5를 지원하는 경우
    Object.defineProperty(this, k, {value:v});
  }else{ //지원안하면 그냥 키에 쓴다.
    this[k] = v;
  }
};

함수는 간단하게 es5를 지원하면 readonly로 설정하고 아닌 경우는 평소처럼 키를 설정해주는 식으로 폴리필합니다.
이제 이걸 Object.prototype에 넣어주면 끝이겠죠?

Object.prototype.readonly = readOnly;

으음 근데 뭔가 찝찝합니다.

Object.prototype의 키가 열거되는 문제

위의 코드를 사용하면 분명히 다음과 같은 코드가 바르게 작동합니다.

var obj = {};
//읽기전용 설정
obj.readonly('a', 3);

console.log(obj.a); //3

obj.a = 5; //5로 바꿔보지만
console.log(obj.a); //여전히 3!

하지만 이 obj를 for in으로 루프를 돌면 readonly도 키에 걸려서 나옵니다.

var key;
for(key in obj){
  console.log(key); //a, readonly가 나온다!
}

물론 prototype의 키이므로 hasOwnProperty를 사용하면 제거할 수 있습니다.

var key;
for(key in obj){
  if(obj.hasOwnProperty(key)){
     console.log(key); //a만 나온다!
  }
}

헌데 Object.prototype에 정의된 hasOwnProperty나 toString등은 안나오는데 readonly는 왜 열거되는 것일까요.
그것은 Object.prototype에 설정할 때 열거불가로 설정하지 않았기 때문입니다.
따라서 위에서 단순히 Object.prototype.readonly = readOnly 하던것도 defineProperty를 통해 잡아줘야합니다.

Object.defineProperty(Object.property, 'readonly', {value:readOnly});

이제서야 추가한 readonly도 for in에서 함부로 열거되지 않게 되었습니다!

응용하기

우선 폴리필의 전체 소스는 다음과 같습니다.

(function(){
  var es5 = Object.defineProperty ? 1 : 0,
    readOnly = function (k, v){
      es5 ? Object.defineProperty(this, k, {value:v}) : this[k] = v;
    };
  if(es5){
    Object.defineProperty(Object.prototype, 'readOnly', {value:readOnly});
  }else{
    Object.prototype.readOnly = readOnly;
  }
})();

위 코드에서의 현재 극복하지 못하는 약점은 es5를 지원하지 않는 브라우저에서 readOnly가 열거되지 않게 할 방법이 없다는 것입니다.
(역시 hasOwnProperty는 생활화하는걸로 ^^)

이제 간단히 클래스를 하나 만들어서 생성자에서 받은 인자를 읽기전용으로 만들어보죠.

var TestClass = function(a){
  this.readOnly('a', a); //a를 읽기전용으로 설정한다.
};

var instance = new TestClass(3);
console.log(instance.a); //3

instance.a = 5; //5로 바꿔보지만
console.log(instance.a); //여전히 3이다!

결론

이미 ECMA2015 즉 es6표준도 재정된 마당에 구형 브라우저 때문에 es5의 기능을 안쓸 수는 없는 노릇입니다.
작은 부분부터 폴리필하더라도 런타임 디버깅 대상을 줄이고 컨텍스트의 가정을 확신으로 바꿀 수 있는 장치가 있다면 적극적으로 활용할 노력을 해볼 때인가 싶습니다.
우선은 읽기전용 속성에 대해서 처리해봤지만 이러한 작은 es5폴리필을 꾸준히 연재할 예정입니다.