JavaScript | ES6+ 文法:その他の ES6+ 機能 – Proxy

JavaScript JavaScript
スポンサーリンク
スポンサーリンク

Proxy とは何か(まずイメージから)

Proxy は、
「あるオブジェクトの前に立って、そのオブジェクトへの操作を横取り・監視・カスタマイズできる“番人”」
のような仕組みです。

普通はこうです。

const user = { name: "Alice" };
user.name = "Bob";          // 書き込み
console.log(user.name);     // 読み取り
delete user.name;           // 削除
JavaScript

Proxy を使うと、この「読み取り」「書き込み」「削除」などのタイミングに
自分の処理を差し込めます。

ここが重要です。
Proxy は「オブジェクトに対する操作にフック(hook)をかける仕組み」 です。
ログをとる、値を検証する、自動補完する、などを中心でまとめて行うことができます。


基本構文:new Proxy(target, handler) の 2 つの役

target(ターゲット)とは

target は「本物のオブジェクト」です。

const user = { name: "Alice", age: 20 };
JavaScript

この user に対して Proxy を作るとき、target は user になります。
実際にデータを持っているのは target 側です。

handler(ハンドラ)とは

handler は、
「どの操作を、どう横取りするか」を定義するオブジェクトです。

const handler = {
  get(target, prop, receiver) {
    // プロパティを「読む」ときに呼ばれる
  },
  set(target, prop, value, receiver) {
    // プロパティを「書く」ときに呼ばれる
  },
};
JavaScript

この handler の中に、
get, set, deleteProperty, has, ownKeys …など
いろいろな「罠(trap)」メソッドを書くことで、
target への操作をカスタマイズできます。

Proxy を作って使ってみる

最小の例を見てみます。

const user = { name: "Alice", age: 20 };

const handler = {
  get(target, prop, receiver) {
    console.log(`get: ${String(prop)}`);
    return target[prop];  // 本物にアクセス
  },
};

const proxy = new Proxy(user, handler);

console.log(proxy.name);   // get: name → "Alice"
console.log(proxy.age);    // get: age  → 20
JavaScript

proxy.name と書いていますが、
実際には:

  1. handler.get が呼ばれる
  2. ログを出す
  3. target[prop] で本物 user から値を取って返す

という流れで動いています。

ここが重要です。
Proxy は「target と同じ見た目で振る舞うが、操作の通り道に自分の処理を挟める“代理人”」
だとイメージしてください。


よく使う trap:get / set の基本と活用

get trap(プロパティ取得の横取り)

obj.prop や obj["prop"] で取得するときに呼ばれます。

const user = { name: "Alice", age: 20 };

const handler = {
  get(target, prop, receiver) {
    console.log(`プロパティ ${String(prop)} が読み取られました`);
    return target[prop];
  },
};

const proxy = new Proxy(user, handler);

console.log(proxy.name);
// ログ: プロパティ name が読み取られました
// 出力: Alice
JavaScript

set trap(プロパティ代入の横取り)

obj.prop = value のような代入のときに呼ばれます。

const user = { name: "Alice", age: 20 };

const handler = {
  set(target, prop, value, receiver) {
    console.log(`プロパティ ${String(prop)} に ${value} が代入されました`);
    target[prop] = value;
    return true; // 成功したことを示す(ほぼお約束)
  },
};

const proxy = new Proxy(user, handler);

proxy.age = 25;
// ログ: プロパティ age に 25 が代入されました

console.log(user.age); // 25(target 側もちゃんと更新されている)
JavaScript

例:存在しないプロパティを読むときの「優しいデフォルト」

ユーザー設定などで「存在しない設定を読んだらデフォルトを返したい」ケースがあります。

const settings = { theme: "dark" };

const handler = {
  get(target, prop, receiver) {
    if (prop in target) {
      return target[prop];
    }
    return "(デフォルト値)";
  },
};

const proxy = new Proxy(settings, handler);

console.log(proxy.theme);   // "dark"
console.log(proxy.font);    // "(デフォルト値)"
JavaScript

本来なら settings.font は undefined になりますが、
Proxy の get で「なかったときの値」を差し込めています。

例:書き込み時に型チェック(バリデーション)

年齢は数値で、かつ 0 以上でなければダメ、という制約を入れてみます。

const user = { name: "Alice", age: 20 };

const handler = {
  set(target, prop, value, receiver) {
    if (prop === "age") {
      if (typeof value !== "number" || value < 0) {
        throw new Error("age は 0 以上の数値でなければなりません");
      }
    }
    target[prop] = value;
    return true;
  },
};

const proxy = new Proxy(user, handler);

proxy.age = 30;      // OK
proxy.age = -5;      // エラー
proxy.age = "若い"; // エラー
JavaScript

ここが重要です。
Proxy を使うと、「オブジェクトへのアクセスや変更に対して、一括でルールを差し込める」。
いちいちセッター関数を通さなくても、普通のプロパティアクセスの形で利用者は書けます。


もう少し踏み込んだ trap:delete / has / ownKeys など

deleteProperty trap(削除の横取り)

delete obj.prop のときに呼ばれます。

const user = { name: "Alice", secret: "秘密" };

const handler = {
  deleteProperty(target, prop) {
    if (prop === "secret") {
      console.log("secret は削除させません");
      return false; // false を返すと削除失敗(strict モードだとエラー)
    }
    delete target[prop];
    return true;
  },
};

const proxy = new Proxy(user, handler);

delete proxy.name;    // 削除される
delete proxy.secret;  // secret は削除させません
JavaScript

has trap(in 演算子の横取り)

prop in obj で呼ばれます。

const user = { name: "Alice", password: "1234" };

const handler = {
  has(target, prop) {
    if (prop === "password") {
      return false;  // 存在していても「ないことにする」
    }
    return prop in target;
  },
};

const proxy = new Proxy(user, handler);

console.log("name" in proxy);      // true
console.log("password" in proxy);  // false(存在しているのに隠している)
JavaScript

ownKeys trap(Object.keys などの横取り)

オブジェクトのキー一覧を取得するときに呼ばれます。

const user = { name: "Alice", password: "1234" };

const handler = {
  ownKeys(target) {
    // password を一覧に出さない
    return Object.keys(target).filter((key) => key !== "password");
  },
};

const proxy = new Proxy(user, handler);

console.log(Object.keys(proxy)); // ["name"]
JavaScript

ここが重要です。
delete / has / ownKeys を組み合わせると、「外から見えるプロパティ」を自在にコントロールできる。
公開したくない情報を隠す、内部用のデータを外に見せない、といった使い方ができます。


配列や関数も Proxy できる

配列の操作を監視する

配列に対する push やインデックスアクセスも、そのまま Proxy の対象です。

const numbers = [];

const handler = {
  set(target, prop, value, receiver) {
    console.log(`index ${prop} に ${value} をセット`);
    target[prop] = value;
    return true;
  },
};

const proxy = new Proxy(numbers, handler);

proxy.push(1); // index 0 に 1 をセット
proxy.push(2); // index 1 に 2 をセット
JavaScript

push が内部的に target[target.length] = value のような書き込みを行うため、
set trap が発火します。

関数に対する Proxy(apply / construct)

関数呼び出しを横取りすることもできます。

function sum(a, b) {
  return a + b;
}

const handler = {
  apply(target, thisArg, args) {
    console.log("呼び出しログ:", args);
    const result = target(...args);
    console.log("結果:", result);
    return result;
  },
};

const proxy = new Proxy(sum, handler);

proxy(1, 2); // 呼び出しログ: [1, 2]
             // 結果: 3
JavaScript

apply trap は、proxy(…) のように関数として呼び出されたときに発火します。


実務での使いどころ(どこから触ると良いか)

実務でよくある用途のイメージ

初心者が実務で Proxy に出会うのは、たとえばこんな場面です。

設定オブジェクトや状態オブジェクトに対して、
「不正な値や存在しないキーへのアクセス」を検知したいとき

デバッグ用に、特定のオブジェクトへのアクセスを全部ログに出したいとき

内部的に「Vue/Pinia/ MobX」などのリアクティブシステム(状態管理)で、
Proxy が使われているのを見るとき

特に Vue 3 などでは、Proxy を使って
「プロパティが変化したことを検知 → 画面を再描画」
という仕組みを作っています。

初学者として「どこまで理解しておくといいか」

正直に言うと、
Proxy は ES6 の中でも、わりと“応用寄りの機能” です。

初心者の段階では、次のポイントが押さえられていれば十分です。

Proxy の構文:new Proxy(target, handler)

主な trap:get と set が「読み取り」「書き込み」を横取りする

使いどころ:
ログを取る、バリデーションする、ないプロパティにデフォルトを返す、
といった「共通ルール」を一箇所に集約できる

「Vue などのフレームワークは、裏で Proxy を使っていることが多い」
というざっくりした理解

そのうえで、実際に小さな Proxy を一つ書いてみるのが一番です。

例えば:

あるオブジェクトに対して
・読み取られたプロパティ名をログに出す
・書き込まれた値の型をチェックする

という handler を自作して動かしてみるだけで、
Proxy のイメージはかなりクリアになります。


まとめ

Proxy の本質は、
「オブジェクトへの操作(読み取り・書き込み・削除・関数呼び出しなど)を、丸ごと横取りしてカスタマイズできる仕組み」
であることです。

押さえておきたいポイントは次の通りです。

new Proxy(target, handler) で作る。target は本体、handler は「横取りのルール」

get trap で読み取りを、set trap で書き込みを横取りできる

deleteProperty, has, ownKeys, apply などを使えば、削除や in 演算子、キー列挙、関数呼び出しもコントロールできる

Proxy は「オブジェクトの前に立つ番人」のイメージ。
“存在しないプロパティのデフォルト値”“不正な値の拒否”“アクセスログ”などを一括管理できる

配列や関数も Proxy できる。Vue などのライブラリの内部では、状態の監視・リアクティブ化に使われていることが多い

まずは、get と set だけ使ったシンプルな Proxy を一つ書いて、
「proxy 経由の操作が全部 handler に流れてくる感覚」を掴んでみてください。

それが体で分かると、
「Proxy は難しい“黒魔術”」ではなく、
「オブジェクト操作をコントロールするための強力なスイッチ」として見えるようになります。

タイトルとURLをコピーしました