메타 프로그래밍

메타 프로그래밍 (Meta programming)

ProxyReflect 객체를 사용하면 기본적인 언어 연산(예: 속성 조회, 할당, 열거, 함수 호출 등)을 가로채고 사용자 정의 동작을 정의할 수 있습니다. 이 두 객체의 도움으로 JavaScript의 메타 수준(meta level)에서 프로그래밍할 수 있습니다.

출처: Meta programming

본문

프록시 (Proxies)

Proxy 객체는 특정 연산을 가로채고 사용자 정의 동작을 구현할 수 있게 해줍니다.

예를 들어 객체의 속성을 가져오는 경우:

const handler = {
 get(target, name) {
   return name in target ? target[name] : 42;
 },
};

const p = new Proxy({}, handler);
p.a = 1;
console.log(p.a, p.b); // 1, 42

Proxy 객체는 target(여기서는 빈 객체)과, 그 안에 get 트랩이 구현된 handler 객체를 정의합니다. 여기서 프록시된 객체는 정의되지 않은 속성을 가져올 때 undefined를 반환하지 않고 대신 숫자 42를 반환합니다.

추가 예제는 Proxy 참조 페이지에서 볼 수 있습니다.

용어 (Terminology)

프록시의 기능을 설명할 때 다음 용어가 사용됩니다.

  • handler: 트랩을 담고 있는 자리 표시자 객체입니다.
  • traps: 속성 접근을 제공하는 메서드입니다. (운영체제의 트랩 개념과 유사합니다.)
  • target: 프록시가 가상화하는 객체입니다. 종종 프록시의 저장 백엔드로 사용됩니다. 객체의 비확장성(non-extensibility) 또는 비구성 가능(non-configurable) 속성에 관한 불변식(invariants, 변하지 않는 의미론)이 target에 대해 검증됩니다.
  • invariants: 사용자 정의 연산을 구현할 때 변하지 않는 의미론을 불변식이라고 합니다. 핸들러의 불변식을 위반하면 TypeError가 발생합니다.

핸들러와 트랩

다음 표는 Proxy 객체가 사용할 수 있는 트랩을 요약합니다. 자세한 설명과 예제는 참조 페이지를 참고하세요.

핸들러 / 트랩 가로채는 연산
handler.getPrototypeOf() Object.getPrototypeOf(), Reflect.getPrototypeOf(), __proto__, Object.prototype.isPrototypeOf(), instanceof
handler.setPrototypeOf() Object.setPrototypeOf(), Reflect.setPrototypeOf()
handler.isExtensible() Object.isExtensible(), Reflect.isExtensible()
handler.preventExtensions() Object.preventExtensions(), Reflect.preventExtensions()
handler.getOwnPropertyDescriptor() Object.getOwnPropertyDescriptor(), Reflect.getOwnPropertyDescriptor()
handler.defineProperty() Object.defineProperty(), Reflect.defineProperty()
handler.has() 속성 질의: foo in proxy, 상속 속성 질의: foo in Object.create(proxy), Reflect.has()
handler.get() 속성 접근: proxy[foo], proxy.bar, 상속 속성 접근: Object.create(proxy)[foo], Reflect.get()
handler.set() 속성 할당: proxy[foo] = bar, proxy.foo = bar, 상속 속성 할당: Object.create(proxy)[foo] = bar, Reflect.set()
handler.deleteProperty() 속성 삭제: delete proxy[foo], delete proxy.foo, Reflect.deleteProperty()
handler.ownKeys() Object.getOwnPropertyNames(), Object.getOwnPropertySymbols(), Object.keys(), Reflect.ownKeys()
handler.apply() proxy(..args), Function.prototype.apply() and Function.prototype.call(), Reflect.apply()
handler.construct() new proxy(...args), Reflect.construct()

해지 가능한 프록시 (Revocable Proxy)

Proxy.revocable() 메서드는 해지 가능한(revocable) Proxy 객체를 만드는 데 사용됩니다. 이는 revoke 함수를 통해 프록시를 해지하고 프록시를 꺼버릴 수 있다는 뜻입니다.

그 후 프록시에 대한 어떤 연산도 TypeError를 발생시킵니다.

const revocable = Proxy.revocable(
 {},
 {
   get(target, name) {
     return `[[${name}]]`;
   },
 },
);
const proxy = revocable.proxy;
console.log(proxy.foo); // "[[foo]]"

revocable.revoke();

console.log(proxy.foo); // TypeError: Cannot perform 'get' on a proxy that has been revoked
proxy.foo = 1; // TypeError: Cannot perform 'set' on a proxy that has been revoked
delete proxy.foo; // TypeError: Cannot perform 'deleteProperty' on a proxy that has been revoked
console.log(typeof proxy); // "object", typeof doesn't trigger any trap

리플렉션 (Reflection)

Reflect는 가로챌 수 있는 JavaScript 연산을 위한 메서드를 제공하는 내장 객체입니다. 그 메서드는 프록시 핸들러의 메서드와 동일합니다.

Reflect는 함수 객체가 아닙니다.

Reflect는 핸들러에서 target으로 기본 연산을 전달(forwarding)하는 데 도움이 됩니다.

예를 들어 Reflect.has()를 사용하면 in 연산자를 함수로 얻을 수 있습니다.

Reflect.has(Object, "assign"); // true

더 나은 apply() 함수

Reflect 이전에는 주어진 this 값과 배열(또는 배열형 객체)로 제공된 인자로 함수를 호출하기 위해 주로 Function.prototype.apply() 메서드를 사용했습니다.

Function.prototype.apply.call(Math.floor, undefined, [1.75]);

Reflect.apply를 사용하면 더 간결하고 이해하기 쉬워집니다.

Reflect.apply(Math.floor, undefined, [1.75]);
// 1

Reflect.apply(String.fromCharCode, undefined, [104, 101, 108, 108, 111]);
// "hello"

Reflect.apply(RegExp.prototype.exec, /ab/, ["confabulation"]).index;
// 4

Reflect.apply("".charAt, "ponies", [3]);
// "i"

속성 정의 성공 여부 확인

Object.defineProperty는 성공하면 객체를 반환하고, 그렇지 않으면 TypeError를 발생시킵니다. 따라서 속성을 정의하는 동안 발생한 오류를 잡으려면 try...catch 블록을 사용해야 합니다. Reflect.defineProperty()는 불리언 성공 상태를 반환하므로 여기서는 if...else 블록만 사용하면 됩니다.

if (Reflect.defineProperty(target, property, attributes)) {
 // success
} else {
 // failure
}

더 알아보기