2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

ProxyとReflectの設計戦略:JavaScriptオブジェクトの操作を傍受・制御するメタプログラミングの核心

Posted at

概要

JavaScriptの ProxyReflect は、オブジェクトの操作を透明かつ動的に監視・操作・再定義できる構文的インターフェースである。
これにより、以下のような構造制御が可能になる:

  • アクセスログの記録 / 非公開プロパティのガード
  • 動的バリデーション / エラーハンドリング
  • 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のメタプログラミングの本質である。

2
2
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
2

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?