概要
JavaScriptの Proxy
と Reflect
は、オブジェクトの操作を透明かつ動的に監視・操作・再定義できる構文的インターフェースである。
これにより、以下のような構造制御が可能になる:
- アクセスログの記録 / 非公開プロパティのガード
- 動的バリデーション / エラーハンドリング
- APIレスポンスの型補正 / 自動変換
- Vue.jsなどリアクティブシステムの根幹処理
この記事では、ProxyとReflectの構文と設計的活用戦略を体系的に整理する。
Proxyの基本構造
const target = { name: 'Toto' };
const handler = {
get(target, prop) {
console.log(`get ${prop}`);
return target[prop];
}
};
const proxy = new Proxy(target, handler);
proxy.name; // → get name → 'Toto'
→ ✅ proxy.name
の呼び出しを handler.get()
が傍受する
サポートされるトラップ一覧
操作 | トラップ名 |
---|---|
プロパティ取得 | get |
プロパティ設定 | set |
存在確認(in演算子) | has |
削除 | deleteProperty |
列挙(for...in) | ownKeys |
Object.keys | ownKeys |
Object.getOwnPropertyDescriptor | getOwnPropertyDescriptor |
プロパティ定義 | defineProperty |
関数呼び出し | apply |
newによる生成 | construct |
Reflect の役割
Reflect.get(target, prop, receiver);
Reflect.set(target, prop, value, receiver);
Reflect.has(target, prop);
- ✅ 通常のオブジェクト操作と同じ動作を返す「安全な内部API」
- ✅ Proxyハンドラー内で
Reflect
を使うことで、元の操作に委譲できる - ✅
return Reflect.set(...)
などで、トラップ内でも“自然な挙動”を保てる
実務ユースケース①:不正プロパティのアクセスガード
const guarded = new Proxy({}, {
get(_, prop) {
if (prop.startsWith('_')) {
throw new Error(`Access to private property "${prop}" denied`);
}
return Reflect.get(...arguments);
}
});
guarded.name = 'Toto';
guarded._secret; // ❌ Error
実務ユースケース②:自動バリデーション
function createValidatedUser() {
const data = {};
return new Proxy(data, {
set(obj, key, value) {
if (key === 'age' && typeof value !== 'number') {
throw new TypeError('age must be a number');
}
return Reflect.set(obj, key, value);
}
});
}
const user = createValidatedUser();
user.age = 28; // ✅
user.age = 'twenty'; // ❌ TypeError
実務ユースケース③:APIレスポンスの補正
function normalizeAPIResponse(obj) {
return new Proxy(obj, {
get(target, key) {
const val = Reflect.get(target, key);
return val === null || val === undefined ? 'N/A' : val;
}
});
}
const res = normalizeAPIResponse({ name: null, age: 30 });
console.log(res.name); // N/A
console.log(res.age); // 30
Vueのリアクティブ実装の核心もProxy
const state = new Proxy({ count: 0 }, {
get(target, key) {
trackDependency(key);
return Reflect.get(target, key);
},
set(target, key, value) {
const result = Reflect.set(target, key, value);
triggerUpdate(key);
return result;
}
});
→ ✅ get
で依存トラッキング、set
で更新通知を実装
よくあるミスと注意点
❌ Proxy は “透過的ではない”
-
proxy instanceof TargetConstructor
→ false になる可能性 - JSON.stringify(proxy) → 期待通りにならないケースあり
❌ Reflect
を使わず target[prop] = value
などすると副作用が起こりやすい
→ ✅ 常に Reflect
経由で基本操作を委譲すべき
設計判断フロー
① オブジェクトの操作を監視・制御したい? → Proxy
② 元の操作を再現しつつ傍受したい? → Reflect を併用
③ ロギング / バリデーション / ガード処理を構造的に挟みたい? → Proxy一択
④ 構造全体に一律のルールを敷きたい? → Proxyで構造単位を制御
⑤ Vue的なリアクティブ設計をしたい? → get/setトラップで実装可能
結語
Proxy
は、単なる“オブジェクトをラップする構文”ではない。
それは オブジェクトの意味と挙動を再構成できる、構文的ミドルウェアである。
-
Proxy
は傍受者であり、 -
Reflect
は原点への導線である。
構造をそのまま使うだけではなく、操作の意味そのものを再設計する。
それが Proxy
/ Reflect
を使う意味であり、JavaScriptのメタプログラミングの本質である。