Javascript ES6 Proxy

2017.04.16 20:10JavaScript

여기저기서 자바스크립트의 새로운 표준인 ECMA2015(ES6) 문법이 많이 쓰이고 있다. arrow, spread부터 class까지 정말 다양한 스펙들이 추가되었다. 그 중에서 아무리 봐도 잘 이해가 안가는 객체가 있었으니 바로 Proxy이다. 구글링을 해봐도 명쾌하게 이해되는 글은 찾아볼 수가 없었다. 이번 기회에 proxy가 뭔지 파헤쳐 보도록 하자.


처음 Proxy 객체를 접하고 이 글을 적은지 2년이 되어가는데, 다시 보니 많이 부족한 글인 것 같다. 따라서 그 동안 공부했던 내용을 약간 보충해 보았다.

Proxy ?

 먼저 Proxy란 단어의 사전적 의미부터 알아보자. 네이버 사전에서 Proxy를 검색해 보았다.
Proxy란 대리, 대리인, (측정・계산하려는 다른 것을 대표하도록 이용하는) 대용물.

 쉽게말해 뭔가를 대리해주는 객체를 의미한다. Proxy란 용어는 Server-Side에서도 많이 사용되는 용어이다. front-proxy, reverse-proxy 서버 등 중간에서 무언가의 역할을 대리해주는 서버들을 가리킨다. 그렇다면 javascript es6의 proxy는 도대체 뭘 대리하는 것일까. MDN Proxy 문서를 한번 찾아 보았다. 

The Proxy object is used to define custom behavior for fundamental operations (e.g. property lookup, assignment, enumeration, function invocation, etc).

정리해보면 Proxy객체는 자바스크립트에서의 기본 작업, 예를들면 속성 조회, 할당, 열거, 함수 호출등에 대한 행위에 대해 사용자의 커스텀 동작을 정의할 때 사용할 수 있다는 것이다. 오 듣고보니 기본 작업을 오버라이딩 하는 느낌이 든다. 실제로 한번 사용해 보도록 하자. Proxy 객체는 다음과 같이 사용한다.



var p = new Proxy(target, handler);

target 에는 Proxy로 랩핑할 대상 객체를 지정해 줄 수 있다. 기본 배열, 함수, 또다른 Proxy객체 등이 들어올 수 있다. handler에는 operation이 수행 될 때, Proxy의 동작을 정의하는 함수 객체를 넣어준다. 여기서 말하는 operation이란 앞에서 언급했던 속성 조회, 할당, 열거, 함수 호출 등이 있겠다. handler에는 get, set, has, defineProperty, deleteProperty, construct, apply 등 총 13가지 함수를 핸들링 할 수 있다. 먼저 가장 간단한 get 부터 사용해보도록 하자.


handler.get.js

var p = new Proxy({}, {
get: function(target, prop, receiver) {
console.log('called: ' + prop);
return 10;
}
});

console.log(p.a); // "called: a"
// 10

 위 코드를 nodejs나 chrome으로 실행시켜 보면 "called: a"가 출력된 후 "10"이 출력되는 것을 볼 수 있다. handler의 get method는 바로 '.' 연산을 hook하는 역할을 한다. 앞에서 언급한 속성 조회('.' 연산자 사용)시 사용자의 커스텀 동작(return 10;)을 실행한 것이다. 막상 써보니 무진장 신기하다. 이번엔 set을 한번 사용해보도록 하자.


handler.set.js


var p = new Proxy({}, {
set: function(target, prop, value, receiver) {
console.log('called: ' + prop + ' = ' + value);
return true;
}
});

p.a = 10; // "called: a = 10"

set method는 '=' 연산자가 실행될 때 hook이 실행된다. 말그대로 뭔가 값을 할당(set)할 때 커스텀 동작을 실행시킨다. 하나만 더 해보도록 하자. construct method는 뭘 hook하는 것일까. construct 즉 생성자인것을 보니 new 연산자를 사용했을 때라 짐작 할 수 있다.


handler.construct.js


var p = new Proxy(function() {}, {
construct: function(target, argumentsList, newTarget) {
console.log('called: ' + argumentsList.join(', '));
return { value: argumentsList[0] * 10 };
}
});

console.log(new p(1, 2, 3).value); // "called: 1, 2, 3"
// 10

예상대로 new 연산자가 실행 될 때 hook하는 것을 볼 수 있다. 두번째 인자로 new 연산자의 arguments들이 넘어오고 json 형태로 리턴값으로 전달도 할 수 있다.이밖에도 apply를 사용하면 함수 호출을, defineProperty를 사용하면 객체를 할당할 때, deleteProperty를 사용하면 객체를 delete할 때 hook을 할 수 있다.

어디다 쓰면 될까?

 당시에는 이걸 어디다 쓸까 고민은 많이 했었는데, 요즘은 쉽게 접할 수 있는 2-Way Binding을 구현하는데 사용할 수 있다. 아래 코드는 Proxy와 가상 돔을 사용하여 나만의 프레임워크 만들기라는 글에서 Proxy를 이용해 간단한 2-Way Binding을 구현한 예제이다.


export default class Model {
constructor(callback) {
const proxy = new Proxy(this, {
get(target, property) {
return target[property];
},
set(target, property, value) {
const oldValue = target[property];
target[property] = value;
if (value !== oldValue && callback) {
callback(property, oldValue, value);
}
return true;
}
});
return proxy;
}
}


실제 Virtual DOM과 함께 사용하면 아래 소스와 같이 구현할 수 있다.


보통은 Observer Pattern을 사용해서 구현할 수도 있는데, Proxy를 사용하니 훨씬 깔끔하고 간편하게 구현할 수 있음을 확인할 수 있다. Vue 3.0에서도 Proxy를 도입할 예정이라고하니, 앞으로 그 쓰임새가 많이 기대되는 API라고 생각한다.

마치며

Proxy에 대한 예제를 몇 가지 모아봤으니 참고하면 좋을 것 같다. 소스코드 예제 보기


'JavaScript' 카테고리의 다른 글

TDD & Mocha - Javascript Test Framework  (0) 2017.05.14
Javascript Image Filter - convolution  (0) 2017.05.02
Javascript ES6 Proxy  (0) 2017.04.16
Javascript Image Filter 만들기  (5) 2017.04.15
Webpack2 시작하기  (3) 2017.04.08
ECMA 2015를 공부해보자  (1) 2016.08.07