proxy-pattern について
JavaScript の Proxy パターンを解説する学習スキルです。オブジェクトのプロパティ操作を捕捉し、検証、ログ記録、Reflect の利用、性能上の注意点を扱います。
主な機能
- 01プロパティの読み書きの捕捉
- 02更新値の検証
- 03アクセスや変更の記録
- 04Reflect による対象操作
- 05性能上のトレードオフの確認
活用シーン
- 01更新時のオブジェクトフィールド検証
- 02デバッグ用の読み書き記録
- 03オブジェクト操作への書式設定や通知の追加
インストール
コマンドをコピーしてターミナルで実行します。
npx skills add https:/ / github.com/ patternsdev/ skills --skill proxy-patternインストール後、Claude Code で/proxy-patternを入力して読み込まれたか確認します。
詳細
プロキシパターン
Proxy オブジェクトを使うと、特定のオブジェクトとのやり取りをより細かく制御できます。プロキシオブジェクトは、値の取得や設定など、オブジェクトを操作するたびに動作を決定できます。
使用する場面
- オブジェクトへのアクセスに、検証、書式設定、通知、デバッグを追加する必要がある場合に使用します
- オブジェクトのプロパティの取得や設定を制御し、割り込んで処理する場合に便利です
使用しない場面
- プロパティにアクセスするたびに発生する Proxy のオーバーヘッドが問題となる、パフォーマンスが重要なホットパス
- 単純な getter/setter メソッドや Object.defineProperty で、より少ない間接処理で同じ検証を実現できる場合
- 対象オブジェクトへのアクセスがほとんどなく、割り込み処理も必要ない場合
手順
getとsetのトラップを定義するハンドラーオブジェクトを使ってProxyを作成します- プロパティへのアクセスや変更をより簡潔に行うため、ハンドラー内で
Reflectオブジェクトを使用します - データの整合性を確保するため、
setトラップに検証ロジックを追加します - オーバーヘッドが発生するため、パフォーマンスが重要なコードパスではプロキシの使用を避けます
詳細
一般に、プロキシとは誰かの代わりを務めるものです。その人と直接話すのではなく、連絡を取ろうとしていた相手を代表するプロキシの人と話します。JavaScriptでも同じことが起こります。対象オブジェクトを直接操作する代わりに、Proxy オブジェクトを操作します。
John Doe を表す person オブジェクトを作成しましょう。
const person = {
name: "John Doe",
age: 42,
nationality: "American",
};
このオブジェクトを直接操作するのではなく、プロキシオブジェクトを介して操作します。JavaScript では、Proxy の新しいインスタンスを作成することで、簡単に新しいプロキシを作成できます。
const person = {
name: "John Doe",
age: 42,
nationality: "American",
};
const personProxy = new Proxy(person, {});
Proxy の2番目の引数は、_ハンドラー_を表すオブジェクトです。ハンドラーオブジェクトでは、操作の種類に応じた動作を定義できます。Proxy ハンドラーに追加できるメソッドは多数ありますが、最も一般的なのは get と set の2つです。
get:プロパティにアクセスしようとしたときに呼び出されますset:プロパティを変更しようとしたときに呼び出されます
person オブジェクトを直接操作する代わりに、personProxy を操作します。
personProxy の Proxy にハンドラーを追加しましょう。プロパティを変更しようとして Proxy の set メソッドが呼び出されたとき、プロキシにそのプロパティの変更前と変更後の値を記録させます。プロパティにアクセスしようとして Proxy の get メソッドが呼び出されたときは、そのプロパティのキーと値を含む、よりわかりやすい文を記録させます。
const personProxy = new Proxy(person, {
get: (obj, prop) => {
console.log(`The value of ${prop} is ${obj[prop]}`);
return obj[prop];
},
set: (obj, prop, value) => {
console.log(`Changed ${prop} from ${obj[prop]} to ${value}`);
obj[prop] = value;
return true;
},
});
name プロパティにアクセスすると、Proxy はより自然な文を返しました:The value of name is John Doe。
age プロパティを変更すると、Proxy はこのプロパティの変更前と変更後の値を返しました:Changed age from 42 to 43。
プロキシは検証を追加するのに役立ちます。ユーザーが person の年齢を文字列の値に変更したり、名前を空にしたりできないようにする必要があります。また、ユーザーがオブジェクトに存在しないプロパティにアクセスしようとした場合は、そのことを知らせる必要があります。
const personProxy = new Proxy(person, {
get: (obj, prop) => {
if (!obj[prop]) {
console.log(
`Hmm.. this property doesn't seem to exist on the target object`
);
} else {
console.log(`The value of ${prop} is ${obj[prop]}`);
}
return obj[prop];
},
set: (obj, prop, value) => {
if (prop === "age" && typeof value !== "number") {
console.log(`Sorry, you can only pass numeric values for age.`);
} else if (prop === "name" && value.length < 2) {
console.log(`You need to provide a valid name.`);
} else {
console.log(`Changed ${prop} from ${obj[prop]} to ${value}.`);
obj[prop] = value;
}
return true;
},
});
このプロキシによって、誤った値で person オブジェクトが変更されないようにできるため、データの純粋性を保てます!
Reflect
JavaScriptにはReflectという組み込みオブジェクトがあり、プロキシを扱う際に、ターゲットオブジェクトを簡単に操作できます。
これまでは、ブラケット記法で値を直接取得または設定して、プロキシ内のターゲットオブジェクトのプロパティを変更したりアクセスしたりしていました。代わりに、Reflectオブジェクトを使用できます。Reflectオブジェクトのメソッド名は、handlerオブジェクトのメソッド名と同じです。
obj[prop]でプロパティにアクセスしたり、obj[prop] = valueでプロパティを設定したりする代わりに、Reflect.get()とReflect.set()を使ってターゲットオブジェクトのプロパティにアクセスしたり変更したりできます。これらのメソッドには、ハンドラーオブジェクトのメソッドと同じ引数を渡します。
const personProxy = new Proxy(person, {
get: (obj, prop) => {
console.log(`The value of ${prop} is ${Reflect.get(obj, prop)}`);
return Reflect.get(obj, prop);
},
set: (obj, prop, value) => {
console.log(`Changed ${prop} from ${obj[prop]} to ${value}`);
return Reflect.set(obj, prop, value);
},
});
Reflectオブジェクトを使うと、ターゲットオブジェクトのプロパティに簡単にアクセスしたり、変更したりできます。
トレードオフ
プロキシは、オブジェクトの動作を制御する強力な手段です。プロキシにはさまざまな用途があり、検証、書式設定、通知、デバッグなどに役立ちます。
Proxyオブジェクトを使いすぎたり、handlerの各メソッド呼び出しで負荷の高い処理を実行したりすると、アプリケーションのパフォーマンスに悪影響を与える可能性があります。パフォーマンスが重要なコードでは、プロキシを使用しないのが最善です。
ソース
参考資料
- Proxy - MDN
- JavaScript Proxy - David Walsh
- Awesome ES2015 Proxy - GitHub @mikaelbr
- Thoughts on ES6 Proxies Performance - Valeri Karpov