Proxy와 Reflect
Proxy 트랩으로 객체 조회·할당·호출을 가로채고 Reflect의 기본 연산에 위임해 메타 프로그래밍을 구현합니다.
우리는 8장에서 let, const, 화살표 함수, 구조 분해 할당, 스프레드 연산자,
그리고 Symbol, Map, Set까지 최신 핵심 기능을 학습했습니다.
이번에는 더 추상적이지만 실무적으로 중요한 주제인 Proxy와 Reflect를 다룹니다.
Proxy는 객체의 기본 동작(프로퍼티 접근, 할당, 함수 호출 등)을
가로채서(intercept) 원하는 방식으로 제어하게 해줍니다.
Reflect는 이런 동작을 표준 메서드 형태로 제공해
Proxy와 함께 쓸 때 안정적인 구현을 돕습니다.
이 장에서는 Proxy의 트랩(trap) 동작과 Reflect의 역할을 연결해
자바스크립트 메타 프로그래밍(Meta-programming)의 기반을 정리하겠습니다.
Proxy trap과 Reflect 기본 경로 — 상태 추적
한 Proxy 쓰기 요청의 set 경로만 추적합니다. set trap이 있으면 handler가 검사·위임하고 없으면 기본 target 동작을 따르며 성공 Boolean과 invariant·strict mode 경계를 확인합니다.
SEQUENCE · PROXY INTERNAL METHOD
Proxy trap과 Reflect 기본 경로
get·set 연산에서 trap의 유무로 경로가 갈린다. 이 예제는 trap에서 Reflect로 기본 연산을 위임하며, Proxy는 결과가 대상의 불변 조건을 지키는지 확인한다.
trap 없음
Proxy 내부 메서드가 target의 대응 내부 연산에 직접 위임하고 그 결과를 caller에게 돌려준다.
trap 있음
이 예제의 handler는 정책을 적용한 뒤 Reflect.get/set(target, …, receiver)로 기본 연산을 위임한다. Reflect 호출 자체는 trap의 필수 요건이 아니다.
결과와 invariant
Reflect.set의 false는 내부 연산의 실패 결과다. 엄격 모드 할당문에서 set trap이 false를 반환하면 TypeError가 발생한다. trap 결과가 non-configurable 등 target invariant를 어기면 Proxy가 TypeError를 던진다.
target reference 누출
원본 target을 직접 읽거나 쓰면 Proxy와 모든 trap을 우회한다. 접근 제어를 맡겼다면 원본 참조를 외부에 노출하지 않는다.
그림의 value·boolean은 내부 get·set 연산 결과다. 할당식은 대입 값을 결과로 내며, 엄격 모드에서 내부 set 실패는 TypeError가 된다. 기본 위임·선택적인 Reflect 호출·invariant 검증은 서로 다른 단계다. false 결과와 invariant 위반의 TypeError도 구분한다.
이 예의 조건
- 원도표의 전체 구조와 설명은 위 관계도와 표에서 유지합니다. 아래 인쇄 표에도 원도표의 관계·조건·라벨·설명·주석을 보존합니다.
- 두 관찰 칸의 위치는 고정입니다. 관찰 상태는 해당 순간의 값이며, 동작·이유는 그 조건에서의 판정과 변화 원인입니다. 독립 비교와 실패 분기는 별도 조건으로 읽고 모든 분기를 한 요청의 필수 실행 순서로 이어 읽지 않습니다.
- 이 단계는 한 set 연산의 요청·처리·결과입니다. 원도표 get은 독립적인 읽기 연산이며 set의 선행 단계가 아닙니다. Reflect.set 위임은 이를 선택한 handler 경로에만 적용됩니다.
관계도의 보충 설명
| 구분 | 내용 |
|---|---|
| 전체 관계·조건 설명 | 요청 뒤 trap 없음 경로는 target 기본 연산으로 직접 위임합니다. trap 있음 경로는 정책을 실행한 뒤 이 예제에서는 Reflect로 위임합니다. 두 영역은 대안이며 연속 단계가 아닙니다. Proxy는 trap 결과에 대한 대상의 불변 조건을 검사하고 위반하면 TypeError를 던집니다. 모바일은 handler와 target을 대상 측 역할로 묶고 내부의 Reflect와 기본 연산을 자기 호출로 표시합니다. Reflect 호출은 선택적이며 원본 직접 접근은 Proxy와 trap을 우회합니다. get의 값과 set의 성공 불리언은 할당식의 값·엄격 할당 실패 오류와 구별합니다. |
| 원도표 노드·간선·영역 라벨 | 대상 측 |
| 원도표 노드·간선·영역 라벨 | 대상 측 두 역할 병합 |
| 원도표 설명 | 이 예제의 handler는 정책을 적용한 뒤 Reflect.get/set(target, …, receiver)로 기본 연산을 위임한다. Reflect 호출 자체는 trap의 필수 요건이 아니다. |
| 원도표 설명 | Reflect.set의 false는 내부 연산의 실패 결과다. 엄격 모드 할당문에서 set trap이 false를 반환하면 TypeError가 발생한다. trap 결과가 non-configurable 등 target invariant를 어기면 Proxy가 TypeError를 던진다. |
| 원도표 설명 | 그림의 value·boolean은 내부 get·set 연산 결과다. 할당식은 대입 값을 결과로 내며, 엄격 모드에서 내부 set 실패는 TypeError가 된다. 기본 위임·선택적인 Reflect 호출·invariant 검증은 서로 다른 단계다. false 결과와 invariant 위반의 TypeError도 구분한다. |
| 장면 | 현재 관찰값 | 설명 | 변화 이유 |
|---|---|---|---|
| 쓰기 요청 · set 경로 | 요청: target에 대한 쓰기 / set 경로: trap 있으면 handler / 없으면 기본 동작 | Proxy 쓰기 요청에서 set trap 유무로 처리 경로를 선택한다 | 호출자는 Proxy를 통하고 실제 target은 별도다 |
| set 위임 | set 결과: 처리 성공 Boolean / target: 조건에 따라 변경 | set trap이 있는 경로는 검사 후 선택한 Reflect.set 위임 결과를 반환한다 | set 처리 성공 Boolean과 대입 표현식의 오른쪽 값은 별개다 |
| 결과 판정 | 허용: 대입 동작 완료 / 거절: 조건에 따라 throw | invariant 및 strict mode 실패 조건을 판정한다 | trap이 임의로 target invariant를 무시할 수 없다 |
Proxy trap과 Reflect 기본 경로 — 상태 추적
한 Proxy 쓰기 요청의 set 경로만 추적합니다. set trap이 있으면 handler가 검사·위임하고 없으면 기본 target 동작을 따르며 성공 Boolean과 invariant·strict mode 경계를 확인합니다.
SEQUENCE · PROXY INTERNAL METHOD
Proxy trap과 Reflect 기본 경로
get·set 연산에서 trap의 유무로 경로가 갈린다. 이 예제는 trap에서 Reflect로 기본 연산을 위임하며, Proxy는 결과가 대상의 불변 조건을 지키는지 확인한다.
trap 없음
Proxy 내부 메서드가 target의 대응 내부 연산에 직접 위임하고 그 결과를 caller에게 돌려준다.
trap 있음
이 예제의 handler는 정책을 적용한 뒤 Reflect.get/set(target, …, receiver)로 기본 연산을 위임한다. Reflect 호출 자체는 trap의 필수 요건이 아니다.
결과와 invariant
Reflect.set의 false는 내부 연산의 실패 결과다. 엄격 모드 할당문에서 set trap이 false를 반환하면 TypeError가 발생한다. trap 결과가 non-configurable 등 target invariant를 어기면 Proxy가 TypeError를 던진다.
target reference 누출
원본 target을 직접 읽거나 쓰면 Proxy와 모든 trap을 우회한다. 접근 제어를 맡겼다면 원본 참조를 외부에 노출하지 않는다.
그림의 value·boolean은 내부 get·set 연산 결과다. 할당식은 대입 값을 결과로 내며, 엄격 모드에서 내부 set 실패는 TypeError가 된다. 기본 위임·선택적인 Reflect 호출·invariant 검증은 서로 다른 단계다. false 결과와 invariant 위반의 TypeError도 구분한다.
이 예의 조건
- 원도표의 전체 구조와 설명은 위 관계도와 표에서 유지합니다. 아래 인쇄 표에도 원도표의 관계·조건·라벨·설명·주석을 보존합니다.
- 두 관찰 칸의 위치는 고정입니다. 관찰 상태는 해당 순간의 값이며, 동작·이유는 그 조건에서의 판정과 변화 원인입니다. 독립 비교와 실패 분기는 별도 조건으로 읽고 모든 분기를 한 요청의 필수 실행 순서로 이어 읽지 않습니다.
- 이 단계는 한 set 연산의 요청·처리·결과입니다. 원도표 get은 독립적인 읽기 연산이며 set의 선행 단계가 아닙니다. Reflect.set 위임은 이를 선택한 handler 경로에만 적용됩니다.
관계도의 보충 설명
| 구분 | 내용 |
|---|---|
| 전체 관계·조건 설명 | 요청 뒤 trap 없음 경로는 target 기본 연산으로 직접 위임합니다. trap 있음 경로는 정책을 실행한 뒤 이 예제에서는 Reflect로 위임합니다. 두 영역은 대안이며 연속 단계가 아닙니다. Proxy는 trap 결과에 대한 대상의 불변 조건을 검사하고 위반하면 TypeError를 던집니다. 모바일은 handler와 target을 대상 측 역할로 묶고 내부의 Reflect와 기본 연산을 자기 호출로 표시합니다. Reflect 호출은 선택적이며 원본 직접 접근은 Proxy와 trap을 우회합니다. get의 값과 set의 성공 불리언은 할당식의 값·엄격 할당 실패 오류와 구별합니다. |
| 원도표 노드·간선·영역 라벨 | 대상 측 |
| 원도표 노드·간선·영역 라벨 | 대상 측 두 역할 병합 |
| 원도표 설명 | 이 예제의 handler는 정책을 적용한 뒤 Reflect.get/set(target, …, receiver)로 기본 연산을 위임한다. Reflect 호출 자체는 trap의 필수 요건이 아니다. |
| 원도표 설명 | Reflect.set의 false는 내부 연산의 실패 결과다. 엄격 모드 할당문에서 set trap이 false를 반환하면 TypeError가 발생한다. trap 결과가 non-configurable 등 target invariant를 어기면 Proxy가 TypeError를 던진다. |
| 원도표 설명 | 그림의 value·boolean은 내부 get·set 연산 결과다. 할당식은 대입 값을 결과로 내며, 엄격 모드에서 내부 set 실패는 TypeError가 된다. 기본 위임·선택적인 Reflect 호출·invariant 검증은 서로 다른 단계다. false 결과와 invariant 위반의 TypeError도 구분한다. |
| 장면 | 현재 관찰값 | 설명 | 변화 이유 |
|---|---|---|---|
| 쓰기 요청 · set 경로 | 요청: target에 대한 쓰기 / set 경로: trap 있으면 handler / 없으면 기본 동작 | Proxy 쓰기 요청에서 set trap 유무로 처리 경로를 선택한다 | 호출자는 Proxy를 통하고 실제 target은 별도다 |
| set 위임 | set 결과: 처리 성공 Boolean / target: 조건에 따라 변경 | set trap이 있는 경로는 검사 후 선택한 Reflect.set 위임 결과를 반환한다 | set 처리 성공 Boolean과 대입 표현식의 오른쪽 값은 별개다 |
| 결과 판정 | 허용: 대입 동작 완료 / 거절: 조건에 따라 throw | invariant 및 strict mode 실패 조건을 판정한다 | trap이 임의로 target invariant를 무시할 수 없다 |
Proxy: 객체의 동작 가로채기
Proxy 객체는 다른 객체(타겟 객체)의 프록시(대리인) 역할을 합니다.
Proxy를 통한 프로퍼티 읽기·쓰기 등의 기본 연산을 해당 트랩으로 가로챕니다. 함수 호출은 호출 가능한 target에, new는 생성 가능한 target에만 적용됩니다. 일반 객체를 프록시로 감쌌다고 함수나 생성자가 되는 것은 아닙니다.
Proxy 생성하기
new Proxy(target, handler) 생성자를 사용하여 Proxy 객체를 생성합니다.
target: 프록시할 대상 객체 (일반 객체, 배열, 함수 등).handler: 객체의 동작을 가로챌 트랩(trap) 메서드들을 정의하는 객체.
const targetObject = {
message1: '안녕하세요',
message2: '반갑습니다'
};
const handler = {
// get 트랩: 프로퍼티를 읽으려 할 때 호출됨
get: function(target, property, receiver) {
console.log(`[Proxy] '${String(property)}' 프로퍼티를 읽으려 합니다.`);
// 원래의 프로퍼티 값을 반환
return target[property];
},
// set 트랩: 프로퍼티에 값을 할당하려 할 때 호출됨
set: function(target, property, value, receiver) {
console.log(`[Proxy] '${String(property)}' 프로퍼티에 '${value}'를 쓰려 합니다.`);
// 원래의 할당 동작 수행
target[property] = value;
return true; // 성공적으로 할당되었음을 나타냄
}
};
const proxyObject = new Proxy(targetObject, handler);
// proxyObject를 통해 프로퍼티에 접근하거나 값을 할당하면 handler의 트랩이 동작합니다.
console.log(proxyObject.message1); // get 트랩 호출 후 "안녕하세요" 출력
proxyObject.message1 = '안녕'; // set 트랩 호출
console.log(proxyObject.message1); // get 트랩 호출 후 "안녕" 출력
console.log(proxyObject.message2); // get 트랩 호출 후 "반갑습니다" 출력주요 트랩(Traps) 종류
handler 객체에는 다양한 종류의 트랩 메서드를 정의할 수 있습니다.
각 트랩은 특정 객체 연산을 가로챕니다.
| 트랩 메서드 | 설명 | 가로채는 연산 |
|---|---|---|
get(target, property, receiver) | 프로퍼티 읽기 | proxy.foo, proxy['bar'] |
set(target, property, value, receiver) | 프로퍼티 쓰기 | proxy.foo = 1, proxy['bar'] = 2 |
has(target, property) | in 연산자 | 'foo' in proxy |
deleteProperty(target, property) | delete 연산자 | delete proxy.foo |
apply(target, thisArg, argumentsList) | 함수 호출 (Proxy 대상이 함수일 때) | proxy(...args) |
construct(target, argumentsList, newTarget) | new 연산자 (생성자로 호출할 때, Proxy 대상이 생성자 함수일 때) | new proxy(...args) |
ownKeys(target) | Object.keys(), Object.getOwnPropertyNames(), Object.getOwnPropertySymbols() 등 | Object.keys(proxy) |
getPrototypeOf(target) | Object.getPrototypeOf(), __proto__ 등 | Object.getPrototypeOf(proxy) |
setPrototypeOf(target, prototype) | Object.setPrototypeOf() | Object.setPrototypeOf(proxy, proto) |
isExtensible(target) | Object.isExtensible() | Object.isExtensible(proxy) |
preventExtensions(target) | Object.preventExtensions() | Object.preventExtensions(proxy) |
defineProperty(target, property, descriptor) | Object.defineProperty(), Object.defineProperties() | Object.defineProperty(proxy, 'foo', descriptor) |
getOwnPropertyDescriptor(target, property) | Object.getOwnPropertyDescriptor() | Object.getOwnPropertyDescriptor(proxy, 'foo') |
const userProfile = {
name: "Unknown",
age: 0
};
const userValidator = {
set: function(obj, prop, value) {
if (prop === 'age') {
if (typeof value !== 'number' || value < 0 || value > 150) {
console.error("나이는 0에서 150 사이의 숫자여야 합니다.");
return false; // 할당 실패
}
} else if (prop === 'name') {
if (typeof value !== 'string' || value.length < 2) {
console.error("이름은 최소 2글자 이상이어야 합니다.");
return false; // 할당 실패
}
}
obj[prop] = value; // 유효성 검사 통과 시 실제 할당
return true;
}
};
const proxyUser = new Proxy(userProfile, userValidator);
proxyUser.name = "John Doe"; // 유효성 통과
proxyUser.age = 30; // 유효성 통과
console.log(proxyUser); // 결과: { name: 'John Doe', age: 30 }
proxyUser.age = -5; // 결과: 나이는 0에서 150 사이의 숫자여야 합니다.
proxyUser.name = "A"; // 결과: 이름은 최소 2글자 이상이어야 합니다.
console.log(proxyUser); // 여전히 { name: 'John Doe', age: 30 } (변경되지 않음)이 예시는 비엄격 script의 할당을 가정합니다. 엄격 모드나 ES module에서는 set trap이 false를 반환한 첫 할당에서 TypeError가 발생해, 잡지 않으면 그 뒤 줄은 실행되지 않습니다. 원문의 숫자 검사에는 NaN 검사가 없으므로 완전한 입력 검증으로 보아서는 안 됩니다.
Proxy를 통한 접근은 객체의 내부 구현을 바꾸지 않고 제어할 수 있지만, 원본 target에 직접 접근하면 trap을 거치지 않습니다.
이는 Vue 3의 반응성 시스템(Reactivity System)이나 MobX 같은 상태 관리 라이브러리에서 데이터 변경 감지에 활용됩니다.
Reflect: 객체 연산의 내장 헬퍼 객체
Reflect는 ES2015(ES6)에서 도입된 내장 객체로, 객체에 대한 다양한 내부 연산들을 메서드 형태로 제공합니다.
Reflect는 Proxy와 밀접한 관련이 있으며, 다음과 같은 특징을 가집니다.
- 메서드 제공:
Object객체의 일부 메서드(Object.keys(),Object.defineProperty())와 유사하지만,Reflect는 대부분의 객체 내부 연산을Function.prototype.apply()나new연산자처럼 '메서드 호출' 형태로 제공합니다. - Proxy 핸들러와의 시너지:
Proxy트랩 내에서Reflect메서드를 사용하면, 원래의 기본 객체 연산을 깔끔하게 호출할 수 있습니다. 이는target[property]와 같은 직접적인 접근보다 권장됩니다. - 불리언 반환: 많은
Reflect메서드는 성공 여부를 불리언 값으로 반환하여, 연산의 성공/실패 여부를 쉽게 판단할 수 있도록 합니다. (예:Reflect.defineProperty,Reflect.deleteProperty)
Reflect의 주요 메서드
Reflect의 메서드들은 대부분 Proxy 핸들러 트랩과 1:1로 매칭됩니다.
Reflect.get(target, property, receiver)Reflect.set(target, property, value, receiver)Reflect.has(target, property)Reflect.deleteProperty(target, property)Reflect.apply(target, thisArgument, argumentsList)Reflect.construct(target, argumentsList, newTarget)- ... 그 외
Object객체와 유사한 메서드들
Proxy 핸들러 내에서 Reflect를 사용하면, 기본 동작을 호출하면서도 this 바인딩(receiver 인자)과 같은 복잡한 내부 로직을 정확하게 처리할 수 있습니다.
const user = {
firstName: "Jane",
lastName: "Doe",
get fullName() {
return `${this.firstName} ${this.lastName}`;
}
};
const loggingHandler = {
get(target, prop, receiver) {
console.log(`[Reflect] '${String(prop)}' 프로퍼티에 접근했습니다.`);
// Reflect.get을 사용하여 원래의 getter를 정확하게 호출
return Reflect.get(target, prop, receiver);
},
set(target, prop, value, receiver) {
console.log(`[Reflect] '${String(prop)}' 프로퍼티를 '${value}'로 설정했습니다.`);
// Reflect.set을 사용하여 원래의 setter를 정확하게 호출
return Reflect.set(target, prop, value, receiver);
}
};
const proxiedUser = new Proxy(user, loggingHandler);
console.log(proxiedUser.firstName); // get 트랩, Reflect.get 호출
proxiedUser.lastName = "Smith"; // set 트랩, Reflect.set 호출
console.log(proxiedUser.fullName); // get 트랩 (fullName getter가 this.firstName/lastName을 참조하므로 다시 get 트랩 호출)위 예시에서 Reflect.get(target, prop, receiver)는 단순히 target[prop]을 하는 것보다 receiver 인자를 통해 fullName과 같은 getter의 this 컨텍스트를 프록시 객체 자신으로 정확하게 유지시켜줍니다.
위 fullName 읽기는 fullName 트랩 → getter 실행 → firstName 트랩 → lastName 트랩 → 문자열 반환 순서입니다. getter의 this가 receiver이므로 내부 읽기도 프록시를 통과합니다. 단순히 target[prop]으로 읽으면 getter의 this는 target이어서 내부 조회는 해당 프록시 트랩을 우회합니다.
Reflect의 다른 유용한 메서드
Reflect는 Object 객체에서 에러를 던지던 일부 연산들을 불리언 값을 반환하도록 하여 더욱 유연한 에러 핸들링을 가능하게 합니다.
const obj = {};
// Object.defineProperty는 실패 시 에러를 던짐
try {
Object.defineProperty(obj, 'prop', { value: 1, writable: false });
Object.defineProperty(obj, 'prop', { value: 2 }); // TypeError: Cannot redefine property
} catch (e) {
console.error(e.message);
}
// Reflect.defineProperty는 실패 시 false를 반환
const success1 = Reflect.defineProperty(obj, 'anotherProp', { value: 10 });
console.log(success1); // 결과: true
const success2 = Reflect.defineProperty(obj, 'prop', { value: 2 }); // 실패
console.log(success2); // 결과: false (에러를 던지지 않음)이러한 특성은 특히 복잡한 객체 조작 로직이나 라이브러리 구현 시 유용합니다.
Proxy 트랩을 직접 작성할 때는 "무엇을 가로챌지"만큼 "기본 객체 규칙을 어떻게 보존할지"가 중요합니다.
앞의 Proxy trap과 Reflect 기본 경로 다이어그램은 무한 재귀, 잘못된 반환값, receiver 누락을 구현 전에 점검하는 기준입니다.
프레임워크는 이러한 연산에 별도의 의존성 추적·렌더 예약을 결합합니다. Proxy나 Reflect 자체가 화면을 다시 그리는 것은 아닙니다. 또한 Reflect.get(proxy, ...)처럼 같은 프록시로 다시 조회하면 트랩에 재진입할 수 있습니다. 기본 위임의 첫 인수인 target을 확인하세요.
예시
입력 검증과 저장 성공을 별도로 확인할 수 있도록, 원문의 나이 검사를 유한 정수까지 요구하는 정책으로 확장해 봅시다. 아래 코드는 원문 예제와 별개로 실행합니다. 실패에도 계속 진행하려고 할당문 대신 불리언을 반환하는 Reflect.set을 사용합니다.
const profile = { age: 0 };
const checkedProfile = new Proxy(profile, {
set(target, key, value, receiver) {
if (key === "age" &&
(!Number.isInteger(value) || value < 0 || value > 150)) {
return false;
}
return Reflect.set(target, key, value, receiver);
}
});
console.log(Reflect.set(checkedProfile, "age", 42), profile.age);
console.log(Reflect.set(checkedProfile, "age", -1), profile.age);
console.log(Reflect.set(checkedProfile, "age", NaN), profile.age);출력은 true 42, false 42, false 42입니다. 거부한 값은 저장하지 않았고, 허용한 값도 실제 기본 쓰기의 성공 여부를 반환합니다. 일반 객체에 대한 이 경로의 실패 불리언과 엄격 모드 할당문의 TypeError는 다른 관찰 결과입니다.
스스로 확인하기
마지막 호출을 Reflect.set(checkedProfile, "age", 42.5)로 바꾸면 결과는 무엇인가요? 이어 별도의 실행에서 원본 profile.age = -1을 직접 수행하면 트랩이 거부할 수 있나요?
풀이 확인
소수는 정수 정책을 통과하지 못하므로 false 42입니다. 원본 target에 대한 직접 할당은 프록시를 거치지 않아 -1이 저장됩니다. 따라서 이 예제는 프록시 경로의 검증이며 원본 참조까지 포함한 보안 경계를 자동으로 만들지 않습니다.
receiver를 확인하는 작은 실행
const reads = [];
const scoreTarget = {
points: 3,
get score() { return this.points * 2; }
};
const scoreProxy = new Proxy(scoreTarget, {
get(target, key, receiver) {
reads.push(String(key));
return Reflect.get(target, key, receiver);
}
});
console.log(scoreProxy.score, reads.join(","));6 score,points가 출력됩니다. Reflect.get(target, key)로 마지막 인수만 생략하고 처음부터 실행하면 6 score입니다. 계산값은 같아도 내부 읽기가 프록시를 통과하는지는 다릅니다. 이 차이가 getter와 변경 추적을 함께 쓸 때 중요합니다.
기본 연산도 대상의 불변 조건을 지켜야 합니다. 예를 들어 변경·재구성이 금지된 자체 데이터 프로퍼티를 읽는 트랩은 실제 값과 다른 값을 마음대로 반환할 수 없습니다. 단순히 true를 반환한다고 모든 쓰기가 허용되는 것도 아닙니다. 자세한 제약은 MDN Proxy 설명, getter의 수신 객체는 MDN Reflect.get 설명에서 확인하세요.
Proxy와 Reflect 통합 정리
이번 장에서는 ES2015(ES6)에서 도입된 객체 동작의 메타 프로그래밍 기능인 Proxy와 Reflect를 학습했습니다.
Proxy는 target 객체의 프로퍼티 읽기/쓰기, 함수 호출 같은 연산을 handler로 가로챌 수 있습니다.
이를 이용하면 유효성 검사, 로깅, 접근 제어를 객체의 핵심 구현과 분리해 둘 수 있습니다.
또한, Reflect 객체가 객체 내부 연산을 위한 메서드들을 제공하며, 특히 Proxy 핸들러 내에서 target[prop] 대신 Reflect 메서드를 사용함으로써 보다 정확하고 견고한 동작을 보장한다는 것을 배웠습니다.
Reflect.set·defineProperty 등은 성공 여부를 불리언으로 반환합니다. Reflect.get은 조회한 값을 반환하며, 메서드와 상황에 따라 예외도 발생할 수 있습니다.
Proxy와 Reflect는 객체 연산을 가로채거나 표준 내부 연산에 맞춰 위임할 때 사용합니다.
Vue.js 3 같은 프레임워크의 반응성 구조를 읽을 때도 이 개념이 기준이 됩니다.
Proxy를 안전하게 사용하려면 생성, 주요 trap, Reflect 위임, receiver와 반환값을 하나의 연산 계약으로 점검해야 합니다.