기본적으로 객체의 동작을 사용자 정의하는 방법을 제공한다.
이는 객체에 대한 작업을 가로채고, 이를 변경하거나, 새로운 동작을 추가하거나,
기본 동작을 수행하기 전에 작업을 수행할 수 있도록 한다.
new 연산자를 사용하지 않으면 TypeError 발생
handler를 작성하지 않으면 에러가 발생
Proxy로 만든 오브젝트는 constructor 가 없다.
Proxy 객체를 만들려면 두가지 인수가 필요하다
target
객체와handler
객체이다.let target = {}; let handler = { get: function(target, prop, receiver) { console.log(`Getting ${prop}`); return Reflect.get(...arguments); }, set: function(target, prop, value, receiver) { console.log(`Setting ${prop} to ${value}`); return Reflect.set(...arguments); } }; let proxy = new Proxy(target, handler); proxy.a = 'example'; // Setting a to example console.log(proxy.a); // Getting a, example
Proxy를 사용할 수 없는 형태로 변경할 수 있는 Proxy 를 만드는 메서드
인스턴스.revoke() 를 하면 사용불가능 해진다.target의 프로퍼티에 접근하려면
Proxy 인스턴스.proxy.프로퍼티식별자
로 접근해야한다.const target = {point : 100}; const handler = { get(target, key){ return target[key]; } }; const obj = Proxy.revocable(target, handler); console.log(obj.proxy.point); // 100, handlr.get 실행 obj.revoke(); try { obj.proxy.point; } catch { console.log("Proxy 기능 사용 불가"); // 실행 }
Proxy 가 가로챌 객체
target에 어떤 수행할 때 가로채서
어떤 수행을 할지 지정하도록 만든 메뉴얼들은 모아둔 객체
handler 가 메뉴얼들을 모아둔 객체라고 했는데
trap은 메뉴얼이라고 보면 된다.즉, target에 어떤 수행할 때 가로채서
어떤 수행을 할지 지정하도록 만든 메뉴얼이다.
모든 메서드를 Proxy 할 수 있는 것은 아니다.
위 사진은 Proxy 할 수 있는 메서드 들의 집합이다.
target 파라미터의 값을 할당할때 가로채는 trap 이다.
파라미터
- target : 대상 객체로 프로퍼티 값을 가져오려는 원본 객체
- key : 가져오려는 프로퍼티 이름
- value : key에 할당할 값
- receiver : 프록시 인스턴스
아래 코드에서는 obj
파라미터 명은 자유롭게 작성할 수 있다.set trap 내부에서 this 바인딩은
handler
이다.const target = {}; const handler = { set(target, key ,value, recevier){ target[key] = value + 200; } }; const obj = new Proxy(target, handler); obj.point = 100; console.log(obj.point); // 300
const target = {}; const handler = { set(target, key ,value, recevier){ target[key] = value + 200; target.title = recevier.title +".JS"; return true; } }; const proxy = new Proxy(target, handler); // 만든 프록시를 부모로 사용 const obj = Object.create(proxy, { title : {value: "책", writable: true} }); obj.point = 100; console.log(obj.title); // 책 console.log(target.title); // 책.JS
const target = {point: 100}; const handler = { point: 123, set(target, key ,value, receiver) { console.log(this.point); // 123 this.book = "책"; } }; const obj = new Proxy(target, handler); obj.point = 500; console.log(handler.book); // 책 console.log(target.book); // undefined
만약 값을 할당하고자 하는 target의 프로퍼티의
프로퍼티 디스크립터 중[[Writeable]]
이나[[Configurable]]
이false
면
set() trap 을 사용해도 값을 변경할 수 없다.
target 파라미터의 값을 얻고자 할때 가로채는 trap 이다.
매개변수
- target : 대상 객체로 프로퍼티 값을 가져오려는 원본 객체
- key : 가져오려는 프로퍼티 이름
- receiver : 프록시 인스턴스
const target = {point : 100}; const handler = { get(target, key, receiver) { return target[key] + 200; } }; const obj = new Proxy(target, handler); console.log(obj.point); // 300 console.log(obj.bonus); // NaN, target[bonus] = undefined
조건을 부여하여 값을 구할때 호출하는 곳마다 조건 코드를 작성하지 않고
get() trap에 조건 조드를 작성하면 클린 코드가 될 수 있다.const target = { point: 100 }; const handler = { get(target, key, receiver){ const value = target[key]; return this.check ? value + 200 : value; } }; const obj = new Proxy(target, handler); handler.check = true; consoel.log(obj.point); // 300
주의할 점
프록시 설정 이후 target을 새로운 객체를 생성하여 할당할 경우
프록시 인스턴스의 [[target]] 과 target의 주소값이 달라지게 되므로
동기화가 끊긴다.따라서 프록시 설정 이후 target을 새로운 객체로 만들지말고
수정이 필요한 경우 프로퍼티를 건드리자
만약 target 프로퍼티의 프로퍼티 디스크립터 중
[[Writable]]
이나[[Configurable]]
이
false
면 반환 값을 변경하여 return이 불가능하다.get() 트랩에서 try-catch 문을 사용할 수 없다.
in 연산자가 사용될 때 가로챈다.
in 연잔자는 객체가 특정 속성을 소유하고 있는지 확인하는 연산자이다.
이때 객체 사진의 속성 뿐아니라 상속받은 프로토타입 체인 상의 모든 속성들을 확인한다.let target = { point : 100 }; let handler = { has(target, key) { return target[key] } }; let obj = new Proxy(target, handler); console.log("point" in obj); // true console.log("book" in obj); // false
만약 obj에 같은 이름의 프로퍼티 이름이 존재한다면 trap을 호출하지 않는다.
let target = { point : 100 }; let handler = { has(target, key) { return target[key] } }; const proxy = new Proxy(target, handler) let obj = Object.create(proxy, { point : {value: 500} } ); console.log("point" in obj); // true, trap 호출 안함 console.log("bonus" in obj); // bonus
만약 오브젝트가 Object.preventExtensions() 등으로 확장 금지이거나
프로퍼티의 프로퍼티 어트리뷰트 중[[Configurable]]
이false
면
반환 값을 변경하여 반환할 수 없다.false로 지정하면 에러가 발생한다.
true만 가능하다.const target = {point: 100}; Object.preventExtensions(target); const handler = { has(target, key) { console.log("has 트랩 실행"); // return false; 에러 발생 return target[key]; } }; const obj = new Proxy(target, handler); console.log("point" in obj);
delete 연산을 호출했을때 가로채는 trap 이다.
delete 연산은 프로퍼티가 없어도 true를 반환한다.
이 트랩을 사용하여 조건을 체크하여 true/false를 반환하면 완전하게 처리할 수 있다.const target = {point : 100}; console.log(delete target.bonus); // true, bonus 프로퍼티는 존재하지 않는다. const handler = { deleteProperty(target, key){ if (key in target) { delete target[key]; return true; } return false; } }; const obj = new Proxy(target, handler); console.log(delete obj.point); // true console.log(target.point); // undefined console.log(delete obj.point); // false console.log(delete target.point); // true, target은 원본 객체이므로 [[Delete]] 가 호출된다. 일반적인 delete 처리 결과이다.
target 프로퍼티의 프로퍼티 디스크립터 중 [[Configurable]] 이 false 이면
삭제할 수 없으므로 에러가 발생한다.
Object.definepProperty() 의 트랩이다.
파라미터
- target : 원본 객체
- key : target의 프로퍼티 식별자 이름
- desc : key의 프로퍼티 디스크립터
const target = {}; const handler = { defineProperty(target, key, desc){ if(desc.value > 0) { desc,value= desc.value * -1; }; Object.defineProperty(target, key, desc); return true; } }; const proxy = new Proxy(target, handler); Object.defineProperty(proxy, "point", {value: 100, writable: true} ); console.log(target.point); // -100
target 오브젝트가
확장 불가 상태
이면 프로퍼티를 추가할 수 없다.
Object.preventExtension() 트랩
target 오브젝트에 오브젝트의 확장 금지를 설정한다.const target = {point: 100}; const handler = { preventExtensions(target){ if(target.point) { // target에 point 프로퍼티가 있을 때만 확장 금지 Object.preventExtensions(target); return true; }; return false; } }; const proxy = new Proxy(target, handler); // 트랩에서 true를 반환히면 true 가 아닌 proxy 인스턴스가 반환된다. // 만약 false 면 false를 반환한다. const obj = Object.preventExtensions(proxy); console.log(obj.point); // 100 console.log(Object.isExtensible(target)); // false
트랩 내부에서 Object.isExtensible(target)의 결과 false 일때
최종적으로 false를 반환하면 TypeError가 발생한다.
True 만 반환할 수 있다.
Object.isExtensible
의 트랩이다.
Object.isExtensible
는
target의확장 가능 여부
를 반환한다.
Object.seal()
,Object.freeze()
,Object.preventExtension()
,
Reflect.preventExtensions()
로 객체가 잠겨져있을 경우false
를 반환한다.
Object.getPrototypeOf() 메서드를 가로채는 데 사용
대상 객체의 프로토타입 지정을 가로채어 변환하거나,
특정 조건을 만족할때만 포로토타입에 접근할 수 있게하는 등의 동작을 구현할 수 있다.class Point { getPoint() { return 100; } // getPoint()는 Point가 아닌 Point.prototype에 할당된다. } const handler = { getPrototypeOf(target){ return target.prototype; } }; const obj = new Proxy(Point, handler); const proto = Object.getPrototypeOf(ojb); console.log(proto.getPoint); // 100
트랩이 호출되는 형태
- Object.getPrototypeOf()
- proto
- instanceOf
- Object.prototype.isPrototypeOf()
- Reflext.getPrototypeOf()
class Point { getPoint() { return 100; } } const handler = { getPrototypeOf(target) { // this 는 handler 가 된다. return this.list ? Array.prototype : target.prototype; } }; const obj = new Proxy(Point, handler); handler.list = true; const proto = obj.__proto__; // Array.prototype console.log(proto.map); // funciton map() { [native code] }
Object.getPrototypeOf() 메서드를 가로채는 데 사용
Object.setPrototypeOf()
__proto__
는 정확하게는Object.prototype
에 있는 파라미터이다.
아래 그림에서는 직관적으로 보기위해 각 오브젝트 모두 포함시켰다.class Book {setTitle() { return "책" } }; class Point {getPoint() { return 100; } };
Object.setPrototypeOf(Book, Point.prototype); console.log(Book.prototype.getPoint); // undefined console.log(Book.__proto__.getPoint); // getPoint() {return 100;} const obj = new Book(); console.log(obj.getPoint); // getPoint() {return 100;}
트랩에서 true를 반환하지 않으면 에러가 발생한다.
class Book {setTitle() { return "책" } }; class Point {getPoint() { return 100; } }; const handler = { setPrototypeOf(target, proto) { Object.setPrototypeOf(target, proto); return true; } }; const obj = new Proxy(Book, handler); Object.setPrototypeOf(obj. Point.prototype); console.log(Book.prototype.getPoint); // undefined console.log(Book.__proto__.getPoint); // getPoint() {return 100;} console.log(obj.getPoint); // getPoint() {return 100;}
new 연산자의 트랩
new 연산자는 인스턴스를 생성하여 반환한다.
new Proxy로 인스턴스를 생성하고 이 인스턴스를 생성자 함수로 사용해서
다시 인스턴스를 만들어 낼 때 트랩이 호출된다.
파라미터
- target : 대상 오브젝트
- args : argumentsList, Array 또는 Array-like
- proxy : newTarget(선택)
인스턴스의 초기값을 설정할때 Point 클래스의 constructor 에서 조건문으로 분기하지말고
트랩 내부에서 조건문을 사용하여 분기하면 클린 코드가 될 수 있다.class Point { constructor(point){ this.point = point; } }; const handler = { construct(target, args, proxy) { // args = [100, "add", 300] let point = args[0]; if(Object.is(args[1], "add")) { point + args[2]; }; return new target(point); } }; const obj = new Proxy(Point, handler); const pointObj = new obj(100, "add", 300); // construct trap 호출 console.log(pointObj.point);
함수에 프록시가 달려있고 그 프록시 객체로 함수를 실행했을때
가로채는 함수
파라미터
- target : 호출할 함수
- that(선택) : this로 참조할 오브젝트
- params(선택) 호출할 함수에 넘겨줄 파라미터
Proxy 인스턴스 호출로 인해 트랩이 실행되면 that에 값이 설정되지 않는다.
function getPoint(...values){ return values.map((value) => { return value + 10} }); |; const handler = { apply(target, that, params){ // params = [100,200] return target.apply(this, params); } }; const obj = new Proxy(getPoint, handler); console.log(obj(100,200));
트랩이 호출되는 형태
- Function.prototype.apply()
- Function.prototype.call()
- proxy(..args) : Proxy 인스턴스
- Reflect.apply()
function getPoint(...values){ return values.map((value)) => { return value + this.bonus; }); }; const handler = { apply(target, that, params){ // that(원본함수에 넘겨줄 this)은 add return target.apply(that, params); } }; const obj = new Proxy(getPoint, handler); const add = {bonus : 10}; consolg.log(obj.apply(add, [100,200])); // add는 this 가 된다. 배열은 getPoint()로 넘겨줄 파라미터이다. console.log(obj.call(add,100,200)); // 이하 동일
Object.getOwnPropertyNames()의 트랩이다.
target의 모든 key를 배열로 반환한다.
[[Configurable]] 이 false거나 오브젝트가 확장 불가이면 반환이 안된다.const target = {}; Object.defineProperties(target, { point: {value: 100, enumerable: true}, bonus: {value: 200} // enumerable, configurable, writable 등의 속성을 명시하지 않으면, 기본값은 false } const handler = { ownKeys(target){ return Object.getOwnPropertyNames(target); } }; const obj = new Proxy(target, handler); console.log(Object.getOwnPropertyNames(obj)); // [point, bonus], 트랩 호출 console.log(Object.keys(obj)); // [point], 트랩 호출, keys() 는 enumerable: true 인 프로퍼티만 출력한다.
트랩이 호출되는 형태
- Object.getOwnPropertyNames()
- Object.getOwnPropertySymbols()
- Object.keys()
- Reflect.ownKeys()
Object.getOwnPropertyDesciptor() 의 트랩이다.
프로퍼티 디스크립터를 반환한다.const target {}; Object.defineProperty(target, "point", {value: 100, configurable: true}); const handler ={ getOwnPropertyDescriptor(target, key){ const desc = Object.getOwnPropertyDescriptor(target,key); if (desc.configurable) return { value: 300, configurable: true }; return desc; } }; const obj = new Proxy(target, handler); console.log(Object.getOwnPropertyDescriptor(obj, "point"); // 300