📌 프록시(Proxy) 란?
프록시(Proxy)는 JavaScript에서 원본 객체의 동작을 가로채어 제어할 수 있는 기능입니다.
이를 통해 객체의 속성 읽기, 쓰기, 삭제, 함수 호출 같은 작업을
중간에서 조작하여 객체 보호, 동작 커스터마이징, 유연한 관리가 가능합니다.
주로 보안이 중요한 정보 보호나 복잡한 데이터 구조 관리에서 유용합니다.
🔎 프록시 주요 역할
- 객체 보호 : 민감한 정보 보호
- 동작 커스터마이징 : 속성 접근 방식 조정
- 유연한 관리 : 객체를 수정하지 않고 원하는 동작 추가
🔎 프록시 특징
- 대리인 역할 : 원본 객체 대신 요청을 받고 결과를 반환
- 명령 재정의 : 속성 접근, 삭제 등 작업을 원하는 대로 변경
📌 프록시(Proxy) 객체
원본 객체의 기본 동작(속성 접근, 할당, 함수 호출 등)을 가로채고 제어할 수 있는 객체입니다.
프록시 객체를 생성하려면 new Proxy(target, handler) 사용하며, 두 인자 필요합니다.
- target : 프록시로 감싸는 원본 객체
- handler : 원본 객체의 동작을 가로채는 함수(트랩*)이 정의된 객체
const proxy = new Proxy(target, handler);
new Proxy(원본객체/함수, 핸들러함수);
💡 트랩(Trap)
트랩은 프록시가 동작을 가로채기 위해 설정하는 함수로, 기본 명령을 재정의하거나 제어할 수 있게 합니다.
📌 프록시 트랩 핸들러(handler) 메서드
프록시의 handler 객체는 특정 동작을 가로채고 제어할 수 있는 메서드들을 정의합니다.
가로챌 수 있는 동작에는 객체의 속성 접근, 함수 호출, 프로토타입 조작 등이 있습니다.
이때 사용하는 핸들러의 메서드를 트랩(trap)이라고 부릅니다.
트랩 메서드는 속성 접근, 할당, 함수 호출 등 원본 객체의 다양한 동작을 가로채어 재정의하거나 제어
🔎 Property(객체 속성) 가로채는 트랩
- get() 함수 : 객체의 속성에 접근할 때 작업을 가로챔
- set() 함수 : 객체 속성에 값을 할당할 때 작업을 가로챔
- has() 함수 : 객체에 in 연산자가 사용될 때 작업을 가로챔
- deleteProperty() 함수 : 객체 속성에 delete 연산자가 사용될 때 작업을 가로챔
핸들러 메서드 | 작동 시점 |
get(target, property) | 프로퍼티에 접근하여 값을 읽을 때 호출 프록시객체명.속성명 target ➡️ 원본 객체 property ➡️ 접근하려는 속성명 |
set(target, property, value) | 프로퍼티 값을 설정할 때 호출 프록시객체명.속성명 = 변경할값; target ➡️ 원본 객체 property ➡️ 설정하려는 속성명 value ➡️ 설정할 값 |
has(target, property) | in 연산자가 사용될 때 호출(속성 존재 여부 확인) 속성명 in 프록시객체명 target ➡️ 원본 객체 property ➡️ 확인하려는 속성명 |
deleteProperty(target, property) | delete 연산자가 사용될 때 호출 delete 프록시객체명.속성명; target ➡️ 원본 객체 property ➡️ 삭제하려는 속성명 |
🔎 Method(함수) 가로채는 트랩
- apply() 함수 : 함수가 호출될 때 동작을 가로챔
- construct() 함수 : new 연산자를 사용해 생성자 함수를 호출할 때 작업을 가로챔
핸들러 메서드 | 작동 시점 |
apply(target, thisArg, args) | 함수가 호출될 때 프록시함수명(매개인자1, 매개인자2, ...); target ➡️ 원본 함수 thisArg ➡️ 함수 호출 시 사용될 this 값 args ➡️ 함수에 전달된 매개변수 목록 |
construct(target, args) | 생성자가 호출될 때 (new 연산자 사용 시) new 프록시객체명(매개인자1, 매개인자2, ...); target ➡️ 원본 생성자 함수 args ➡️ 생성자에 전달된 매개변수 목록 |
✏️ 프록시(Proxy) 사용방법
프록시는 객체나 함수의 접근, 변경, 삭제 등의 동작을 가로채고 원하는 로직을 추가할 수 있는 기능
이를 통해 데이터 보호, 함수 호출 제어, 값 검증 등 다양한 작업을 수행할 수 있습니다.
프록시가 설정된 객체나 함수에서 특정 작업이 발생하면,
자바스크립트는 해당 작업에 연결된 트랩 메서드를 자동으로 호출합니다.
트랩 메서드가 있으면 이를 실행하고, 없으면 원래 동작을 그대로 수행합니다.
- 객체형 프록시 : 객체 보호/제어 (get, set, has, deleteProperty)
- 함수형 프록시 : 함수 호출 제어 (apply)
- 생성자 프록시 : 객체 생성 제어 (construct)
- 값 검증 프록시 : 특정 값 제한 (set)
- 읽기 전용 프록시 : 읽기 전용 객체 (set)
- 읽기 전용 프록시 : 읽기 전용 함수 (apply)
1. 객체형 프록시 : 객체 보호/제어 (get, set, has, deleteProperty)
프록시는 원본 객체에 대한 접근을 가로채 특정 조건에 따라 제어할 수 있습니다.
originalUser 객체에서 pw 속성은 항상 ******로 표시되게 하고,
realpw로 접근할 때만 실제 비밀번호를 보여줄 수 있습니다.
이렇게 설정하면 proxyUser.pw는 마스킹된 값만 표시되고,
proxyUser.realpw 통해서만 실제 비밀번호를 확인할 수 있습니다.
- get : 프록시 객체의 속성에 접근할 때 get 트랩 자동 실행
예) proxyUser.pw 접근 시 ➡️ get 트랩 작동 - set : 프록시 객체의 속성에 값을 할당할 때 set 트랩 자동 실행
예) proxyUser.id = '소현'; ➡️ 할당 시 set 트랩 작동 - deleteProperty : delete 연산자를 사용할 때 deleteProperty 트랩 자동 실행
예) delete proxyUser.id; 실행 시 ➡️ deleteProperty 트랩 작동
// 📜index.js
// 1️⃣ 원본 객체 생성
const originalUser = { id: 'root', pw: '1234' };
// 2️⃣ 프록시 핸들러 설정
const handler = {
// ● 속성 접근 시 동작 설정
get(origin, props) {
if (props === 'pw') return '******'; // 'pw' 접근 시 ****** 반환
else if (props === 'realpw') return origin.pw; // 'realpw' 접근 시 실제 pw반환
else return origin[props];
},
// ● 속성 변경 시 동작 설정
set(origin, props, value) {
if (props === 'pw') return; // 'pw' 속성은 변경 금지
else origin[props] = value; // 다른 속성은 변경 허용
},
// ● 속성 존재 확인 시 동작 설정
has(origin, props) {
if (props === 'pw') return false; // 'pw' 속성이 있는지 확인 시 false 반환
return true; // 다른 속성은 true 반환
},
// ● 속성 삭제 시 동작 설정
deleteProperty(origin, props) {
delete origin[props]; // 실제 속성 삭제
}
};
// 3️⃣ 프록시 객체 생성
const proxyUser = new Proxy(originalUser, handler);
// 4️⃣ 프록시 객체의 메서드 호출
// ➡️ get 메서드
// get 메서드로 pw 속성 접근 시 ******가 반환되고, realpw 접근 시 실제 비밀번호 반환
console.log(proxyUser.id); // 'root' 출력
console.log(proxyUser.pw); // '******' 출력
console.log(proxyUser.realpw); // 실제 pw '1234' 출력
// ➡️ set 메서드
// set 메서드로 id는 수정할 수 있지만, pw는 변경이 허용되지 않습니다.
proxyUser.id = '소현'; // id는 변경 허용
proxyUser.pw = '7890'; // pw는 변경되지 않음
console.log(proxyUser.id); // '소현' 출력
console.log(proxyUser.pw); // '******' 출력
console.log(proxyUser.realpw); // 실제 pw '1234' 출력
// ➡️ has 메서드
// has 메서드를 통해 in 연산자를 사용할 때 pw는 항상 존재하지 않는 것처럼 표시됩니다.
console.log('id' in proxyUser); // true 출력
console.log('pw' in proxyUser); // false 출력 (has 메서드에서 false 반환)
// ➡️ deleteProperty 메서드
// deleteProperty 메서드를 통해 id 속성이 삭제됩니다.
delete proxyUser.id; // id 속성 삭제
console.log(proxyUser.id); // undefined 출력 (id가 삭제되었기 때문에 undefined)
2. 함수형 프록시 : 함수 호출 제어 (apply)
함수 originalMinMax 프록시로 감싸, 전달된 인자의 순서를 자동으로 조정합니다.
apply 메서드를 사용하여 함수가 호출될 때 동작을 제어합니다.
프록시 proxyMinMax로 감싼 originalMinMax 함수는 인자 순서를 자동으로 조정합니다.
- apply : 프록시 객체가 함수로 호출될 때 apply 트랩 자동 실행
예) proxyMinMax(5, 3); 호출 시 ➡️ apply 트랩 작동
// 1️⃣ 원본 함수 정의
// originalMinMax 함수는 min부터 max까지의 숫자를 순서대로 출력
function originalMinMax(min, max) {
for (let i = min; i <= max; i += 1) {
console.log(i);
}
}
// 2️⃣ 프록시 핸들러 설정
// apply 트랩은 함수가 호출될 때 인자 순서를 확인하여 필요 시 순서를 자동으로 조정합니다.
const handler = {
apply(origin, thisArg, args) {
// const [min, max, ...losts] = args; // 세 번째 이후 인자를 배열로 수집
const [min, max] = args;
if (min < max) return origin(min, max); // 순서가 맞으면 그대로 실행
else return origin(max, min); // 순서가 반대면 교환하여 실행
}
};
// 3️⃣ 프록시 객체 생성
// originalMinMax 함수를 프록시로 감싸서 proxyMinMax 객체를 생성하여
// apply 트랩이 적용되도록 합니다.
const proxyMinMax = new Proxy(originalMinMax, handler);
// 4️⃣ 프록시 함수 호출
// 프록시 객체 proxyMinMax를 호출합니다.
// apply 트랩이 작동하여
// 인자가 5와 3 순서로 전달되더라도, 작은 값부터 큰 값까지 출력되도록 자동 조정됩니다.
proxyMinMax(5, 3); // 3부터 5까지 출력 (3 4 5)
3. 생성자 프록시 : 객체 생성 제어 (construct)
생성자 함수 Person 동작을 프록시로 감싸서, 객체 생성 시 추가 작업을 할 수 있습니다.
이 프록시는 new 연산자를 사용할 때 객체 생성 동작을 가로챌 수 있습니다.
💡 construct 트랩에서는 Reflect.construct를 사용해야 올바르게 객체가 생성됩니다.
💡 construct 트랩은 객체 생성 시에만 사용되며, 함수를 클래스처럼 활용할 때 유용합니다.
- construct : 프록시 객체가 생성자로 호출될 때 construct 트랩 자동 실행
예) new proxyPerson('소현', '29'); 호출 시 ➡️ construct 트랩 작동
// 1️⃣ 원본 함수 정의
// Person 생성자 함수는 name과 age를 받아 새로운 객체를 생성합니다.
function Person(name, age) {
this.name = name;
this.age = age;
}
// 2️⃣ 프록시 핸들러 설정
// construct 트랩은 new 연산자로 호출될 때 실행되며,
// Reflect.construct를 통해 원래 Person 생성자 함수의 동작을 그대로 수행
const handler = {
construct(origin, args, newTarget) {
return Reflect.construct(origin, args, newTarget);
}
};
// 3️⃣ 프록시 객체 생성
// Person 함수를 프록시로 감싸서 proxyPerson 프록시 객체 생성
// 이제 proxyPerson 객체가 new 연산자로 호출될 때마다 construct 트랩 작동
const proxyPerson = new Proxy(Person, handler);
// 4️⃣ 프록시 생성자 호출
// 프록시 객체 proxyPerson을 생성자로 호출하여 새로운 객체를 생성
// construct 트랩이 작동하여 Person 생성자를 통해 객체가 생성
const personInstance = new proxyPerson('소현', '29');
console.log(personInstance); // Person { name: '소현', age: '29' }
4. 값 검증 프록시 : 특정 값 제한 (set)
utillity_origin 객체의 data 속성을 프록시로 감싸서, data 값이 0 미만으로 설정되지 않도록 합니다.
// 1️⃣ 원본 객체 생성
// utillity_origin 객체는 data 속성을 가지고 있으며, 초기 값 50
const utillity_origin = { data: 50 };
// 2️⃣ 프록시 핸들러 설정
// set 트랩은 data 속성에 값을 설정할 때마다 작동하며,
// data에 0 미만의 값이 할당될 경우 0으로 자동 조정
const handler = {
set(origin, props, value) {
if (props === 'data') {
origin[props] = value < 0 ? 0 : value; // data가 0 미만이면 0으로 설정
}
}
};
// 3️⃣ 프록시 객체 생성
// utillity_origin 객체를 프록시로 감싸서, utillity 프록시 객체 생성
// 이제 data 속성에 값을 할당할 때마다 set 트랩이 작동
const utillity = new Proxy(utillity_origin, handler);
// 4️⃣ 프록시 객체 사용
console.log(utillity.data); // 50 출력
// utillity.data에 -50을 설정하면, set 트랩 작동하여 0 설정
utillity.data = -50;
console.log(utillity.data); // 0 출력
// utillity.data에 70을 설정하면, set 트랩 작동하여 70 설정
utillity.data = 70;
console.log(utillity.data); // 70 출력
5. 읽기 전용 프록시 : 읽기 전용 객체 (set)
readOnly 함수는 원본 객체가 수정되지 않도록 읽기 전용 프록시를 생성합니다.
// 1️⃣ 읽기 전용 프록시 생성 함수
// set 트랩에서 설정을 무시하여, 속성 값을 변경할 수 없도록 만듦
function readOnly(origin) {
return new Proxy(origin, {
set() { return; } // 설정을 무시하여 읽기 전용으로 만듦
});
}
// 2️⃣ 읽기 전용 객체 생성
// readOnly 함수를 사용하여 원본 객체가 수정되지 않는 읽기 전용 객체 readusers 생성
const readusers = readOnly({ id: 'root', pw: '1234' });
// 3️⃣ 읽기 전용 속성 변경 시도
// 속성 변경이 무시되므로, readusers 객체는 원본 상태를 유지합니다.
readusers.id = 'admin'; // 변경 시도 무시
console.log(readusers); // { id: 'root', pw: '1234' }
6. 읽기 전용 프록시 : 읽기 전용 함수 (apply)
readOnlyFunc 함수는 전달된 함수(func)를 읽기 전용으로 감싸
함수가 호출될 때 인자로 전달된 객체가 수정되지 않도록 합니다.
이를 통해 함수 내부에서 객체 속성을 변경하려 해도 원본 객체는 유지됩니다.
// 1️⃣ 읽기 전용 함수 생성 함수
// readOnlyFunc 함수는 전달된 함수(func)를 프록시로 감싸서,
// 해당 함수가 호출될 때 인자로 전달된 객체를 읽기 전용으로 변환하여 전달합니다.
function readOnlyFunc(func) {
return new Proxy(func, {
apply(origin, thisArg, args) {
// 함수 호출 시, 인자로 전달된 모든 객체를 읽기 전용으로 변환하여 전달
const readOnlyArgs = args.map(arg =>
typeof arg === 'object' ? new Proxy(arg, { set() { return; } }) : arg
);
// 읽기 전용 객체로 변환된 인자들로 함수 호출
return origin.apply(thisArg, readOnlyArgs);
}
});
}
// ➡️ 읽기 전용 속성 변경 시도
// modifyUser 함수는 전달받은 user 객체의 id 값을 변경하려고 시도합니다.
function modifyUser(user) {
user.id = 'modified'; // 시도해도 변경되지 않음(읽기전용)
console.log(user); // { id: 'original', pw: '1234' } 출력
}
// 2️⃣ 읽기 전용 함수 생성
// modifyUser 함수를 읽기 전용 함수로 감싼 readOnlyModifyUser 생성
const readOnlyModifyUser = readOnlyFunc(modifyUser);
// 3️⃣ 사용할 객체 생성
const user = { id: 'original', pw: '1234' };
// 4️⃣ 읽기 전용 함수 사용
// user 객체를 전달하여 readOnlyModifyUser를 호출
// modifyUser 함수에서 user.id를 변경하려고 해도 원본 객체는 수정되지 않음
readOnlyModifyUser(user); // { id: 'original', pw: '1234' } 출력