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
});