何年にもわたって、JavaScript は強力で適応性の高いプログラミング言語に成長し、変化する開発者のニーズに対応するために絶えず進化しています。
その比較的最近の進歩の 1 つが Proxy オブジェクトです。これにより、プログラマーは、他のオブジェクトの主要な操作をインターセプトおよび変更できる強力で柔軟なオブジェクトを作成できるようになります。
この記事では、Proxy in JavaScriptの機能について詳しく説明し、その構文、属性、典型的なアプリケーション、長所、制限、実例、および推奨されるアプローチについて説明します。
プロキシは、別のオブジェクトを包み込み、プロパティへのアクセス、割り当て、削除などの基本的な操作をインターセプトするオブジェクトです。プロキシは、開発者がより用途が広く堅牢なコードを記述できるようにする JavaScript の重要な側面です。
この記事の目的は、構文、特性、利点、欠点、図、および推奨される手法を含めて、JavaScript での Proxy の包括的な理解を提供することです。
JavaScript のプロキシは、他のオブジェクトで実行される基本操作を変更およびカスタマイズできるオブジェクトの作成を可能にする機能です。
Proxy オブジェクトを確立するには、ターゲット オブジェクトとハンドラー オブジェクトの 2 つのコンポーネントが必要です。ターゲット オブジェクトは、操作がインターセプトされるオブジェクトであり、ハンドラー オブジェクトは、これらの操作をキャッチするために使用されるトラップまたはメソッドを保持する役割を果たします。
基本的な Proxy オブジェクトを作成する方法を示す例を次に示します。
const target = { name: 'John', age: 25, }; const handler = { get: function(target, prop) { console.log(`Getting property ${prop}`); return target[prop]; }, }; const proxy = new Proxy(target, handler); console.log(proxy.name); // Getting property name // John
この例では、名前と年齢という 2 つの特性を持つターゲット オブジェクトを生成します。また、ターゲット オブジェクトのプロパティを読み取ろうとする試みをキャプチャするための get トラップを持つハンドラ オブジェクトも生成します。その後、Proxy コンストラクターにターゲット オブジェクトとハンドラー オブジェクトを提供して、Proxy オブジェクトを生成します。最後に、Proxy オブジェクトの name プロパティを取得します。これにより、get トラップが呼び出され、メッセージがコンソールに出力されます。
トラップは、ターゲット オブジェクトに対する操作をインターセプトするメソッドです。 get、set、has、deleteProperty など、Proxy オブジェクトで使用できるいくつかのトラップがあります。
最も一般的に使用されるいくつかのトラップの簡単な概要を次に示します。
get : このトラップは、ターゲット オブジェクトのプロパティを読み取ろうとする試みをインターセプトします。ターゲット オブジェクトとアクセスされるプロパティの 2 つの引数を取ります。トラップはプロパティの値を返します。
set : このトラップは、ターゲット オブジェクトでプロパティを確立しようとするすべての作業をキャプチャします。ターゲット オブジェクト自体、確立されるプロパティ、およびそのプロパティの更新された値の 3 つのパラメーターが必要です。このメカニズムには、確立されている値を変更する機能があり、エラーを生成して値の確立を禁止することもできます。
has : このトラップは、ターゲット オブジェクトにプロパティが存在するかどうかを確認する試みをインターセプトします。ターゲット オブジェクトとチェック対象のプロパティの 2 つの引数を取ります。トラップは、プロパティが存在するかどうかを示すブール値を返します。
deleteProperty : このトラップは、ターゲット オブジェクトからプロパティを削除しようとする試みをインターセプトします。ターゲット オブジェクトと削除されるプロパティの 2 つの引数を取ります。トラップは、プロパティを削除するか、エラーをスローして、プロパティが削除されないようにすることができます。
プロキシ オブジェクトには、それらを無効化できる魅力的な特性があり、その結果、そのトラップがターゲット オブジェクトの操作をインターセプトしなくなります。無効化できる Proxy オブジェクトを作成するには、 Proxy.revocable()
関数を使用します。
次に例を示します。
const target = { name: 'John', age: 25, }; const handler = { get: function(target, prop) { console.log(`Getting property ${prop}`); return target[prop]; }, }; const {proxy, revoke} = Proxy.revocable(target, handler); console.log(proxy.name); // Getting property name // John revoke(); console.log(proxy.name); // Uncaught TypeError: Cannot perform 'get' on a proxy that has been revoked
この例では、 Proxy.revocable()
メソッドを使用して取り消し可能な Proxy オブジェクトを作成します。次に、Proxy オブジェクトの name プロパティにアクセスします。これにより、get トラップがトリガーされ、メッセージがコンソールに記録されます。次に、 revoke()
メソッドを使用して Proxy オブジェクトを取り消します。これは、Proxy オブジェクトのプロパティにそれ以上アクセスしようとしても失敗することを意味します。
Proxy オブジェクトのもう 1 つの興味深い機能は、JavaScript で継承パターンを実装するために使用できることです。 Proxy オブジェクトを別のオブジェクトのプロトタイプとして使用することで、プロパティ ルックアップをインターセプトし、プロトタイプ チェーンの動作をカスタマイズできます。
次に例を示します。
const parent = { name: 'John', }; const handler = { get: function(target, prop) { console.log(`Getting property ${prop}`); if (!(prop in target)) { return Reflect.get(parent, prop); } return target[prop]; }, }; const child = new Proxy({}, handler); console.log(child.name); // Getting property name // John child.name = 'Bob'; console.log(child.name); // Getting property name // Bob console.log(parent.name); // John
この例では、親オブジェクトが定義されており、name 属性があります。次に、子オブジェクトのプロパティに対する読み取り要求を防ぐ get トラップを使用してハンドラー オブジェクトを作成します。プロパティが子オブジェクトに存在しない場合、トラップは Reflect.get() メソッドを使用して親オブジェクトにフォールバックします。
次に、Proxy オブジェクトをプロトタイプとして、ハンドラー オブジェクトをハンドラーとして使用して、子オブジェクトを作成します。最後に、子オブジェクトの name プロパティにアクセスして変更します。これにより、get および set トラップがオフになり、メッセージがコンソールに記録されます。
Proxy の使用例の 1 つは、高価な関数呼び出しをキャッシュすることです。この例では、引数に基づいて関数呼び出しの結果をキャッシュする Proxy オブジェクトを作成します。
function calculateCost(price, taxRate) { console.log('Calculating cost...'); return price * (1 + taxRate); } const cache = new Map(); const proxy = new Proxy(calculateCost, { apply(target, thisArg, args) { const key = args.join('-'); if (cache.has(key)) { console.log('Returning cached result...'); return cache.get(key); } else { const result = Reflect.apply(target, thisArg, args); cache.set(key, result); return result; } }, }); console.log(proxy(10, 0.2)); // Calculating cost... 12 console.log(proxy(10, 0.2)); // Returning cached result... 12 console.log(proxy(20, 0.2)); // Calculating cost... 24 console.log(proxy(20, 0.3)); // Calculating cost... 26 console.log(proxy(20, 0.3)); // Returning cached result... 26
この例では、価格と税率を受け取り、税込みのコストを返すcalculateCost
という関数を定義します。次に、 Map
クラスを使用してキャッシュ オブジェクトを作成します。
次に、 apply
トラップを使用して関数呼び出しをインターセプトするproxy
という名前の Proxy オブジェクトを作成します。 apply
トラップは、関数が呼び出されるたびに呼び出され、関数の引数を配列として受け取ります。引数を使用してキャッシュ キーを生成し、結果が既にキャッシュにあるかどうかを確認します。そうであれば、キャッシュされた結果を返します。それ以外の場合は、結果を計算してキャッシュに保存します。
最後に、異なる引数を使用してproxy
関数を呼び出し、同じ引数を使用した後続の呼び出しのために結果がキャッシュに格納されることを確認します。
Proxy のもう 1 つのユース ケースは、オブジェクト プロパティの検証です。この例では、文字列プロパティの長さを検証する Proxy オブジェクトを作成します。
const user = { name: 'John', password: 'secret', }; const proxy = new Proxy(user, { set(target, prop, value) { if (prop === 'password' && value.length < 8) { throw new Error('Password must be at least 8 characters long'); } target[prop] = value; return true; }, }); console.log(proxy.name); // John console.log(proxy.password); // secret proxy.password = '12345678'; console.log(proxy.password); // 12345678 proxy.password = '123'; // Error
この例では、 name
とpassword
プロパティを持つuser
というオブジェクトを定義します。次に、 set
トラップを使用してプロパティの割り当てをインターセプトするproxy
と呼ばれる Proxy オブジェクトを作成します。 set
トラップは、プロパティが割り当てられるたびに呼び出され、プロパティ名、新しい値、およびターゲット オブジェクトを受け取ります。
set
トラップを使用して、割り当てられているプロパティがpassword
プロパティであるかどうか、および値が 8 文字未満であるかどうかを確認します。そうである場合、エラーをスローします。それ以外の場合は、ターゲット オブジェクトにプロパティ値を設定します。
proxy
オブジェクトを使用してさまざまな値をpassword
プロパティに割り当てます。長さが 8 文字未満の値はエラーをトリガーすることに注意してください。
Proxy のもう 1 つの一般的な使用例は、オブジェクト プロパティのアクセスと割り当てをログに記録することです。この例では、プロパティへのアクセスと割り当てをログに記録する Proxy オブジェクトを作成します。
const user = { name: 'John', email: '[email protected]', }; const proxy = new Proxy(user, { get(target, prop) { console.log(`Getting ${prop} property`); return target[prop]; }, set(target, prop, value) { console.log(`Setting ${prop} property to ${value}`); target[prop] = value; return true; }, }); console.log(proxy.name); // Getting name property -> John proxy.email = '[email protected]'; // Setting email property to [email protected] console.log(proxy.email); // Getting email property -> [email protected]
この例では、 name
とemail
プロパティを持つuser
というオブジェクトを定義します。次に、 get
トラップとset
トラップを使用してプロパティ アクセスと割り当てをインターセプトするproxy
という Proxy オブジェクトを作成します。
get
トラップは、プロパティがアクセスされるたびに呼び出され、プロパティ名とターゲット オブジェクトを受け取ります。この例では、プロパティがアクセスされていることを示すメッセージをコンソールに記録し、ターゲット オブジェクトからプロパティ値を返します。
set
トラップは、プロパティが割り当てられるたびに呼び出され、プロパティ名、新しい値、およびターゲット オブジェクトを受け取ります。この例では、プロパティが割り当てられていることを示すメッセージをコンソールに記録してから、ターゲット オブジェクトにプロパティ値を設定します。
最後に、 proxy
オブジェクトを使用してさまざまなプロパティにアクセスして割り当て、メッセージがコンソールに記録されることを確認します。
カスタマイズ可能な動作: プロキシ オブジェクトを使用すると、他のオブジェクトの基本的な操作をインターセプトしてカスタマイズできるため、アクセス制御、キャッシュ、ログなどの高度な機能を作成できます。
継承: プロキシ オブジェクトは、JavaScript で継承パターンを実装する機能を提供します。これにより、より用途が広くスケーラブルなコードを作成できます。
Revocable : プロキシ オブジェクトは、作成後に無効にしたり取り消したりできるため、プロキシ オブジェクトのスコープを制限したり、セキュリティ上の理由で役立ちます。
プロキシは長い間使用されてきましたが、ブラウザのすべてのバージョンがこの機能をサポートしているわけではありません。
さらに、プロキシを頻繁に使用すると、アプリケーションのパフォーマンスに悪影響を与える可能性があります。
プロキシを使用する意味を理解することが重要です。ユーザー入力の重要な検証など、アプリケーションにとって重要な瞬間には信頼されるべきではありません。
制限に注意してください: コードにプロキシを実装する前に、プロキシが課す制限と、それらがアプリケーションの速度とセキュリティにどのように影響するかを確認してください。
プロキシ オブジェクトは、コードのパフォーマンスに影響を与える可能性があるため、絶対に不可欠な場合にのみ使用してください。
慎重にテストする: Proxy オブジェクトを使用する場合は、慎重にテストし、予期しない動作が発生する可能性があることに注意してください。
規範に従う: コードを読みやすく維持しやすいものにするために、Proxy オブジェクトを実装する際に、受け入れられている規則とベスト プラクティスに従います。
この記事では、継承パターンや取り消し可能な Proxy オブジェクトを作成する機能など、Proxy の高度な機能について詳しく説明します。
開発者としての経験レベルに関係なく、JavaScript でプロキシを理解することは、コードをより高いレベルに引き上げるための基本です。
Proxy は、その適応性と効力により、複雑なアプリケーションを簡単に構築したい JavaScript 開発者にとって重要な手段となります。