Strong Root


프록시 (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