2
3

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の本質を理解する:オブジェクトの振る舞いを動的に設計するJavaScriptの構文戦略

Posted at

概要

JavaScriptの Proxy は、「あるオブジェクトに対するすべての操作」を横から捕捉し、任意の振る舞いに書き換えることができる。

つまり、ただのデータ構造だったオブジェクトが、知能を持った“反応型オブジェクト”に進化する。

Vue3のリアクティブ設計、型チェックの強化、アクセスログの記録、APIラッパーなど、あらゆる“動的挙動”の裏に潜むのが Proxy だ。

本記事では、構文から設計まで、Proxyを使うべき場面・使わないべき場面を含めて構造的に理解する。


対象環境

ES6以降(Node.js / モダンブラウザ)

Proxyの基本構文

const proxy = new Proxy(target, handler);
  • target: 操作対象となるオブジェクト
  • handler: どのように振る舞いを上書きするかの定義

最小構成例:ログを出力するProxy

const target = { name: 'toto' };

const handler = {
  get(obj, prop) {
    console.log(`GET ${prop}`);
    return obj[prop];
  }
};

const proxy = new Proxy(target, handler);

console.log(proxy.name); // ログ + 値出力

サポートされるTrap一覧

Trap名 捕捉対象
get プロパティの取得
set プロパティの代入
has in 演算子
deleteProperty delete 演算子
apply 関数の呼び出し
construct new によるコンストラクタ呼び出し
ownKeys Object.keys() など
getOwnPropertyDescriptor Object.getOwnPropertyDescriptor()

ユースケース①:動的なバリデーション

const user = {
  age: 0
};

const validator = {
  set(obj, prop, value) {
    if (prop === 'age' && typeof value !== 'number') {
      throw new TypeError('age must be a number');
    }
    obj[prop] = value;
    return true;
  }
};

const proxyUser = new Proxy(user, validator);
proxyUser.age = 30;      // ✅ OK
proxyUser.age = 'young'; // ❌ TypeError

→ ✅ 型安全な設計が可能に


ユースケース②:存在しないプロパティのハンドリング

const fallback = new Proxy({}, {
  get: (obj, prop) => prop in obj ? obj[prop] : `未定義:${prop}`
});

console.log(fallback.language); // '未定義:language'

→ ✅ 安全に “未定義アクセス” を検出・補完可能


ユースケース③:自動バインディング(Vueのようなリアクティブ設計)

function reactive(obj) {
  return new Proxy(obj, {
    get(target, prop) {
      console.log(`依存追跡: ${prop}`);
      return target[prop];
    },
    set(target, prop, value) {
      console.log(`更新検知: ${prop} = ${value}`);
      target[prop] = value;
      return true;
    }
  });
}

const state = reactive({ count: 0 });
state.count++; // get → set → 更新検知ログ

→ ✅ Vue 3の reactive() はまさにこの形


ユースケース④:関数呼び出しの拡張(apply)

function greet(name) {
  return `Hello, ${name}`;
}

const logged = new Proxy(greet, {
  apply(target, thisArg, args) {
    console.log(`Call: ${args[0]}`);
    return target.apply(thisArg, args);
  }
});

logged('toto'); // ログ + 呼び出し

→ ✅ 関数に“副作用”を追加したいときに便利


ユースケース⑤:構築を制御(construct)

class Person {
  constructor(name) {
    this.name = name;
  }
}

const Protected = new Proxy(Person, {
  construct(target, args) {
    if (!args[0]) throw new Error('name is required');
    return new target(...args);
  }
});

new Protected('toto'); // OK
new Protected();       // ❌ Error

→ ✅ コンストラクタの検査・注入処理に使える


注意点・落とし穴

❌ Proxyを多用するとパフォーマンスが低下する可能性

  • リアクティブ構造を大規模に作る場合は Proxy の使いすぎに注意

❌ Proxyはオブジェクトの“見た目”を変えない

typeof proxy; // 'object'
Array.isArray(proxy); // ❌ false(元が配列でも)

→ ✅ 必要に応じて Reflect を使って正確に模倣する


Reflectと組み合わせて安全に

const safeProxy = new Proxy(obj, {
  get: (target, prop) => Reflect.get(target, prop),
  set: (target, prop, value) => Reflect.set(target, prop, value)
});

→ ✅ Proxyの中で本来の操作を正しく行うために Reflect を活用


結語

Proxy は単なる構文ではない。
それは「JavaScriptのあらゆる振る舞いをオーバーライドできるインターセプター」であり、
設計者に “オブジェクトの真の振る舞い” を定義する力を与える。

  • バリデーションの自動化
  • 隠蔽的デザイン
  • リアクティブ構造
  • メタ的ロジックの挿入

これらをコードの中に安全に、かつ動的に埋め込むための道具が Proxy である。

設計とは「コードを書く」ことではない。
「コードがどう振る舞うか」を定義することである。

2
3
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
3

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?