
1. 메타프로그래밍
2. 자바스크립트 프록시 패턴
3. Proxy & Reflect
4. Proxy.revocable
5. 활용 - 모든 프로퍼티 은닉화
6. 활용 - 한시적 접근 허용
메타프로그래밍(Metaprogramming)이란 자기 자신 혹은 다른 컴퓨터 프로그램을 데이터로 취급하며 프로그램을 작성·수정하는 것을 말한다. 넓은 의미에서, 런타임에 수행해야 할 작업의 일부를 컴파일 타임 동안 수행하는 프로그램을 말하기도 한다.
https://ko.wikipedia.org/wiki/%EB%A9%94%ED%83%80%ED%94%84%EB%A1%9C%EA%B7%B8%EB%9E%98%EB%B0%8D
다른 코드를 조작하는 코드를 작성한다.
const proxy = new Proxy(target, handler);
target : 감싸게 될 대상 객체handler : 동작을 가로채는 메서드인 trap이 담긴 객체로, 여기서 프록시를 설정proxy에 작업이 가해지고, handler에 작업과 상응하는 trap이 있으면 trap이 실행trap이 없으면 target에 작업이 직접 수행trap이 없으면 target에 작업이 직접 수행handler가 비어있으면 Proxy에 가해지는 작업은 곧바로 target에 전달const name = {
firstName: 'Sangbeom',
lastName: 'Heo',
};
const proxy = new Proxy(name, {}); // no traps
proxy.firstName = 'first';
proxy.lastName = 'last';
console.log(name); // {firstName: "first", lastName: "last"}
Reflect에 정의되어 있으며, Proxy의 trap과 Reflect의 메소드는 1:1로 매칭된다.Reflect : 기본동작들을 똑같이 반영해서 정의해 놓음. 프록시가 가로채서 로직을 진행한 후 기본동작을 호출하고 싶을 때 사용한다.Proxy Trap | 기본 동작 | to override |
|---|---|---|
| get | Reflect.get() | getter, 프로퍼티를 읽을 때 |
| set | Reflect.set() | setter, 프로퍼티에 쓸 때 |
| has | Reflect.has() | in 연산자가 동작할 때 |
| deleteProperty | Reflect.deleteProperty() | delete 연산자가 동작할 때 |
| getPrototypeOf | Reflect.getPrototypeOf() | Object.getPrototypeOf() |
| setPrototypeOf | Reflect.setPrototypeOf() | Object.setPrototypeOf() |
| isExtensible | Reflect.isExtensible() | Object.isExtensible() |
| preventExtensions | Reflect.preventExtensions() | Object.preventExtensions() |
| getOwnPropertyDescriptor | Reflect.getOwnPropertyDescriptor() | Object.getOwnPropertyDescriptor() |
| defineProperty | Reflect.defineProperty() | Object.defineProperty() |
| ownKeys | Reflect.ownKeys() | Object.getOwnPropertyNames(),Object.getOwnPropertySymbols(),Object.keys() |
| apply | Reflect.apply() | 함수를 호출할 때 |
| construct | Reflect.construct() | new 연산자가 동작할 때 |
const person = {
firstName: 'Sangbeom',
lastName: 'Heo',
age: 20,
};
const personProxy = new Proxy(person, {
set(target, key, value, receiver) {
if (!target.hasOwnProperty(key)) {
console.log('객체에 존재하지 않는 프로퍼티');
return;
}
if (key === 'age' && typeof value !== 'number') {
console.log('age의 값은 number 타입만 올 수 있다.');
}
return Reflect.set(target, key, value, receiver);
},
get(target, key, receiver) {
return Reflect.get(target, key, receiver);
},
});
personProxy.age = 10;
personProxy.number = 20; // 객체에 존재하지 않는 프로퍼티
personProxy.age = '10'; // age의 값은 number 타입만 올 수 있다.
Proxy.revocable(target, handler);
const person = {
name: 'heo',
age: 10,
};
const revocableProxy = Proxy.revocable(person, {
deleteProperty(target, key) {
return key === 'age' ? false : Reflect.deleteProperty(target, key);
// age 일 때 삭제를 못하게 막음
},
});
console.log(revocableProxy.proxy);
delete revocableProxy.proxy.name;
console.log(revocableProxy.proxy);
delete revocableProxy.proxy.age; // 삭제 안됌
console.log(revocableProxy.proxy);
const ProxyClass = (() => {
const PROP = Symbol('PROP');
const handler = {
set(target, key, value) {
target[PROP].set(key, value);
},
get(target, key) {
return target[PROP].get(key);
},
};
return class {
constructor() {
this[PROP] = new Map();
return new Proxy(this, handler);
}
};
})();
const instance = new ProxyClass();
instance.a = 10;
instance.hasOwnProperty('a'); // TypeError: instance.hasOwnProperty is not a function
// 프로퍼티에 직접 접근할 수 없다.
const getPostInfo = postId =>
fetch(`https://jsonplaceholder.typicode.com/posts/${postId}`)
.then(res => res.json())
.then(res => {
let counter = 0;
const pr = Proxy.revocable(res, {
get(target, key) {
if (counter++ > 2) {
//2번 동작 후 프록시 종료
pr.revoke();
}
return res[key];
},
});
return pr.proxy;
})
.catch(err => {
console.error(err);
});
getPostInfo(1).then(post => {
console.log(`id: ${post.id}`);
console.log(`title: ${post.title}`);
console.log(`body: ${post.body}`); // TypeError: Cannot perform 'get' on a proxy that has been revoked
});