js-proxy

Proxy는 특정 객체를 감싸 프로퍼티 읽기, 쓰기와 같은 객체에 가해지는 작업을 중간에서 가로채는 객체이다.

가로채진 작업은 Proxy 자체에서 처리되기도 하고, 원래 객체가 처리하도록 그대로 전달되기도 한다.

Creating a proxy object

1
let proxy = new Proxy(target, handler);
  • target – 감싸게 될 객체로, 함수를 포함한 모든 객체 포함
  • handler – 동작을 가로채는 메서드인 ‘트랩(trap)’이 담긴 객체로, 여기서 Proxy를 설정

A simple proxy example

1
2
3
4
5
6
7
8
9
10
11
12
13
14
const user = {
firstName: 'John',
lastName: 'Doe',
email: 'john.doe@example.com',
}

const handler = {
get(target, property) {
console.log(`Property ${property} has been read.`);
return target[property];
}
}

const proxyUser = new Proxy(user, handler);

js-proxy

1
2
3
4
5
6
console.log(proxyUser.firstName);
console.log(proxyUser.lastName);
// Property firstName has been read.
// John
// Property lastName has been read.
// Doe

user 객체에 proxyUser로 접근할경우 get() 메서드가 호출된다.

1
2
3
4
user.firstName = 'Jane';
console.log(proxyUser.firstName);
// Property firstName has been read.
// Jane

user 객체를 변경하면 proxyUser에도 반영된다.

1
2
proxyUser.lastName = 'William';
console.log(user.lastName); // William

proxyUser 객체를 변경해도 user에 반영된다.

Proxy Traps

The get() trap

일반적으로 get() 메서드에 커스텀한 로직을 작성하여 property에 접근할 때 get() trap을 발생시킬 수 있다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
const user = {
firstName: 'John',
lastName: 'Doe'
}

const handler = {
get(target, property) {
return property === 'fullName' ?
`${target.firstName} ${target.lastName}` :
target[property];
}
};

const proxyUser = new Proxy(user, handler);

console.log(proxyUser.fullName); // John Doe

The set() trap

set() traptargetproperty가 set될때 발생한다.

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
const user = {
firstName: 'John',
lastName: 'Doe',
age: 20
}

const handler = {
set(target, property, value) {
if (property === 'age') {
if (typeof value !== 'number') {
throw new Error('Age must be a number.');
}
if (value < 18) {
throw new Error('The user must be 18 or older.')
}
}
target[property] = value;
}
};

const proxyUser = new Proxy(user, handler);

proxyUser.age = 'foo'; // Error: Age must be a number.
proxyUser.age = '16'; // The user must be 18 or older.
proxyUser.age = 21; // OK.

The apply() trap

apply() trap은 함수가 호출될때 발생한다.

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
let proxy = new Proxy(target, {
apply: function(target, thisArg, args) {
//...
}
});

const user = {
firstName: 'John',
lastName: 'Doe'
}

const getFullName = function (user) {
return `${user.firstName} ${user.lastName}`;
}

const getFullNameProxy = new Proxy(getFullName, {
apply(target, thisArg, args) {
return target(...args).toUpperCase();
}
});

console.log(getFullNameProxy(user)); // JOHN DOE

proxy trap의 작동 시점

내부 메서드 핸들러 메서드 작동 시점
[[Get]] get 프로퍼티를 읽을 때
[[Set]] set 프로퍼티에 쓸 때
[[HasProperty]] has in 연산자가 동작할 때
[[Delete]] deleteProperty delete 연산자가 동작할 때
[[Call]] apply 함수를 호출할 때
[[Construct]] construct new 연산자가 동작할 때
[[GetPrototypeOf]] getPrototypeOf Object.getPrototypeOf
[[SetPrototypeOf]] setPrototypeOf Object.setPrototypeOf
[[IsExtensible]] isExtensible Object.isExtensible
[[PreventExtensions]] preventExtensions Object.preventExtensions
[[DefineOwnProperty]] defineProperty Object.defineProperty, Object.defineProperties
[[GetOwnProperty]] getOwnPropertyDescriptor Object.getOwnPropertyDescriptor, for..in, Object.keys/values/entries
[[OwnPropertyKeys]] ownKeys Object.getOwnPropertyNames, Object.getOwnPropertySymbols, for..in, Object/keys/values/entries

참고자료

후기

js proxy는 다양한 라이브러리와 프레임워크에서 사용되고 있다고 한다.

잘 활용해서 JS 환경에서 좀 더 클린하고 효율적인 코드를 작성할 수 있지 않을까 기대를 해본다.

댓글

You forgot to set the shortname for Disqus. Please set it in _config.yml.