프록시(Proxy)
Proxy는 특정 객체를 감싸 프로퍼티 읽기, 쓰기와 같은 객체에 가해지는 작업을 중간에서 가로채는 객체로,
가로채진 작업은 Proxy 자체에서 처리되기도 하고, 원래 객체가 처리하도록 그대로 전달 되기도 함
Proxy의 사전적 뜻은 '대리인', '대리'라는 뜻으로 자바스크립트에서도 대리인의 역할을 수행함
프록시 객체(Proxy object)는 대상 객체(Target object) 대신 사용됨
대상 객체를 직접 사용하는 대신, 프록시 객체가 사용되며 각 작업을 대상 객체로 전달하고 결과 다시 코드로 돌려줌
이러한 방식을 통해 프록시 객체는 자바스크립트의 기본적인 명령에 대한 동작을 사용자 정의 가능하도록 함
객체 자체가 처리하는 특정 명령을 정의할 수 있게 되는 것
이런 명령의 종류는 속성검색, 접근, 할당, 열거, 함수 호출 등이 대표적
▶ Proxy 사용법
// 대상 객체(Real Subject)
let target = { ... }
// target의 동작을 가로채어 제어하는 미리 정의된 메서드 내부를 구현한 핸들러
let handler = {
get(target, prop) { ... },
set(target, prop, value) { ... },
...
}
// 프록시 생성 및 등록
const proxy = new Proxy(target, handler)
프록시 객체를 생성하기 위해 new를 붙여 생성자를 호출해야 함
Proxy는 필수적으로 2개의 인자를 받음
- target : Proxy의 대상이 되는 객체(감싸질 객체, 함수를 포함한 모든 객체가 가능)
- handler : target의 동작을 제어하는 메서드를 정의한 객체(get, set 등 메서드를 정의한 것을 볼 수 있는데 이 메서드들은 target 객체의 동작을 중간에 가로채서 제어 로직을 가미하게 하는 미리 정의된 메서드, 트랩(trap)이라고 부름)
- ex) get 트랩은 target의 프로퍼티를 읽을 때, set 트랩은 target의 프로퍼티를 쓸 때 활성화됨
proxy에 작업이 가해지고, handler에 작업과 상응하는 트랩이 있으면 트랩이 실행되어 프록시가 이 작업을 처리할 기회를 얻게 됨(트랩이 없으면 target에 작업이 직접 수행됨)
Proxy 예제
// 대상 객체 (target)
const user = {
firstName: 'John',
lastName: 'Doe',
email: 'john.doe@example.com',
}
// 핸들러 (handler)
const handler = {
// 대상 객체에 프로퍼티 값을 할당하려는 동작(get)을 가로채어 실행
get(target, property) {
console.log(`Property ${property} has been read.`);
return target[property];
}
}
const proxyUser = new Proxy(user, handler);
console.log(proxyUser.firstName);
// Property firstName has been read
// John
이때 주의할점은 클라이언트는 프록시 객체인 proxyUser를 통해 접근해야 된다는 점
user 객체를 프록시 객체가 감싸고 있는 형태이기 때문에 외부 포장과 같은 프록시를 통해 대상 객체인 user와 통신하게 되기 때문(콘솔 로그를 실행하면 원래는 값만 나와야 되는것이 다음과 같이 추가적으로 코드(콘솔)가 가미됨을 볼 수 있음)
Proxy 트랩 핸들러 종류
Property 가로채는 트랩 | 작동 시점 |
get | 프로퍼티를 읽을 때 |
set | 프로퍼티에 값을 쓸 때 |
has | in 연산자가 작동할 때 |
deleteProerty | delete 연산자가 작동할 때 |
Method 가로채는 트랩 | 작동 시점 |
apply | 함수를 호출할 때 |
constructor |
new 연산자가 작동할 때 |
Object 가로채는 트랩 | 작동 시점 |
getPrototypeOf | Object.getPrototypeOf 작동할 때 |
setPrototypeOf | Object.setPrototypeOf 작동할 때 |
isExtensible | Object.isExtensible 작동할 때 |
preventExtensions | Object.preventExtensions 작동할 때 |
getOwnPropertyDescriptor | Object.getOwnPropertyDescriptor 작동할 때 |
ownKeys | Object.getOwnPropertyNames 작동할 때 Object.getOwnPropertySymbols 작동할 때 |
▶ Proxy 해제하기
쓸모없어진 프록시 객체를 해체하여 메모리 측면에서 여유공간을 얻고싶다면 Proxy를 해제하면 됨
Proxy 객체의 Target 원본 객체를 null로 해버리면 자동으로 해체될것 같아 보이지만 반영되지 않음
따라서 직접 명시적으로 프록시 객체를 제거할 필요가 있음
프록시를 해지하지 위해선, 처음 부터 취소 가능 프록시(Revocable Proxy)로 정의할 필요가 있는데, Proxy.revocable()를 통해 얻을 수 있는 revoke를 이용해서 Target 객체를 참조하던 Proxy 객체를 해제할 수 있음
let obj = {
name: '홍길동',
};
let handler = {
get(target, key) {},
set(target, key, value) {},
};
// 취소 가능한 프록시 객체를 구조분해로 얻기
const { proxy, revoke } = Proxy.revocable(obj, handler);
try {
revoke(); // 프록시 해제
proxy.name = "임꺽정"; // !프록시 사용 불가
} catch (err) {
console.error(err.message); // Cannot perform 'set' on a proxy that has been revoked
}
▶ Proxy를 사용하는 이유
객체에 어떤 작업을 할 땐 자바스크립트 명세서에 정의된 '내부메서드'가 깊숙한 곳에서 관여함
프로퍼티를 읽을 땐 [[Get]]이라는 내부 메서드가, 프로퍼티에 쓸 땐 [[Set]]이라는 내부 메서드가 관여하게 됨
이런 내부 메서드들은 명세서에만 정의된 메서드이기 때문에 개발자가 코드를 사용해 호출할 순 없음
프록시 기능 활성화 예제(get 가로채기)
let numbers = [0, 1, 2];
numbers = new Proxy(numbers, {
get(target, prop) {
if (prop in target) {
return target[prop];
} else {
return 0; // 기본값
}
}
});
객체에 없는 프로퍼티를 호출했을 때, undefined가 아닌 0이 출력
자바스크립트의 내부 메서드 GET을 통해, 객체에 없는 프로퍼티를 읽으면 undefined가 출력되게 되어있음
하지만, 프록시로 0이 출력되도록 한 것
이것이 프록시의 기능
Proxy와 Object.defineProperty
▶ Object.defineProperty
Object.defineProperty()는 JavaScript에서 객체의 프로퍼티를 정의 또는 수정하는 메서드
메서드를 사용하면 객체에 새로운 프로퍼티를 추가하거나 기존 프로퍼티의 속성을 변경할 수 있음
즉, 객체에 새로운 속성을 직접 정의하거나 이미 존재하는 속성을 수정한 후 해당 객체를 반환함
defineProperty 인수
Object.defineProperty(obj, prop, descriptor);
const person = {};
Object.defineProperty(person, 'firstName', {
value: 'Ungmo',
writable: true,
enumerable: true,
configurable: true
}
- obj: 속성을 정의할 객체(반환값으로 obj 객체를 반환함)
- prop : 새로 정의하거나 수정하려는 속성의 이름 또는 Symobl
- descriptor: 새로 정의하거나 수정하려는 속성을 기술하는 객체
- value(속성값) : undefined
- get(속성 접근자 함수) : undefined
- set(속성 설정자 함수): undefined
- writable(속성의 값 변경 가능 여부) : false
- enumerable(열거 가능 여부) : false
- configurable(속성 값 변경/삭제 여부) : false
- [value, writable]과 [get, set]은 동시에 사용할 수 없음
즉, [value, writable] 중 하나(또는 전체)와 [get, set] 중 하나(또는 전체)를 함께 사용할 수 없음
특징 | Proxy | Object.defineProperty |
문법 | new Proxy(target, handler) | Object.defineProperty(obj, prop, desc) |
유형 | 메타 프로그래밍 도구 | 속성 설명자 도구 |
호환성 | ES6 이상 | ES5 이상 |
직접적 상호작용 | 대상에 대한 모든 작업을 가로챔 | 객체의 단일 속성에만 영향을 줌 |
사용 | 전체 객체의 동작 수정 | 단일 속성의 동작 수정 |
성능 | 가로채리고 인한 일반적으로 느림 | 직접 속성을 수정하므로 더 빠름 |
확장성 | 동작에 대한 세부적인 제어 제공 | 기본 속성 설명자로 제한됨 |
사용 사례 | 고급 객체 조작, 유효성 검사 | 특정 동작을 가진 새로운 속성 추가 |
- 메타 프로그래밍: 프로그램이나 언어를 조작하는 것을 의미
메타 프로그래밍 도구는 프로그램의 동작이나 구조를 변경하고 확장하기 위해 사용되는 도구들을 가리킴
Proxy를 사용하면 객체의 동작을 가로채거나 수정하여 객체에 대한 접근을 제어하거나 변경 이를 통해 객체의 동작을 수정하거나 확장하여 메타 프로그래밍 작업을 수행할 수 있음
Reference
'CS > 프로그래밍' 카테고리의 다른 글
ArrayList vs LinkedList (0) | 2024.06.07 |
---|---|
Jotai (0) | 2024.05.08 |
JS, StructuredClone() (1) | 2024.05.01 |
[Typescript] Generic과 forwardRef (0) | 2024.04.17 |
DX (2) | 2024.04.03 |