ES6 In Depth: 프락시 (Proxy)

ES6 In Depth 시리즈는 ECMAScript 표준 6번째 에디션(줄여서 ES6)을 통해 JavaScript 에 새로 추가된 요소들을 살펴보는 시리즈입니다.

오늘 우리가 살펴보려는 것은 다음 코드입니다.

var obj = new Proxy({}, {
  get: function (target, key, receiver) {
    console.log(`getting ${key}!`);
    return Reflect.get(target, key, receiver);
  },
  set: function (target, key, value, receiver) {
    console.log(`setting ${key}!`);
    return Reflect.set(target, key, value, receiver);
  }
});

첫 예제 치고는 어렵습니다. 천천히 설명하겠습니다. 우선 우리가 만든 객체의 동작부터 살펴봅시다.

> obj.count = 1;
    setting count!
> ++obj.count;
    getting count!
    setting count!
    2

무슨 일이 일어난 건가요? 객체에 존재하는 속성에 접근하려는 시도가 가로채기 당했습니다. 우리가 "." 연산자를 오버로딩한 것입니다.

어떻게 한거죠?

컴퓨터 분야의 가장 멋진 기술은 가상화(virtualization) 기술입니다. 가상화는 무척 범용적인 기술로 놀라운 일을 해냅니다. 가상화 기술의 동작 방식은 다음과 같습니다.

  1. 임의의 사진을 찍습니다.

    (picture of a coal power plant)

    Photo credit: Martin Nikolaj Bech
  2. 사진 위의 사물 주변에 외곽선을 그립니다.

    (same photo, with the power plant circled)
  3. 이제 외곽선 내부 또는 외곽선 외부를 전혀 상상도 못할 다른 것으로 바꿉니다. 한가지 규칙만 지키면 됩니다. 바로 하위 호환성(Backwards Compatibility) 규칙입니다. 교체하는 것이 이전 것과 충분히 비슷하게 동작해야 합니다. 그래서 경계선 밖에선 어느 누구도 무엇이 바뀌었는지 눈치채지 못해야 합니다.

    (the circled part is replaced with a wind farm)

    Photo credit: Beverley Goodwin.

아마도 트루먼쇼매트릭스 같은 고전적인 SF 영화를 통해 이런 아이디어를 친숙하게 접해보셨을 것입니다. 이들 영화 속 주인공들은 경계선 안쪽에서 생활합니다. 그런데 경계선 밖의 나머지 세계는 정상적인 것처럼 꾸며진 인공적인 세계입니다.

하위 호환성(Backwards Compatibility) 규칙을 만족시키려면, 바꿔 넣는 것을 정교하게 만들 필요가 있습니다. 하지만 핵심은 외곽선을 적절히 그려넣는 데 있습니다.

외곽선이란, 다시 말해 API 로 구분지어진 경계를 의미합니다. 인터페이스 말입니다. 인터페이스는 서로 다른 2개의 코드가 어떻게 상호작용 하는지를 규정합니다. 그리고 각 코드가 서로 상대 코드에 기대하는 바를 규정합니다. 만약 시스템에 어떤 인터페이스가 설계되어 있다면, 당신을 위한 외곽선이 이미 주어져 있는 것입니다. 이제 당신은 인터페이스의 어느 한쪽을 교체할 수 있습니다. 그래도 다른 한쪽은 그런 교체 사실에 구애받지 않습니다.

인터페이스가 존재하지 않은 때가 바로 당신의 창의력이 필요한 때입니다. 가장 멋진 소프트웨어 업무는 기존에 없던 새로운 API 를 설계하고, 엄청난 노력을 통해 그 인터페이스를 실현하는 것입니다.

가상 메모리(virtual memory), 하드웨어 가상화(hardware virtualization), 도커(Docker), Valgrind, rr — 정도의 차이는 있지만, 이 프로젝트들은 모두 기존 시스템에 상상도 못한 새로운 인터페이스를 도입한 것들입니다. 어떤 경우에는 새로운 외곽선이 제대로 동작하도록 만들기 위해 몇 년의 시간, 새로운 운영체제의 기능, 심지어 새로운 하드웨어가 필요하기도 합니다.

멋진 가상화 결과물은 가상화 대상에 대한 새로운 차원의 이해가 있어야 가능합니다. 어떤 대상에 대한 API를 쓰려면 반드시 그 대상을 이해해야 합니다. 일단 대상을 이해하고 나면, 우리는 놀라운 일을 할 수 있습니다.

ES6 는 JavaScript 의 가장 기초적인 개념에 대한 가상화를 시도했습니다. 바로 객체 말입니다.

객체(object)가 뭔가요?

농담이 아닙니다. 진지하게 묻습니다. 잠시 멈춰보세요. 객체에 대해 생각해보세요. 객체가 무엇인지 생각이 정리되면 아래로 스크롤 해보세요.

(picture of Auguste Rodin’s sculpture, The Thinker)

Photo credit: Joe deSousa.

제겐 이 질문이 너무 어렵습니다! 이 질문에 대해 만족스러운 답을 들어본 적도 없습니다.

놀라운가요? 기초 개념을 정의하는 것은 언제나 어렵습니다. 기회가 되면 유클리드 기하학 요소들에 대한 처음 정의 몇 개를 살펴보세요. ECMAScript 랭귀지의 스펙 정의도 유클리드 기하학처럼 난해합니다. 특히 객체에 대한 정의는 별로 도움이 되지 않습니다. ECMAScript 는 객체를 “Object 타입에 속한 멤버들(member of the type Object)” 이라고 정의합니다.

나중에, “객체는 속성들의 집합 (An Object is a collection of properties)” 이라는 정의가 스펙에 추가되었습니다. 이 정의는 나쁘지 않습니다. 만약 지금 당장 객체에 대한 정의가 필요하다면 이 정의를 사용하는 것이 좋겠습니다. 객체의 정의에 대해 나중에 좀 더 이야기합시다.

좀 전에 어떤 대상에 대한 API를 쓰려면, 그 대상을 이해해야 한다 고 말했습니다. 아마 이번 글을 읽고 나면, 객체를 좀 더 이해할 수 있을 것입니다. 그래서 놀라운 일을 해낼 수 있을 것입니다.

ECMAScript 표준화 위원회의 발자취를 따라가 봅시다. 그래서 JavaScript 객체에 대한 API, 즉 인터페이스를 정의하기 위해 무엇이 필요한지 알아 봅시다. 어떤 종류의 메소드가 필요할까요? 객체로 무엇을 할 수 있을까요?

객체로 무엇을 할 수 있는지는 객체가 무엇인지에 따라 결정됩니다. DOM Element 객체가 할 수 있는 일이 따로 있고, AudioNode 객체가 할 수 있는 일이 따로 있습니다. 하지만 모든 객체가 공유하는 기능도 아주 조금 있습니다.

  • 객체는 속성을 갖습니다. 우리는 속성을 조회하거나(get), 설정하거나(set), 제거할(delete) 수 있습니다.
  • 객체는 프로토타입을 갖습니다. JS 랭귀지는 프로토타입을 이용해서 상속(inheritance)을 처리합니다.
  • 어떤 객체는 함수이거나 생성자입니다. 우리는 그런 객체들을 호출할 수 있습니다.

JS 프로그램이 객체로 하는 거의 모든 일은 속성과 프로토타입과 함수를 이용하는 일입니다. 심지어 Element 객체나 AudioNode 객체의 특별한 행동도 메소드 호출을 통해 처리되는데, 메소드란 단지 상속된 함수 속성일 뿐입니다.

그래서 ECMAScript 표준화 위원회가 객체의 14개 내부 메소드(internal method)를 정의할 때, 즉 객체의 공통 인터페이스를 정의할 때, 이 3가지 기초 요소들(속성, 프로토타입, 함수)에 집중했던 것은 당연한 일이라 하겠습니다.

ES6 표준의 도표5와 도표6 링크에서 표준이 정하는 내부 메소드들의 목록을 모두 볼 수 있습니다. 이 글에서는 그 중 일부만 설명하겠습니다. 다소 이상해 보이는 이중 꺽쇠 표시는, [[ ]], 이들이 내부 메소드라는 사실을 강조합니다. 내부 메소드는 일반 JS 코드에게는 가려져 있습니다. 그래서 우리는 다른 일반 메소드들처럼 내부 메소드를 호출(call)하거나 제거(delete)할 수 없습니다.

  • obj.[[Get]](key, receiver) – 속성의 값을 가져옵니다.

    obj.prop 또는 obj[key] 같은 JS 코드를 사용할 때 호출됩니다.

    obj 는 속성을 조회 중인 현재의 대상 객체입니다. receiver 는 속성 조회를 처음 시작했던 대상 객체입니다. 어떤 경우에는 속성 값을 찾기 위해 여러 객체들을 조회해야 합니다. objreceiver 객체의 프로토타입 체인(prototype chain)에 속한 객체들 중 하나일 것입니다.

  • obj.[[Set]](key, value, receiver) – 객체의 속성에 값을 할당합니다.

    obj.prop = value 또는 obj[key] = value 같은 JS 코드를 사용할 때 호출됩니다.

    obj.prop += 2 같은 할당문의 경우, [[Get]] 메소드가 먼저 호출되고, 그다음 [[Set]] 메소드가 호출됩니다. ++-- 구문도 마찬가지입니다.

  • obj.[[HasProperty]](key) – 속성이 존재하는지 테스트합니다.

    key in obj 같은 JS 코드를 사용할 때 호출됩니다.

  • obj.[[Enumerate]]()obj 에 존재하는 열거 가능한 속성들을 나열합니다.

    for (key in obj) ... 같은 JS 코드를 사용할 때 호출됩니다.

    이 메소드는 이터레이터 객체를 리턴합니다. forin 루프가 객체에 존재하는 속성들의 이름을 얻는 방식입니다.

  • obj.[[GetPrototypeOf]]()obj 의 프로토타입을 리턴합니다.

    obj.__proto__ 또는 Object.getPrototypeOf(obj) 같은 JS 코드를 사용할 때 호출됩니다.

  • functionObj.[[Call]](thisValue, arguments) – 함수를 호출합니다.

    functionObj() 또는 x.method() 같은 JS 코드를 사용할 때 호출됩니다.

    이 메소드는 있을 수도 있고 없을 수도 있습니다 (Optional). 모든 객체가 함수인 것은 아닙니다.

  • constructorObj.[[Construct]](arguments, newTarget) – 생성자를 실행합니다.

    예를 들어 new Date(2890, 6, 2) 같은 JS 코드를 사용할 때 호출됩니다.

    이 메소드는 있을 수도 있고 없을 수도 있습니다 (Optional). 모든 객체가 생성자는 아닙니다.

    newTarget 인자는 서브클래싱(subclassing)을 위해 사용합니다. 서브클래싱에 대해서는 나중에 다른 글을 통해 설명할 것입니다.

아마 다른 7개 메소드들도 짐작하실 수 있을 것입니다.

ES6 표준 전반에 걸쳐, 가능한 모든 경우에, 객체와 관련된 문법이나 빌트인(builtin) 함수는 모두 이들 14개의 내부 함수로 정의됩니다. ES6 는 객체의 두뇌 주변에 명확한 경계선을 그렸습니다. 프락시(proxy)를 이용해서 할 수 있는 일이란 보통의 두뇌를 임의의 JS 코드로 교체하는 일입니다.

이들 내부 메소드를 교체하는 방법에 대해 이야기 하기 전에 꼭 기억하세요. 우리는 지금 obj.prop 같은 구문, Object.keys() 같은 빌트인 함수, 그리고 기타등등 JS의 핵심 문법을 오버라이딩하려는 것이라는 사실을 말입니다.

Proxy

ES6는 Proxy 라는 새로운 글로벌 생성자를 정의합니다. 이 생성자는 2개의 인자를 요구합니다. target 객체와 handler 객체입니다. 간단히 예를 들면 다음과 같은 모습이 될 것입니다.

var target = {}, handler = {};
var proxy = new Proxy(target, handler);

handler 객체는 잠시 미뤄두고 proxy 객체와 target 객체가 어떻게 연결되는지 살펴봅시다.

proxy 객체의 동작 방식을 한 문장으로 설명할 수 있습니다. proxy 객체는 모든 내부 메소드 호출을 target 객체로 전달합니다. 즉, 누군가 proxy.[[Enumerate]]() 메소드를 호출하면, target.[[Enumerate]]() 메소드가 그 결과를 리턴합니다.

테스트 해봅시다. proxy.[[Set]]() 메소드가 호출될만한 코드를 실행시켜 봅시다.

proxy.color = "pink";

좋습니다. 무슨 일이 일었났나요? proxy.[[Set]]() 메소드가 호출되면 자동적으로 target.[[Set]]() 메소드가 호출될 겁니다. 그래서 target 객체에 새로운 속성을 만들 것입니다. 그랬나요?

> target.color
    "pink"

그랬습니다. 그리고 나머지 내부 메소드들도 마찬가지로 동작합니다. 이 프록시 객체는 대부분의 경우 타겟 객체인 것처럼 동작합니다.

이런 환상이 깨지는 한계 상황이 존재합니다. proxy !== target 프락시는 타겟과 다릅니다. 그래서 proxy 는 target 이 성공하는 타입 체크에 실패하곤 합니다. 예를 들어 proxy 의 target 이 DOM Element 인 경우, proxy 자신은 진짜로는 Element 가 아닙니다. 그래서 document.body.appendChild(proxy) 같은 코드는 TypeError 를 내면서 실패하게 됩니다.

프락시 핸들러 (Proxy handler)

이제 handler 객체를 살펴봅시다. handler 객체가 프락시를 유용하게 만듭니다.

handler 객체의 메소드들을 통해 프락시 객체의 모든 내부 메소드들을 오버라이드할 수 있습니다.

예를 들어 객체의 속성에 값을 할당하는 모든 시도를 가로채고 싶다면, 그냥 handler.set() 메소드를 정의하면 됩니다.

var target = {};
var handler = {
  set: function (target, key, value, receiver) {
    throw new Error("Please don't set properties on this object.");
  }
};
var proxy = new Proxy(target, handler);

> proxy.name = "angelina";
    Error: Please don't set properties on this object.

handler 객체로 오버라이딩 가능한 메소드들의 목록은 Proxy 에 대한 MDN 문서를 보면 알 수 있습니다. 목록에는 14개의 메소드들이 존재합니다. 이 메소드들 각각이 ES6 가 정의하는 14개의 내부 메소드들에 해당합니다.

모든 handler 메소드들은 옵셔널입니다. 만약 내부 메소드가 handler 객체에 의해 가로채어지지 않는다면, 우리가 이전에 본 것처럼 해당 메소드 호출은 그냥 target 객체로 전달됩니다.

예제: “불가능한” 자동 생성 객체

우리는 이제 프락시를 이용해서 신기한 일을 해낼 수 있을만큼 프락시에 대해 충분히 알게 되었습니다. 프락시가 없다면 해낼 수 없는 일 말입니다.

여기 첫번째 연습 과제가 있습니다. Tree() 함수를 만들어 봅시다. 이 함수는 다음 같은 일을 할 수 있어야 합니다.

> var tree = Tree();
> tree
    { }
> tree.branch1.branch2.twig = "green";
> tree
    { branch1: { branch2: { twig: "green" } } }
> tree.branch1.branch3.twig = "yellow";
    { branch1: { branch2: { twig: "green" },
                 branch3: { twig: "yellow" }}}

branch1, branch2, branch3 같은 모든 중간 객체들이 마법처럼 필요할 때마다 자동으로 생성되는 것에 주의합시다. 편리해 보이지 않나요? 어떻게 동작하는 걸까요?

지금까지는 이런 일을 가능하게 하는 방법이 없었습니다. 하지만 프락시를 이용하면 아주 짧은 코드로 이런 일을 해낼 수 있습니다. 단지 tree.[[Get]]() 메소드에 끼어들기만 하면 됩니다. 직접 도전해보고 싶다면, 아래에 있는 코드를 보기 전에 직접 구현해보세요.

(picture of a tap in a maple tree)

나무에 끼어드는 방법 (JS 에는 적절치 않음). Photo credit: Chiot’s Run.

여기 제가 만든 해답이 있습니다.

function Tree() {
  return new Proxy({}, handler);
}

var handler = {
  get: function (target, key, receiver) {
    if (!(key in target)) {
      target[key] = Tree();  // auto-create a sub-Tree
    }
    return Reflect.get(target, key, receiver);
  }
};

마지막의 Reflect.get() 구문에 주의합시다. 프락시 핸들러 메소드를 구현할 때 “이제 그냥 원래 하던 동작을 할 수 있도록 target 객체로 메소드 호출을 전달해주세요” 라고 말하는 기능이 꼭 필요하다는 사실이 밝혀졌습니다. 그래서 ES6 는 14개의 메소드들과 함께 새로운 Reflect 객체를 정의했습니다. 바로 이 용도로 쓰기 위해서 말입니다.

예제: 읽기전용 뷰 (read-only view)

혹시나 프락시가 쓰기 쉽다는 잘못된 인상을 제공한 것이 아닌지 모르겠습니다. 프락시가 쓰기 쉬운지 어떤지 알아보기 위해 예제를 하나 더 살펴봅시다.

이번에 우리가 해결할 과제는 조금 더 복잡합니다. 이제 우리는 readOnlyView(object) 함수를 구현해야 합니다. 이 함수는 임의의 객체를 인자로 전달받아서 그 객체와 똑같이 동작하는 프락시 객체를 리턴합니다. 하지만 리턴된 객체는 변경할 수 없습니다. 그래서 예를 들자면 다음처럼 동작해야 합니다.

> var newMath = readOnlyView(Math);
> newMath.min(54, 40);
    40
> newMath.max = Math.min;
    Error: can't modify read-only view
> delete newMath.sin;
    Error: can't modify read-only view

어떻게 구현하면 될까요?

첫번째 단계는 타겟 객체를 변경할 수 있는 모든 내부 메소드들을 가로채는 것입니다. 5개의 메소드들이 여기에 해당합니다.

function NOPE() {
  throw new Error("can't modify read-only view");
}

var handler = {
  // Override all five mutating methods.
  set: NOPE,
  defineProperty: NOPE,
  deleteProperty: NOPE,
  preventExtensions: NOPE,
  setPrototypeOf: NOPE
};

function readOnlyView(target) {
  return new Proxy(target, handler);
}

이 코드는 동작합니다. 이 코드는 읽기전용 뷰(read-only view)가 할당 작업, 신규 속성 정의 작업 등을 하지 못하도록 막습니다.

이 코드에 구멍은 없을까요?

가장 큰 문제점은 [[Get]] 메소드(그리고 다른 몇개의 메소드)가 여전히 수정 가능한 객체를 리턴한다는 것입니다. 그래서 만약 어떤 객체 x 가 읽기전용 뷰(read-only view)라고 하더라도 x.prop 객체는 수정 가능합니다! 이건 큰 구멍입니다.

이 구멍을 메꾸려면 handler.get() 메소드를 수정해야 합니다.

var handler = {
  ...

  // Wrap other results in read-only views.
  get: function (target, key, receiver) {
    // Start by just doing the default behavior.
    var result = Reflect.get(target, key, receiver);

    // Make sure not to return a mutable object!
    if (Object(result) === result) {
      // result is an object.
      return readOnlyView(result);
    }
    // result is a primitive, so already immutable.
    return result;
  },

  ...
};

이것도 아직 완벽하지 않습니다. getPrototypeOfgetOwnPropertyDescriptor 같은 다른 메소드들에도 비슷한 코드를 적용해야 합니다.

아직도 문제가 더 존재합니다. 이런 종류의 프락시에 있는 getter 나 다른 메소드가 호출될 때, 메소드에 전달되는 this 는 통상 프락시 객체 자신입니다. 하지만 우리가 조금 전에 본 것처럼, 많은 접근자들과 메소드들이 프락시 객체가 통과할 수 없는 타입 체킹을 수행합니다. 이쯤 되면 타겟 객체를 프락시 객체로 바꾸는 것이 더 좋을 것 같습니다. 가능할까요?

지금까지의 논의로부터 프락시를 만드는 것은 쉽지만, 직관대로 동작하는 프락시를 만드는 것은 무척 어렵다는 사실을 알 수 있습니다.

마무리하자면

  • 프락시는 무엇에 알맞은가요?

    프락시는 어떤 객체에 대한 접근을 관찰하거나 로그를 남길 때 확실히 유용합니다. 프락시는 디버깅할 때 참 좋습니다. 테스트 프레임워크들이 가짜 객체 (mock object)를 만들 때 프락시를 이용할 수 있습니다.

    프락시는 보통의 객체보다 아주 약간 느린 동작이 필요한 경우에 유용합니다. 예를 들면 게으르게 반환되는 속성 (lazily populating properties) 같은 경우입니다.

    제가 아주 싫어하는 시나리오지만, 프락시를 사용하는 코드 내부에서 어떤 일이 일어나고 있는지 확인하는 가장 좋은 해결책 중 하나는… 바로 프락시의 핸들러 객체를 다시 다른 프락시로 감싸는 것입니다. 그래서 핸들러의 메소드가 호출될 때마다 콘솔에 로그가 출력되도록 만드는 것입니다.

    프락시는 우리가 readOnlyView 로 했던 것처럼 어떤 객체에 대한 접근을 제한하기 위해 사용할 수 있습니다. 어플리케이션 코드에서는 이런 유즈케이스가 흔하지 않습니다. 하지만 Firefox 는 내부적으로 프락시를 사용해서 서로 다른 도메인들 사이에 존재하는 보안 장벽 (security boundary)을 구현합니다. 이것이 Firefox 보안 모델의 핵심입니다.

  • Proxy ♥ WeakMap. readOnlyView 예제에서, 우리는 객체에 접근할 때마다 매번 새로운 프락시를 생성했습니다. 우리가 생성하는 프락시를 WeakMap 에 캐시하면 아주 많은 메모리를 절약할 수 있습니다. 그러면 어떤 객체가 여러번 readOnlyView 에 전달되더라도, 프락시는 한번만 생성되도록 만들 수 있습니다.

    이것은 WeakMap 의 아주 유용한 유스케이스입니다.

  • 폐기 가능한 프락시(revocable proxy). ES6 는 또 Proxy.revocable(target, handler) 이라는 함수도 제공합니다 이 함수는 new Proxy(target, handler) 함수처럼 프락시 객체를 리턴합니다. 하지만 이 함수를 통해 생성되는 프락시는 나중에 폐기시킬 수 있습니다. (Proxy.revocable 함수는 어떤 객체를 리턴합니다. 이 객체에는 .proxy 속성과 .revoke 메소드가 존재합니다.) 일단 프락시를 폐기시키면, 해당 프락시는 더이상 동작하지 않습니다. 폐기된 이후 호출되는 내부 메소드들은 예외를 발생시킵니다.

  • 객체의 불변조건(object invariants). 어떤 경우에는, ES6 에서 프락시 핸들러 메소드가 target 객체의 상태와 일치하는 결과를 보고하도록 강제해야 할 때가 있습니다. 이렇게 하는 이유는 모든 객체들(심지어 프락시 객체들까지 포함해서)에서 변경불가(immutability) 원칙을 관철시키기 위해서 입니다. 예를 들어, 타겟 객체가 확장 불가능(inextensible) 하지 않은데, 프락시 객체가 확장 불가능하다고 주장할 수는 없습니다.

    정확한 규칙을 설명하기에는 지면이 부족합니다. 하지만 만약 "proxy can't report a non-existent property as non-configurable" 같은 에러 메시지를 보게 된다면, 바로 이런 이유 때문입니다. 이때 좋은 처방은 프락시가 자신에 대해 보고하는 바를 수정하는 것입니다. 또다른 처방은 프락시가 보고하는 내용에 따라 그때그때 타겟을 수정하는 것입니다.

결국 객체가 뭐죠?

우리가 잠정적으로 동의했던 정의는 “객체란 속성들의 집합이다.” 였습니다.

이 정의는 그다지 만족스럽지 않습니다. 프로토타입과 호출가능성(callability) 모두를 별거 아니라고 생각해도 말입니다. 제 생각에 “집합”이라는 단어가 너무 모호합니다. 이 때문에 프락시에 대한 정의도 모호해집니다. 프락시의 핸들러 메소드들은 무슨 짓이든 할 수 있을 것입니다. 프락시의 핸들러 메소드들이 매번 임의의 결과를 리턴하는 것도 가능할 것입니다.

객체가 무엇을 할 수 있는지 식별함으로써, 이 메소드들을 표준화함으로써, 그리고 일급(first-class) 명세서를 추상화하여 모든 사람들이 사용할 수 있게 함으로써 ECMAScript 표준화 위원회는 더 큰 가능성을 만들었습니다.

객체는 이제 더 자유로운 무엇이 되었습니다.

이제 “객체가 무엇인가요?”에 대한 가장 정직한 대답은 필수적인 12개의 내부 메소드들을 갖는 무엇일 것입니다. 객체란 JS 프로그램에서 사용되는 [[Get]] 메소드와 [[Set]] 메소드와 기타 메소드들을 갖는 무엇입니다.


이제 객체에 대한 이해가 조금 높아졌나요? 그건 잘 모르겠군요! 우리가 놀라운 일을 해낸 건가요? 그건 맞습니다. 우리는 지금까지 JS 로 할 수 없던 일을 해낸 것입니다.

프락시를 지금 당장 쓸 수 있나요?

아뇨! 웹에서는 지금 당장 쓸 수 없습니다. Firefox 와 Microsoft Edge 브라우저만 프락시를 지원합니다. 더구나 폴리필도 존재하지 않습니다.

Node.js 에서 프락시를 쓰려면 디폴트로 비활성화되어 있는 옵션(--harmony_proxies)을 켜야하고 동시에 harmony-reflect 폴리필을 사용해야 합니다 V8 엔진이 Proxy 스펙의 옛날 버전에 따라 구현되었기 때문입니다. (본 블로그 글은 이에 관해 잘못된 내용을 전달하고 있었습니다. Mörre 와 Aaron Powell 의 커멘트 덕분에 오류를 바로잡을 수 있었습니다.)

그렇습니다. 편한 마음으로 프락시를 실험해보세요! 비슷한 객체들의 복사본이 수천개 존재하는 마치 거울로 만들어진 방 같은 상황을 만들어보세요. 아마 아무것도 디버깅할 수 없을 것입니다! 이제 프락시를 사용해야 할 바로 그 때입니다. 다만 프락시 코드를 상용으로 사용하기에는 아직 이르다는 것만 일러둡니다.

프락시는 2010년에 Andreas Gal 가 처음 구현하고 Blake Kaplan 가 처음 리뷰했습니다. 그리고 나서 표준화 위원회가 스펙을 다시 설계했습니다. 2012년에 Eddy Bruel 가 새로운 스펙을 구현했습니다.

Reflect 는 제가 구현해서 Jeff Walden 으로부터 리뷰 받았습니다. 이번 주말부터 Firefox Nightly 버전에 포함될 것입니다. 다만 Reflect.enumerate() 는 아직 구현되지 않았습니다.

다음엔 ES6 의 기능 중에 가장 논쟁이 많은 기능에 대해 이야기하려고 합니다. 아마도 그 기능을 Firefox 에 직접 구현한 사람보다 더 잘 설명할 수 있는 사람은 없을 것입니다. 다음 기사는 모질라 엔지니어 Eric Faust 가 ES6 클래스에 대해 깊게 설명하는 글이 될 것입니다. 함께 해주세요.

이 글은 Jason Orendorff 가 쓴 ES6 In Depth: Proxies 의 한국어 번역본입니다.

작성자: ingeeKim

"누구에게나 평등하고 자유로운 웹"에 공감하는 직장인.

ingeeKim가 작성한 문서들…


댓글이 없습니다.

댓글 쓰기