ES 6プロキシで楽しむこと


執筆Maciej Cieślar ✏️Proxy JavaScriptのES 6版で紹介された最も見落とされた概念の1つです.
確かに、それは日常的には特に役に立ちません、しかし、それはあなたの将来のいくつかの点で便利に来ることになっています.

基礎


The Proxy objectは、プロパティの検索、割り当て、関数呼び出しなどの基本的な操作のためのカスタム動作を定義するために使用されます.
プロキシの最も基本的な例は以下の通りです.
const obj = {
 a: 1,
 b: 2,
};

const proxiedObj = new Proxy(obj, {
 get: (target, propertyName) => {
   // get the value from the "original" object
   const value = target[propertyName];

   if (!value && value !== 0) {
     console.warn('Trying to get non-existing property!');

     return 0;
   }

   // return the incremented value
   return value + 1;
 },
 set: (target, key, value) => {
   // decrement each value before saving
   target[key] = value - 1;

   // return true to indicate successful operation
   return true;
 },
});

proxiedObj.a = 5;

console.log(proxiedObj.a); // -> incremented obj.a (5)
console.log(obj.a); // -> 4

console.log(proxiedObj.c); // -> 0, logs the warning (the c property doesn't exist)
両方のデフォルトの動作を中断しましたget and set 操作はプロキシコンストラクタに提供されるオブジェクトのそれぞれの名前でハンドラを定義することによって.現在get 操作は、プロパティのインクリメントされた値を返しますset ターゲットオブジェクトに保存する前に値を下げます.
プロキシで覚えておくことが重要であることは、一旦プロキシがつくられるならば、それがオブジェクトと対話する唯一の方法であるべきです.

異なる種類のトラップ


多くのトラップ(オブジェクトのデフォルト動作を妨害するハンドラ)がありますget and set , しかし、我々はこの記事でそれらのどれも使用していません.そのことについて、あなたが彼らについてもっと読むことに興味があるならば、ここにありますdocumentation .

楽しい


今、私たちはプロがどのように働くかを知っています.

物体の状態の観察


それがプロキシで操作を妨害するのが非常に簡単である前に、それが述べられているように.オブジェクトの状態を監視するには、割り当て操作があるたびに通知します.
const observe = (object, callback) => {
 return new Proxy(object, {
   set(target, propKey, value) {
     const oldValue = target[propKey];

     target[propKey] = value;

     callback({
       property: propKey,
       newValue: value,
       oldValue,
     });

     return true;
   }
 });
};

const a = observe({ b: 1 }, arg => {
 console.log(arg);
});

a.b = 5; // -> logs from the provided callback: {property: "b", oldValue: 1, newValue: 5}
そして、それは我々がしなければならないすべてです-毎回提供されたコールバックを起動してくださいset ハンドラを起動します.
としてcallback , 変更されたプロパティの名前、古い値、および新しい値の3つのプロパティを持つオブジェクトを提供します.
実行する前にcallback , 割り当てが実際に行われるように、目標オブジェクトに新しい値を割り当てます.返さなければならないtrue 操作が成功したことを示すそうでなければ、TypeError .
はい、どうぞlive example .

setのプロパティの検証


あなたがそれについて考えるならば、プロキシは合法化を実装するのに良い場所です-彼らはしっかりとデータ自体と結合しません.簡単な検証プロキシを実装しましょう.
前の例のように、私たちはset 操作.次の方法でデータ検証を宣言します.
const personWithValidation = withValidation(person, {
 firstName: [validators.string.isString(), validators.string.longerThan(3)],
 lastName: [validators.string.isString(), validators.string.longerThan(7)],
 age: [validators.number.isNumber(), validators.number.greaterThan(0)]
});
これを達成するためにwithValidation 関数:
const withValidation = (object, schema) => {
 return new Proxy(object, {
   set: (target, key, value) => {
     const validators = schema[key];

     if (!validators || !validators.length) {
       target[key] = value;

       return true;
     }

     const shouldSet = validators.every(validator => validator(value));

     if (!shouldSet) {
       // or get some custom error
       return false;
     }

     target[key] = value;
     return true;
   }
 });
};
まず最初に、私たちはvalidators 現在割り当てられているプロパティの指定されたスキーマでは、存在しない場合は検証するものはありません.
確かならvalidators プロパティのために定義されて、我々は彼ら全員が戻ると断言しますtrue 割り当てる前に.バリデータのいずれかを返しますfalse , 全体set 操作が偽を返し、プロキシがエラーをスローさせます.
最後にすべきことはvalidators オブジェクト.
const validators = {
 number: {
   greaterThan: expectedValue => {
     return value => {
       return value > expectedValue;
     };
   },
   isNumber: () => {
     return value => {
       return Number(value) === value;
     };
   }
 },
 string: {
   longerThan: expectedLength => {
     return value => {
       return value.length > expectedLength;
     };
   },
   isString: () => {
     return value => {
       return String(value) === value;
     };
   }
 }
};
The validators objectには、検証すべき型によってグループ化された検証関数が含まれます.呼び出しの各バリデータは必要な引数をとりますvalidators.number.greaterThan(0) , 関数を返します.検証は返された関数で起こります.
バリデーターの中から仮想フィールドのような驚くべき機能やエラーを投げることによって妥当性を拡張することができました.
はい、どうぞlive example .

コードレイジー


最後に-そして、うまくいけば最もおもしろい例のために、すべての操作を怠惰にするプロキシを作成しましょう.
ここでは非常にシンプルなクラスCalculator , いくつかの基本的な算術演算を含んでいます.
class Calculator {
 add(a, b) {
   return a + b;
 }

 subtract(a, b) {
   return a - b;
 }

 multiply(a, b) {
   return a * b;
 }

 divide(a, b) {
   return a / b;
 }
}
通常は、次の行を実行します.
new Calculator().add(1, 5) // -> 6
結果は6である.
このコードはその場で実行されます.私たちが望むのは、コードが実行されるのを待つコードを持っていることですrun メソッド.この方法は、必要がなくなるまでは延期されます.必要がない場合は、全く実行されません.
したがって、次のコードは6の代わりに、Calculator クラスそのもの:
lazyCalculator.add(1, 5) // -> Calculator {}
それは、もう一つの良い特徴を与えます:方法連鎖.
lazyCalculator.add(1, 5).divide(10, 10).run() // -> 1
そのアプローチの問題はdivide , 結果は何の手がかりもないadd それは、それが役に立たないようにします.引数を制御するので、以前に定義された変数を通して結果を利用できるようにすることができます.$ , 例えば.
lazyCalculator.add(5, 10).subtract($, 5).multiply($, 10).run(); // -> 100
$ ここでは定数Symbol . 実行中に、前のメソッドから返された結果と動的に置き換えます.
const $ = Symbol('RESULT_ARGUMENT');
我々が何を実装するかについての公正な理解がある今、それを正しくしましょう.
関数を作成しましょうlazify . この関数は、get 操作.
function lazify(instance) {
 const operations = [];

 const proxy = new Proxy(instance, {
   get(target, propKey) {
     const propertyOrMethod = target[propKey];

     if (!propertyOrMethod) {
       throw new Error('No property found.');
     }

     // is not a function
     if (typeof propertyOrMethod !== 'function') {
       return target[propKey];
     }

     return (...args) => {
       operations.push(internalResult => {
         return propertyOrMethod.apply(
           target,
           [...args].map(arg => (arg === $ ? internalResult : arg))
         );
       });

       return proxy;
     };
   }
 });

 return proxy;
}
インサイドget トラップは、要求されたプロパティが存在するかどうかを確認します.そうでなければ、エラーを投げます.プロパティが関数でない場合は、何もせずに返します.
プロキシはメソッド呼び出しを遮断する方法を持っていません.代わりに、2つの操作として扱うget 操作と関数呼び出し.我々get ハンドラはそれに応じて動作しなければなりません.
プロパティが関数であることを確認すると、ラッパーとして機能する独自の関数を返します.ラッパー関数が実行されると、別の新しい関数が操作配列に追加されます.ラッパー関数はプロキシをチェーンメソッドにできるように返さなければなりません.
操作配列に提供される関数の内部で、ラッパーに与えられた引数を持つメソッドを実行します.この関数は結果引数でコールされます.$ を返します.
このようにしてリクエストが実行されるまで遅延します.
操作を格納する基本的なメカニズムを構築したので、関数を実行する方法を追加する必要があります.run() メソッド.
これはかなり簡単です.私たちがしなければならないのは、要求されたプロパティ名が等しいかどうかをチェックすることです.この関数を実行すると、ラッパー関数が返されます.ラッパーの内部では、操作配列からすべての関数を実行します.
最終的なコードは次のようになります.
const executeOperations = (operations, args) => {
 return operations.reduce((args, method) => {
   return [method(...args)];
 }, args);
};

const $ = Symbol('RESULT_ARGUMENT');

function lazify(instance) {
 const operations = [];

 const proxy = new Proxy(instance, {
   get(target, propKey) {
     const propertyOrMethod = target[propKey];

     if (propKey === 'run') {
       return (...args) => {
         return executeOperations(operations, args)[0];
       };
     }

     if (!propertyOrMethod) {
       throw new Error('No property found.');
     }

     // is not a function
     if (typeof propertyOrMethod !== 'function') {
       return target[propKey];
     }

     return (...args) => {
       operations.push(internalResult => {
         return propertyOrMethod.apply(
           target,
           [...args].map(arg => (arg === $ ? internalResult : arg))
         );
       });

       return proxy;
     };
   }
 });

 return proxy;
}
The executeOperations 関数は関数の配列を取り、それらを一つずつ実行し、前のものの結果を次のものの呼び出しに渡す.
最後に例を示します.
const lazyCalculator = lazify(new Calculator());

const a = lazyCalculator
 .add(5, 10)
 .subtract($, 5)
 .multiply($, 10);

console.log(a.run()); // -> 100
あなたは私にいくつかのより多くの機能を追加している機能を追加するに興味がある場合lazify 関数-非同期実行、カスタムメソッド名、および.chain() メソッド.両方のバージョンlazify 関数はlive example .

概要


あなたがプロキシを動作しているのを見た今、私はあなたがあなた自身のコードベースでそれらのために良い使用を見つけることができることを望みます.
プロキシは、負のインデックスを実装し、オブジェクト内のすべての存在しないプロパティをキャッチするなど、ここで覆われているものよりも多くの興味深い用途を持っています.しかし注意してください:プロキシは悪い選択ですperformance 重要な要素です.
このポストで何か悪いことを見ている?あなたは正しいバージョンを見つけることができますhere .

プラグイン:ログオン、WebアプリのDVR





LogRocket あなたが自分のブラウザで起こったかのように問題を再生することができるフロントエンドのログツールです.代わりに、エラーが発生したり、スクリーンショットやログのダンプのユーザーを求めるのを推測するのではなく、LogRocketすぐに何が間違って理解するためにセッションをリプレイすることができます.これは、フレームワークに関係なく、任意のアプリケーションを完全に動作し、RedUx、Vuex、および@ NGRX/ストアからの追加のコンテキストを記録するプラグインがあります.

ログのReduxのアクションと状態に加えて、ログログオンレコードコンソールログ、JavaScriptのエラー、StackTrart、ヘッダー/本文、ブラウザのメタデータ、およびカスタムログを使用してネットワークのリクエスト/応答.また、DOMは、最も複雑な単一ページのアプリのピクセル完璧なビデオを再現、ページ上のHTMLとCSSを記録するために楽器を計る.

Try it for free .
郵便Having fun with ES6 proxies 最初に現れたLogRocket Blog .