Javascript Proxy 와 Reflect 간단 정리하기

최근 Proxy 와 Reflect 을 알게되어 간단히 정리해보고자 한다.

Javascript 에서 Proxy 는 객체를 Wrapping 하고 객체의 동작을 가로채 수정하거나, 추가 행동을 할 수 있게 해준다.

간단 예제

function Obj(value) { this.value = value }
Obj.prototype.prototype_value = 20
var origin_object = new Obj(10)
var proxy_object = new Proxy(origin_object, {
get: function (target, key, receiver) {
// target === origin_object
// key : 호출한 key
// receiver === proxy_object
if(key === 'proxy_value') {
return 30
}
return Reflect.get(target, key, receiver);
},
set: function(target, key, value, receiver) {
if(key === 'proxy_value') {
console.log(target, key, value, receiver)
return true
}
return Reflect.set(target, key, value, receiver);
}
});
proxy_object.proxy_value = 40console.log(
origin_object.value, // 10
origin_object.prototype_value, // 20
origin_object.proxy_value, // undefined
proxy_object.value, // 10
proxy_object.prototype_value, // 20
proxy_object.proxy_value, // 30
)
  1. Obj 객체를 생성 합니다.
  2. Proxy 로 생성한 Obj 를 Wrapping 합니다.
  3. proxy_object 의 proxy_value 를 호출합니다.
  4. 이때 Proxy 객체를 생성할때, 선언한 handler 에 get 함수가 있었기 때문에, 해당 proxy_object 의 프로퍼티를 호출 할때, handler 의 get 함수를 먼저 호출합니다.
  5. 만약 Proxy handler 의 반환자로 Reflect 를 반환했다면, origin_object 객체를 탐색합니다.

Proxy 에서 가로챌수 있는 항목들

Property

  • get
  • set
  • has
  • deleteProperty

Method

  • apply
  • construct

Object

  • getPrototypeOf
  • setPrototypeOf
  • isExtensible
  • preventExtensions
  • getOwnPropertyDescriptor
  • defineProperty
  • ownKeys

Javascript 에서의 Object 는 어떻게 동작할까

Javascript 에서 Object 는 프로퍼티로 구성되어 있으며, key 와 value 형태로 구성되어 있다.

각 프로퍼티는 object.key 혹은 object[“key”] 형태로 접근할 수 있다.

Object 의 프로퍼티에 접근 하게되면 [[Get]] 연산을 진행한다

[[Get]] 연산은 주어진 이름의 프로퍼티를 찾아보고 있으면, 그 값을 반환한다

만약 해당 객체의 프로퍼티가 없다면, [[Get]] 연산은 [[Prototype]] 링크를 따라 일치하는 프로퍼티 명이 나올 때까지 연쇄 과정을 반복하게 된다.

Object.prototype 까지 찾아가도 없다면, undefined 를 반환한다.

Object.prototype.a = 1var obj = {
get b() { return 2 }
}
Object.defineProperty(obj, 'c', {
get: function() { return 3 },
enumerable: false
})
console.log(
obj.a, // 1
obj.b, // 2
obj.c, // 3
)

Javascript Proxy 와 Reflect

Javascript Proxy 는 Handler 에서 지정한 연산자중에 Target Object 에서 발생한 행동을 가로채서 추가행동을 이어갈 수 있다.

Target Object 에서 [[Get]] 연산이 발생하면, Proxy Handler 에 정의한 get 함수가 먼저 호출하게 된다.

때문에, get 함수에 정의한 내용에 따라 Target Object 의 [[Get]] 연산과 별개로 동작이 가능하다.

만약 Target Object 의 [[Get]] 연산을 이어나가고 싶다면, Reflect.get 으로 처리가 가능하다

만약 Handler 에 get 을 따로 정의하지 않았다면, Target Object 의 [[Get]] 연산을 처리한다.

Proxy API 는 object 뿐만 아니라 function 의 행동 또한 가로챌 수 있다.

function 을 Proxy 할때는 apply(target, thisArg, args) 를 사용할 수 있다.

function Fn() { console.log('Fn') }var fn = new Proxy(Fn, {
apply: function(target, thisArg, argumentsList) {
if (argumentsList.length) {
console.log(...argumentsList)
}
return Reflect.apply(target, thisArg, argumentsList)
}
});
fn()
// Fn
fn('a', 'b')
// a b

Reflect 는 Proxy 와 동일한 Method 를 가지고 있으며, Proxy 내에서 Reflect 를 반환하면 사이드 이펙트 없이 사용이 가능하다.

또한 반드시 Proxy 내부에서만 사용할 필요없이 따로 사용이 가능하다.

Reflect.get({a: 10}, 'a')
// 10

Proxy.revocable

Proxy.revocable 는 Target Object 를 참조하던 Proxy 객체를 해제할 수 있는 revoke 를 추가적으로 제공한다.

Proxy 객체의 Target Object 는 원본이 null 이 되더라도 반영되지 않는다.

때문에, revoke 를 이용하면 명시적으로 제거가 가능하다.

var object = {
data: null
};
let {proxy, revoke} = Proxy.revocable(object, {
get: function (target, key, receiver) {
return Reflect.get(target, key, receiver);
},
set: function (target, key, value, receiver) {
return Reflect.set(target, key, value, receiver);
}
});
proxy.data = 10console.log(
object.data, // 10
proxy.data, // 10
)
object = nullconsole.log(
proxy.data, // 10
)
try {
// Proxy 해제
revoke()
proxy.data = 20
// Cannot perform 'set' on a proxy that has been revoked
} catch (err) {
console.error(err.message)
}

Proxy Polyfill

Proxy API 는 ES6 SPEC 이기 때문에, 최신 브라우저에서는 Proxy API 가 잘 지원하지만, ES5 이하를 지원하는 브라우저는 사용이 어렵다.

때문에 Babel transfer 만으로는 지원이 어렵다.

하지만 Google 팀에서 proxy-fill 만든 도구가 있으며, IE 9 까지 지원한다

모든 기능을 지원하지는 않으며 get, set, apply, construct 를 지원한다

Javascript is great We may not be great

Javascript is great We may not be great