ES5 Harmony Proxy-JavaScript内のJavaScriptのセマンティクスを変更します

プロキシは、プログラマが自分の動作を決定する必要がある新しいJavaScriptオブジェクトです。 すべてのオブジェクトの標準動作はJavaScriptエンジンで定義されています。JavaScriptエンジンは、ほとんどの場合C ++で記述されています。 プロキシを使用すると、プログラマーはJavaScriptオブジェクトのほぼすべての動作を決定できます。基本オブジェクトや関数ラッパーを作成したり、仮想オブジェクトの抽象化を作成したり、メタプログラミング用のAPIを提供したりするのに役立ちます。 現在、プロキシは標準の一部ではありませんが、ECMAScript Harmonyで標準化が計画されています。 混乱を避けるため、これらのプロキシはプロキシとは無関係であることを明確にします。

どこで使用できますか?


1.一般的な中間抽象化
2.仮想オブジェクトの作成:既存のオブジェクトのラッパー、リモート(遠くから離れた)オブジェクト、オブジェクトの遅延作成(ORMの例-Ruby ActiveRecord、Groovy GORM)
3.透過的なロギング、トレース、プロファイリング
4.ドメイン固有の言語の紹介
5.存在しないメソッドの動的インターセプト、欠落したメソッドの作成(__noSuchMethod__)
6.特定のイテレーターのベース

コンセプト


1.この言語の機能は、「包括的なメカニズム」(元のキャッチオールメカニズム)と呼ばれます-この名前は、この機能を説明するために使用されます。 テキストは元の名前を使用します。
2.「包括的なメカニズム」の概念の別名-仲介者(orig。Intercession API)
3.プロパティを処理するオブジェクトはハンドラーと呼ばれます-ハンドラー
4.プロパティが置き換えられるオブジェクトはプロキシと呼ばれます-プロキシ
5.プロキシオブジェクトを作成するオブジェクト/メソッドは、プロキシファクトリと呼ばれます-プロキシファクトリ
6.動作を処理するハンドラーに含まれるメソッドは、トラップ/インターセプタートラップと呼ばれます(オペレーティングシステムに類似)
7.プロキシは、エキサイティング/アクティブ(元のトラッピング)または解散(元の固定)です。

それらを使用する方法-API


プロキシファクトリには2つのタイプがあります。 1つはオブジェクト用、もう1つは関数用です。

プロキシコンストラクター:
var proxy = Proxy.create(handler, proto); 

プロキシコンストラクターコンストラクター:
 var proxy = Proxy.createFunction(handler, callTrap, constructTrap); 

proto-プロキシプロトタイプを定義するオプションのパラメーター
callTrap-プロキシ関数が直接呼び出されたときに元の関数を置き換える関数(以下の例ですべてを説明します)。 重要:この代替関数(callTrap)は、代替thisと一致します。
constructTrap-オプションのパラメーター-newを通じて呼び出されたときに関数の元のコンストラクターを置き換える関数。 重要:この置換関数(constructTrap)は常に未定義です。 constructTrapが渡されない場合、callTrapが使用され、proxy.prototypeからこのデリゲートが使用されます(ES5コンストラクターの通常の動作第13.2.2章)
ハンドラ -プロキシの動作を定義するオブジェクト。 このオブジェクトには常にトラップが含まれている必要があります。

ベーストラップ/インターセプター(基本的なトラップ)

読み方: : function(, __) -> { } //
 { getOwnPropertyDescriptor: function(name) -> {PropertyDescriptor | undefined} // Object.getOwnPropertyDescriptor(proxy, name) getPropertyDescriptor: function(name) -> {PropertyDescriptor | undefined} // Object.getPropertyDescriptor(proxy, name) (not in ES5) getOwnPropertyNames: function() -> {String[]} // Object.getOwnPropertyNames(proxy) getPropertyNames: function() -> {String[]} // Object.getPropertyNames(proxy) (not in ES5) defineProperty: function(name, propertyDescriptor) -> {Mixed} // Object.defineProperty(proxy,name,pd) delete: function(name) -> {Boolean} // delete proxy.name fix: function() -> {{String:PropertyDescriptor}[]|undefined} // Object.{freeze|seal|preventExtensions}(proxy) } 


派生トラップ/インターセプター(派生トラップ)

これらのインターセプターはオプションであり、定義されていない場合、デフォルトのロジックが使用されます。
 { has: function(name) -> {Boolean} // if (name in proxy) ... hasOwn: function(name) -> {Boolean} // ({}).hasOwnProperty.call(proxy, name) get: function(receiver, name) -> {Mixed} // receiver.name; set: function(receiver, name, val) -> {Boolean} // receiver.name = val; enumerate: function() -> {String[]} // for (name in proxy)         keys: function() -> {String[]} // Object.keys(proxy)      } 

デフォルトでは、 次のロジックが実行されます
表形式でここに見ることができます

派生トラップは、ベーストラップで定義できるため、「派生」と呼ばれます。 たとえば、getPropertyDescriptorフックを使用してhasフックを定義できます(undefinedを返すかどうかを確認します)。 派生インターセプターを使用すると、より低いコストでプロパティをエミュレートできます。これが、定義された理由です。 プロキシがObject.preventExtensionsObject.sealおよびObject.freezeと対話できるようにするために、修正トラップが導入されました。 非拡張、密閉、または凍結されたオブジェクト(非拡張、密閉、凍結)は、何らかの方法でハンドラーの自由を制限する必要があります。 set, getなどの将来の呼び出しで何を返すか たとえば、以前のhandler.get(p, “foo”)呼び出しが未定義ではないを返した場合、その後のhandler.get(p, "foo")呼び出しは、オブジェクトが凍結されている場合に同じ値を返す必要があります。 プロキシがフリーズ、シール、ブロック(非拡張、シール、またはフリーズ)を試みるたびに、 "fix"フックトラップが呼び出されます。
修正ハンドラーには2つのオプションがあります。
1.要求を拒否し(修正プログラムは未定義を返す必要があります)、TypeError例外がスローされます。
2.クエリを実行し、 {String:PropertyDescriptor}[]の形式でオブジェクトの説明を返します。この場合、「包括的なメカニズム」は、オブジェクトの説明に基づいて新しいオブジェクトを作成します。 この時点で、ハンドラーへのすべての参照が削除されます(ガベージコレクターによって削除できます)。 この場合、プロキシはlicentiousと呼ばれます。



こんにちはプロキシ!

次のコードは、プロパティへのアクセスを中断し、各プロパティ「p」に対して「Hello、p」を返すプロキシを作成します。
 var p = Proxy.create({ get: function(proxy, name) { return 'Hello, '+ name; } }); document.write(p.World); //  'Hello, World' 

生きている例

シンプルなプロファイラー

どのプロパティが取得されたかをカウントする単純なラッパーを作成します。
 function makeSimpleProfiler(target) { var forwarder = new ForwardingHandler(target); var count = Object.create(null); forwarder.get = function(rcvr, name) { count[name] = (count[name] || 0) + 1; return this.target[name]; }; return { proxy: Proxy.create(forwarder, Object.getPrototypeOf(target)), get stats() { return count; } }; } 

makeSimpleProfiler関数は、監視するオブジェクトを引数として受け取ります。 プロキシ自体と統計-呼び出しの数という2つのプロパティを持つオブジェクトを返します。
生きている例

makeSimpleProfiler関数の2行目のForwardingHandler関数は、すべてのプロキシ操作をターゲットに透過的に委任する単純なリダイレクトプロキシを作成します。 これは次のようなものです。
 function ForwardingHandler(obj) { this.target = obj; } ForwardingHandler.prototype = { has: function(name) { return name in this.target; }, get: function(rcvr,name) { return this.target[name]; }, set: function(rcvr,name,val) { this.target[name]=val;return true; }, delete: function(name) { return delete this.target[name]; } enumerate: function() { var props = []; for (name in this.target) { props.push(name); }; return props; }, iterate: function() { var props = this.enumerate(), i = 0; return { next: function() { if (i === props.length) throw StopIteration; return props[i++]; } }; }, keys: function() { return Object.keys(this.target); }, ... }; Proxy.wrap = function(obj) { return Proxy.create(new ForwardingHandler(obj), Object.getPrototypeOf(obj)); } 

この関数の完全なバージョンは、 ここにあります 。 この転送ハンドラは標準の一部になりそうです。

削除されたアイテム

プロキシを使用すると、リモートオブジェクトまたは既存のオブジェクトをエミュレートできる仮想オブジェクトを作成できます。 デモのために、既存のJavaScriptリモート通信ライブラリのラッパーを作成しましょう。 web_send Tyler Closeライブラリを使用して、サーバー上にあるオブジェクトへのリモート接続を作成できます。 これで、このリモート接続を使用してHTTP POST要求でメソッドを呼び出すことができます。 残念ながら、リモート通信はオブジェクトとして使用できません。
比較してください。 リモート関数を呼び出すには、最初に呼び出す必要がありました。
 Q.post(ref, 'foo', [a,b,c]); 

プロキシを使用して、この呼び出しをより自然にし、ラッパーを記述できます。
 function Obj(ref) { return Proxy.create({ get: function(rcvr, name) { return function() { var args = Array.prototype.slice.call(arguments); return Q.post(ref, name, args); }; } }); } 

これでObj(ref).foo(a,b,c)を実行できます。

__NoSuchMethod__エミュレーション

プロキシを使用すると、それをサポートしていないブラウザで__noSuchMethod__フックをエミュレートすることができます(ただし、現在これは関係ありません)。
 function MyObject() {}; MyObject.prototype = Object.create(NoSuchMethodTrap); MyObject.prototype.__noSuchMethod__ = function(methodName, args) { return 'Hello, '+ methodName; }; new MyObject().foo() // returns 'Hello, foo' 

このオブジェクトは、元の__noSuchMethod__をgetフックに置き換えるNoSuchMethodTrapプロキシを使用します。
 var NoSuchMethodTrap = Proxy.create({ get: function(rcvr, name) { if (name === '__noSuchMethod__') { throw new Error("receiver does not implement __noSuchMethod__ hook"); } else { return function() { var args = Array.prototype.slice.call(arguments); return this.__noSuchMethod__(name, args); } } } }); 

生きている例

高次メッセージ

ここで説明されているように、高次メッセージは、引数として他のメッセージを受け取るメッセージです
高次のメッセージは高次の関数に似ていますが、コードではより容量が大きくなります。 プロキシを使用すると、高次のメッセージを簡単に作成できます。 関数内のメッセージをリメイクする特別なオブジェクト「_」を考えてみましょう。
 var msg = _.foo(1,2) msg.selector; // "foo" msg.args; // [1,2] msg(x); // x.foo(1,2) 

msgは、関数function(z) { return z.foo(1,2); }として定義されているかのように、1つの引数を使用する関数function(z) { return z.foo(1,2); } function(z) { return z.foo(1,2); } 次の例は、上記のドキュメントのSVPを直接解釈したものですが、より容量の大きいコードで記述されています。
 var words = "higher order messages are fun and short".split(" "); String.prototype.longerThan = function(i) { return this.length > i; }; //        document.write(words.filter(_.longerThan(4)).map(_.toUpperCase())); //       : // words.filter(function (s) { return s.longerThan(4) }) // .map(function (s) { return s.toUpperCase() }) 

オブジェクト「_」のコードは次のとおりです。
 //     var _ = Proxy.create({ get: function(_, name) { return function() { var args = Array.prototype.slice.call(arguments); var f = function(rcvr) { return rcvr[name].apply(rcvr, args); }; f.selector = name; f.args = args; return f; } } }); 

生きている例

ベースオブジェクトのエミュレーション

プロキシを使用すると、JavascriptプログラマはDOMなどの基本オブジェクトの奇妙な部分をエミュレートできます。 これにより、ライブラリの作成者は、基本オブジェクトをラップして「調整」(サンドボックスに近似)したり、修正してブラウザ間の非互換性を軽減したりできます。

プロキシ機能

前の例ではオブジェクトを使用しました。 簡単なプロキシ関数の例を次に示します。
 var simpleHandler = { get: function(proxy, name) { // can intercept access to the 'prototype' of the function if (name === 'prototype') return Object.prototype; return 'Hello, '+ name; } }; var fproxy = Proxy.createFunction( simpleHandler, function() { return arguments[0]; }, // call trap function() { return arguments[1]; }); // construct trap fproxy(1,2); // 1 new fproxy(1,2); // 2 fproxy.prototype; // Object.prototype fproxy.foo; // 'Hello, foo' 

生きている例

プロキシ関数は、純粋なJavaScriptでは利用できなかった特別なイディオムを記述する機会を提供します。 まず、プロキシ関数は任意のオブジェクトから関数(呼び出し可能なオブジェクト)を作成できます。
 function makeCallable(target, call, construct) { return Proxy.createFunction( new ForwardingHandler(target), call, construct || call); } 

次に、プロキシ関数を使用して、インスタンスが呼び出し可能なエンティティである擬似クラスを作成できます。
 function Thing() { /* initialize state, etc */ return makeCallable(this, function() { /* actions to perform when instance is called like a function */ }); } 

新しいセマンティクスの実験


言語が好きな人向け:プロキシを使用して、JavaScript自体で「標準」オブジェクトとJavaScript関数を作成できます。 JavaScript内のJavaScriptオブジェクトのセマンティクスを変更する機能により、実験のためにセマンティクスにわずかな変更を加えるメカニズムが大幅に簡素化されます。 JavaScript内のセマンティクスの部分的な実装は、 デフォルトのトラップ値で確認できます 。 別の例:プロキシを使用するJavaScript内のJavaScript配列

プロキシのヒント


再帰を避ける

トラップ内の暗黙的なtoString呼び出しは避けてください。 getおよびsetフックのレシーバー引数に注意してください。 レシーバはプロキシへのリンクを表すため、暗黙的にgetおよびsetを呼び出すと、無限再帰が発生します。 たとえば、conster.log(receiver)を呼び出してセッター内でデバッグすると、toStringメソッドが呼び出され、無限再帰が発生します。
 get: function(receiver, name) { print(receiver); return target[name]; } 

pが上記のトラップを使用するプロキシである場合、 p.fooを呼び出すと、無限ループが発生します。まず、 name="foo"getトラップが呼び出され、 receiver (つまりp )が出力されます。 これによりp.toString()が呼び出され、今回はname="toString"で再びトラップが呼び出されます。 などなど。

ハンドラーとしてのプロキシ

プロキシハンドラー自体をプロキシにすることができます。 以下のプロキシトレーサーはこのパターンをドメイン化し、単一のgetフックを介してハンドラーのすべての操作の「トンネリング」を作成するために使用されます。

画像

プロキシトレーサー/プロキシプローブ

トレーサーは、処理したすべての操作の説明を単に出力します。 これは、デバッグやプロキシの動作方法を学習するのに非常に役立ちます。
生きている例

プローブには、トレーサーと同様のロジックがあり、適用されたすべてのメタレベルの操作を記録します。
生きている例

いつ使用できますか?


現在、Firefox 4.0のみがプロキシをサポートしています。 拡張としてNode.jsのプロキシ実装がありますnode-overload (部分的なサポート) node-proxy (ほぼ完全なサポート)。 いずれにせよ、プロキシは標準に含まれるため、ブラウザにもすぐに表示されます!

追加のリソース


1. ECMAScript Harmony
2. Mozilla Developer Networkのドキュメント
3.標準の開発: DLS 2010で示されたこのGoogle Tech Talkこのペーパーの最初の部分。
4. Brendan Eichは彼のブログでProxyの基本を簡単に説明しています。
5. Firefox 4のオープンプロキシの問題の部分的なリスト
6. jsconfの給与からブレンダン・エイチをスライド

記事で使用されるリソース


1. MDCプロキシ(ドラフト)
2. ES5キャッチオールプロキシ
3. プロキシインセプション (ブレンダンアイヒ)
4. チュートリアル:Harmony Proxies (Tom Van Cutsem)

不明な点がある場合は、質問するかスライドをご覧ください。 提案、希望、批判は大歓迎です!

Source: https://habr.com/ru/post/J117915/


All Articles