[JavaScript(ES6)] Proxy
프록시 (Proxy) 란
Proxy 의 사전적 의미는 "대행", "대리", "위임" 이다.
프록시 (Proxy) 는 객체에 수행되는 동작들 (예를들면 속성값 조회, 변경) 을 가로챌 수 있게 해주고, 커스터마이징도 할 수 있게 해주는 일종의 객체 감싸미다.
프록시는 세 요소로 구성된다:
* 타겟 (target) : 프록시로 감쌀 대상 객체. * 핸들러 (handler) : 트랩 메서드들을 담고 있는 객체. 타겟에 대한 동작을 감지하여 그에 대응하는 트랩 메서드가 존재할 경우 해당 트랩 메서드를 호출한다. * 트랩 (trap) : 핸들러 안에 존재하는 메서드. 타겟 객체에 대한 동작을 가로채며, 사용자 정의 로직을 넣을 수 있다. |
아래는 프록시를 적용한 예제 코드이다. 여기서는 트랩으로 get, deleteProperty 가 사용되었다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | var target = {a: 1}; var handler = { get: function(target, key) { // any custom logic console.log('GET: ' + key); return target[key]; }, deleteProperty: function(target, key) { // any custom logic console.log('DELETE: ' + key); return delete target[key]; } }; var proxiedTarget = new Proxy(target, handler); console.log(proxiedTarget.a); // GET: a // 1 console.log(proxiedTarget.b); // GET: b // undefined delete proxiedTarget.a; // DELETE: a | cs |
Reflect
ECMAScript 6 에서는 모든 트랩에 대하여 포워딩을 도와주는 Reflect 라는 객체가 추가되었다:
handler.trap(target, arg_1, ···, arg_n) 위와 같은 모든 trap 메서드에 대응하는 Reflect.trap(target, arg_1, ···, arg_n) 이 존재한다. |
Reflect 를 이용한 포워딩 (Forwarding intercepted operations with Reflect):
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 | var target = {a: 1}; var handler = { get: function(target, key) { // any custom logic console.log('GET: ' + key); return Reflect.get(target, key); }, deleteProperty: function(target, key) { // any custom logic console.log('DELETE: ' + key); return Reflect.deleteProperty(target, key); } }; var proxiedTarget = new Proxy(target, handler); console.log(proxiedTarget.a); // GET: a // 1 console.log(proxiedTarget.b); // GET: b // undefined delete proxiedTarget.a; // DELETE: a | cs |
Why Proxy? Proxy 의 강력한 활용
아래 코드를 이해하면 Proxy 의 강력함을 느낄 수 있다.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 | let normalObj = {name: 'CGun'}; let dfObj = makeDefensiveObject(normalObj); /* [1] name */ console.log(dfObj.name); // GET: name // CGun /* [2] marriage */ console.log(dfObj.marriage); // GET: marriage // Uncaught ReferenceError: marriage does not exist. dfObj.marriage = 'x'; // SET: marriage, x // Uncaught TypeError: marriage should be "기혼" or "미혼" dfObj.marriage = '미혼'; // SET: marriage, 미혼 console.log(dfObj.marriage); // GET: marriage // 미혼 /* [3] age */ console.log(dfObj.age); // GET: age // Uncaught ReferenceError: age does not exist. dfObj.age = 'x'; // SET: age, x // Uncaught TypeError: The age is not an integer dfObj.age = -30; // SET: age, -30 // Uncaught RangeError: The age seems invalid dfObj.age = 29; // SET: age, 29 console.log(dfObj.age); // GET: age // 29 /* [4] grade */ console.log(dfObj.grade); // GET: grade // Uncaught ReferenceError: grade does not exist. dfObj.grade = 80; // SET: grade, 80 // Uncaught TypeError: grade should be "A" to "F" dfObj.grade = 'b'; // SET: grade, b console.log(dfObj.grade); // GET: grade // B /* [5] Proxy */ function makeDefensiveObject(target) { if (target === undefined) { target = {}; } let handler = { get: function(target, key) { console.log('GET: ' + key); if (!(key in target)) { throw new ReferenceError(key + ' does not exist.'); } if (key == 'marriage') { return Reflect.get(target, key) === true ? '기혼' : '미혼'; } return Reflect.get(target, key); }, set: function(target, key, value) { console.log('SET: ' + key + ', ' + value); if (key == 'marriage') { if (value == '기혼') { value = true; } else if (value == '미혼') { value = false; } else { throw new TypeError('marriage should be "기혼" or "미혼"'); } } if (key == 'age') { if (!Number.isInteger(value)) { throw new TypeError('The age is not an integer'); } if (value < 0 || value > 150) { throw new RangeError('The age seems invalid'); } } if (key == 'grade') { if (!value || value.length != 1) { throw new TypeError('grade should be "A" to "F"'); } value = value.toUpperCase(); if (value < 'A' || value > 'F') { throw new RangeError('The grade seems invalid'); } } return Reflect.set(target, key, value); } }; return new Proxy(target, handler); } | cs |
Revocable proxy
ECMAScript 6 에서는 취소가 가능한 (스위치를 끌 수 있는) 프록시가 추가되었다:
1 | var {proxy, revoke} = Proxy.revocable(target, handler); | cs |
revoke 함수를 실행하고 나면 해당 proxy 가 취소되며, 사용하려고 하면 TypeError 가 발생한다:
1 2 3 4 5 6 7 8 9 10 | var target = {}; // Start with an empty object var handler = {}; // Don’t intercept anything var {proxy, revoke} = Proxy.revocable(target, handler); proxy.foo = 123; console.log(proxy.foo); // 123 revoke(); console.log(proxy.foo); // TypeError: Revoked | cs |
출처
http://exploringjs.com/es6/ch_proxies.html
https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Global_Objects/Proxy
'뿌리튼튼 CS > Web' 카테고리의 다른 글
[JavaScript(ES6)] Promise (0) | 2016.06.22 |
---|---|
구형 브라우저에서의 HTML5 (HTML5 Browser Support) (0) | 2016.06.11 |
[JavaScript(ES6)] Iterators and Generators (0) | 2016.05.16 |
[JavaScript] DOM 엑세스 줄이기 (Reduce DOM Access) (0) | 2015.08.03 |
[JavaScript] Loose Comparison vs. Strict Comparison (0) | 2015.07.31 |