Proxy 객체는 다른 객체를 감싸고 해당 객체에 대한 특정 동작을 가로채거나 수정하는 데 사용할 수 있다.
객체의 속성 검증, 가상 속성 생성, 캐싱 등 다양한 목적으로 사용한다.
let proxy = new Proxy(감시_객체, 핸들러)
첫번째 인자로 Proxy로 감쌀 객체가 들어가고
두번째 인자로는 특정 동작을 가로채는 메서드들이 담겨있는 핸들러 객체가 들어간다.
💡 Proxy 핸들러 리스트
- property
- get
- set
- has
- deleteProperty
- method
- apply
- constructor
- Object
- getPrototypeOf
- setPrototypeOf
- isExtensible
- preventExtensions
- getOwnPropertyDescriptor
- ownKeys
proxy에 작업이 생겼을때 핸들러에 해당 작업에 대한 메서드가 정의되어 있으면 proxy에 작업이 처리된다.
핸들러가 비어있다면 proxy에 가해지는 작업은 감시_객체에 곧바로 전달 된다.
const ageList = {};
let proxyAgeList = new Proxy(ageList, {});
proxyAgeList.ddowoo = "10";
console.log(target); // { ddowoo: '10' }
console.log(proxy); // { ddowoo: '10' }
target
: 동작을 전달할 객체
property
: 프로퍼티 키
해당 프로퍼티가 존재하면 해당 값을 반환하고, 존재하지 않는 경우에는 커스텀한 값을 반환하거나 다른 동작을 수행할 수 있다.
const ageList = {};
let proxyAgeList = new Proxy(ageList, {
get: (target, prop) => {
if (prop in target) {
return target[prop];
} else {
return "존재하지 않는 유저입니다.";
}
},
});
console.log(ageList.ddowoo); // undefined
console.log(proxyAgeList.ddowoo); // 존재하지 않는 유저입니다.
true
를 반환하고, 그렇지 않은 경우 false
를 반환한다.target
: 동작을 전달할 객체
property
: 프로퍼티 키
value
: 프로퍼티 값
receiver
: 할당이 지시된 원래 객체
const ageList = {};
let proxyAgeList = new Proxy(ageList, {
set: (obj, prop, value) => {
console.log("obj :", obj);
console.log("prop : ", prop);
console.log("value : ", value);
// 숫자인 경우에만 할당
if (typeof value === "number") {
obj[prop] = value;
return true;
} else {
return false;
}
},
});
proxyAgeList.ddowoo = '5'; // 숫자가 아닌경우
// obj : {}
// prop : ddowoo
// value : '5'
console.log(ageList.ddowoo); // undefined
console.log(proxyAgeList.ddowoo); // undefined
proxyAgeList.ddowoo = 5; // 숫자인 경우
// obj : {}
// prop : ddowoo
// value : 5
console.log(ageList.ddowoo); // 5
console.log(proxyAgeList.ddowoo); // 5
💡
true
를 반환하지 않으면 TypeError이 발생할수도 있다 한다. 꼭true
를 반환해주자
delete
연산자를 사용하여 프로퍼티를 삭제할 때 호출된다true
를 반환하고, 그렇지 않은 경우 false
를 반환한다.target
: 동작을 전달할 객체
property
: 프로퍼티 키
const ageList = {
ddowoo: 10,
dong: 10,
};
let proxyAgeList = new Proxy(ageList, {
deleteProperty: (obj, prop) => {
if (prop === "ddowoo") {
return false;
} else {
delete obj[prop];
return true;
}
},
});
delete proxyAgeList.ddowoo;
delete proxyAgeList.dong;
console.log(proxyAgeList.ddowoo); // 10 (안지워짐)
console.log(proxyAgeList.dong); // undefined
in
연사자가 사용될 때 호출된다.target
: 동작을 전달할 객체
property
: 프로퍼티 키
has
핸들러를 사용해 _
로 시작하는 키를 가진 프로퍼티가 있는 경우, in
연산자를 통해 해당 키를 조회되지 않도록 작업했습니다.
// has
const ageList = {
_ddowoo: 10,
ddowoo: 10,
};
let proxyAgeList = new Proxy(ageList, {
has: (obj, prop) => {
if (prop.startsWith("_")) {
return false;
} else {
return obj.hasOwnProperty(prop);
}
},
});
console.log("_ddowoo" in proxyAgeList); // false
console.log("ddowoo" in proxyAgeList); // true
target
: 원본 함수
thisArg
: 함수가 호출될 때 this 값
argumentsList
: 호출에 대한 인수 목록
function tellAge(age) {
return `나이는 ${age}살 입니다.`;
}
const tellAgeProxy = new Proxy(tellAge, {
apply: function (target, thisArg, argumentsList) {
return `만나이는 ${argumentsList[0] - 2}살 입니다.`;
},
});
const result1 = tellAge(10);
const result2 = tellAgeProxy(10);
console.log(result1); // 나이는 10살 입니다.
console.log(result2); // 만나이는 8살 입니다.
Reflect는 Proxy와 같이 명령을 가로챌 수 있는 메서드를 제공하는 내장 객체이다.
Reflect는 생성자가 아니기 때문에 Proxy와 같이 생성자로는 사용할 수 없다. 메서드 종류는 프록시의 핸들러 메서드와 동일하다.
Reflect로 객체에 여러 동작을 수행할 때 코드상 통일된 방식으로 작성해 유지보수가 용이하고, 예외처리를 간소하게 할수 있다.
const ageList = { ddowoo : 10 }
console.log(Reflect.get(ageList,'ddowoo')); // 10
const ageList = {}
console.log(Reflect.set(ageList,'ddowoo',10)); // true
console.log(ageList.ddowoo); // 10
const ageList = {ddowoo : 10}
console.log(Reflect.has(ageList,'ddowoo')); // true
Proxy의 핸들러 함수에서 직접 target 파라미터 객체에 작업을 하는 방식은 예상치 못한 오류가 생길수 있다. 이런 경우 Reflect로 명시적 대상 객체에 동작을 수행하면 부작용을 줄일수 있어 주로 Proxy와 함께 쓰인다.
예시코드
const ageList = {};
let proxyAgeList = new Proxy(ageList, {
get: (obj, prop) => {
// if (prop in obj) { => 기존 코드
if (Reflect.has(obj, prop)) {
// return obj[prop]; => 기존 코드
return Reflect.get(obj, prop);
} else {
return "존재하지 않는 유저입니다.";
}
},
set: (obj, prop, value) => {
if (typeof value === "number") {
// obj[prop] = value; => 기존 코드
// return true; => 기존 코드
return Reflect.set(obj, prop, value); // 리턴값으로 true
} else {
return false;
}
},
});
proxyAgeList.ddowoo = 10;
console.log(ageList); // { ddowoo: 10 }
console.log(proxyAgeList); // { ddowoo: 10 }
console.log(ageList.ddowoo); // 10
console.log(proxyAgeList.ddowoo); // 10
console.log(ageList.John); // undefined
console.log(proxyAgeList.John); // 존재하지 않는 유저입니다.
참조
Reflect - JavaScript | MDN
Proxy와 Reflect
JS-📚-자바스크립트-Proxy-Reflect-고급-기법