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 を作るとき、targetuser になります。
実際にデータを持っているのは 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.propobj["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.fontundefined になりますが、
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:getset が「読み取り」「書き込み」を横取りする

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

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

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

例えば:

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

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


まとめ

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

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

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

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

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

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

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

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

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

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