JavaScriptで関数呼び出しを聞く

多くの人々は、多くのプログラミング言語で実装されているEvent-Dispatcher-Listener'ovのメカニズムを知っています。 イベントではなく、JavaScriptオブジェクトの任意のメソッドであるオブジェクトに対して、同様のメカニズムを作成します。
私はオリジナルのふりをしません、いいえ。 この記事の主な目標は、JavaScriptの興味深いプロトタイピングメカニズム、デコレーターの作成を検討し、実際、多くの場合気分を害し、過小評価されているこの素晴らしい言語のパワーと柔軟性を少なくとも少しだけ引き出してみることです。

UPD1:サマリー:
1. JavaScript デコレータの作成
2. デコレータを使用して関数呼び出しリスナーメカニズムを作成する

UPD2:2009年6月9日から
記事の最後に、 Update:Decorator Mark IIセクションを追加しました。 修正と改善があります(このため記事全体を再描画しませんでした)

それで、私のJavaScriptクックブックの最初の(そしてできれば最後ではない)レシピ。


関数呼び出しリスナー 、詰め物( 多機能 )、独自のジュース( ライブラリを使用せずに、純粋なJavaScriptで )で焼きます

成分


1. JavaScriptプロトタイプの理解
2.匿名JavaScript関数について
3. Function クラスのおおよその知識

心配しないでください、私は成分をより詳細に説明します。

レシピ


深さ数メートルのダイビング


それでは始めましょう。 最初に、 John Resigから小さなヘルパーを借ります(彼の本から)。
Function.prototype.method = function (methodName, f) {
return this .prototype[methodName] = f;
}


* This source code was highlighted with Source Code Highlighter .


このメソッドは何をしますか? 文字通り:現在の関数のプロトタイプに新しいメソッドを簡単に追加できます。
ご存じのように、JavaScriptの関数はクラスおよびクラスコンストラクターでもあります(ヒットしないでください。通常の意味ではJavaScript クラスがありません)。

また、次のことも当てはまります。 クラスコンストラクターは関数(または、科学用語では、 Function クラスのインスタンス)です。
プロトタイプの原則に基づいて、新しいmethod(...)Function クラスのプロトタイプ追加すると、 Function クラスのすべてのインスタンス新しいmethod(...)が追加されmethod(...) (ただし、JavaScript、Neoにはメソッドがありません)。

任意のクラス YourClassNameArrayNumberYourClassName Function クラスのインスタンスです 。 ただの機能。 そこで登場しました: Object.method(...)Array.method(...)YourClassNam.method(...)

関数が使い慣れたクラスであると仮定すると、本質的にすべてのすべてのクラスに 静的メソッドを追加します (JavaScriptは実際に挿入します:-))。

デコレータを作成する


さて、独創性については十分です。 私のコードに移りましょう:
Function.method( "decorate" , function (f) {
var oldMe = this ;
var newMe = f;
newMe.old = oldMe;
return newMe;
})


* This source code was highlighted with Source Code Highlighter .

出来上がり! 先ほど言ったように Function クラス自体は関数、またはFunction クラスのインスタンス (aaaa :-))です
それで、追加したらすぐに:
Function.prototype.method = function (...) {}

* This source code was highlighted with Source Code Highlighter .

すぐにFunction. method (...)を取得しましたFunction. method (...) Function. method (...) (プロトタイプではなく Function クラスの インスタンス

上記のコードを実行すると、新しいFunction.prototype. decorate (...)メソッドが作成されFunction.prototype. decorate (...) Function.prototype. decorate (...) 。 そして再び-メソッドはFunctionプロトタイプに登場し、したがってall-all-all クラスに登場しました。 ただし、ここでは重要ではありませんが、すべての機能にとって重要なのは、 decorate(...)メソッドの存在だけです。

decorate (...)は何ですか?
//
var oldMe = this ;

// , " " .
var newMe = f;
newMe.old = oldMe;
return newMe;


* This source code was highlighted with Source Code Highlighter .

もちろん、このコードは大幅に削減できますが、この方法で-より明確になります。

しかし、それだけではありません。最も興味深いのは先です。 私は私のメソッドを無駄に呼び出したと思いますか? いいえ、無駄ではありません! これはおなじみのデコレータです。

装飾例


簡単なクラスを作成しましょう:
// -,
function MyCoolNumber(initNumber) {
this .value = initNumber;
}


* This source code was highlighted with Source Code Highlighter .

しかし、それは数字ですか? いいえ、もちろんです。 そこに何でも転送できます。
new MyCoolNumber( ' ' ) // " "

* This source code was highlighted with Source Code Highlighter .

しかし、何をすべきか? 私は絶対にコンストラクタを変更したくありません。 解決方法があります:転送されるパラメーターを制限し、それをクラスに適用するデコレーターを作成します。
function strictArgs() { // ,
var types = arguments; //
return function () {
var params = arguments;
if ( params .length != types.length)
throw "! " + types.length + " (), " + params .length;
for ( var i=0, l= params .length; i < l; i++) {
if (!( params [i] instanceof types[i]) && !( params [i].constructor == types[i]))
throw "! #" + (i+1) + " " + types[i].name;
}
arguments.callee.old.apply( this , arguments); // , ""
}
}


* This source code was highlighted with Source Code Highlighter .

テスト対象に戻りましょう。
function MyCoolNumber(initNumber) {
this .value = initNumber;
}
MyCoolNumber = MyCoolNumber.decorate(strictArgs(Number))

new MyCoolNumber(); // ! 1 (), 0
new MyCoolNumber(1, 2, 3); // ! 1 (), 3
new MyCoolNumber( "" ); // ! #1 Number
var x = new MyCoolNumber(6); // OK!
alert(x.value) // 6, , .


* This source code was highlighted with Source Code Highlighter .

このすべての不名誉をより詳しく考えてみましょう。 デコレータが使用される瞬間から始めましょう。
  1. strictArgs 1つの引数strictArgsれます
  2. var types = arguments; // strictArgs(...) types, [Number] - 1 -: Number. Arguments - , ,
  3. strictArgs(...)は、次の新しいstrictArgs(...)返します。
    1. var params = arguments; // , , MyCoolNumber;
    2. 次元と型の一致についてこれら2つの配列を比較し始めます

  4. MyCoolNumberから返されたMyCoolNumber装飾しstrictArgs(...)
    注:元の関数とデコレータと接続は、 arguments.callee.old.apply(this, arguments)介して行われarguments.callee.old.apply(this, arguments)
    • arguments呼び出された関数の引数を記述するための標準オブジェクト
    • arguments.calleeデコレータ関数自体
    • arguments.callee.old何であるか覚えていますか-古いですか? decorator関数をdecorate(...)メソッドに渡すと、この関数に「古い」関数を参照して古い属性が追加されます
    • arguments.callee.old.apply(...)は、Functionクラスの標準メソッドです。 私はそれについて話をしません、私はそれが与えられたスコープと引数で関数を呼び出すと言うことができます
    • arguments.callee.old.apply(this, arguments) -実際には、上記の確認


だから、私は主なポイントに焦点を当てたようです。
ああ、忘れた! デコレータ関数を「スロー」して古い関数を返す方法は? これ以上簡単なことはありません:
Function.method( "recover" , function () {
return this .old || this ;
})


* This source code was highlighted with Source Code Highlighter .

続けましょう!

オブジェクトを見て、メソッドを聞きます


レシピの完成に徐々に近づいています。 もう少しスパイスを入れて、火の箱に入れます... :)
Object.method( 'before' , function (methodName, f){
var method = listenerInit.call( this , methodName);
if (method)
method.listenersBefore.push(f);
})

Object.method( 'after' , function (methodName, f){
var method = listenerInit.call( this , methodName);
if (method)
method.listenersAfter.push(f);
})


* This source code was highlighted with Source Code Highlighter .

ご想像のとおり、すべての最も重要なことは特定のlistenerInit(...)で行われますが、それについては後で行われます。 今のところ、彼女が必要な準備をすべてしていると信じてください。
リスナーを追加する方法は理解できます。 次に、それを「削除」する機能が必要です。
Object.method( 'removeBefore' , function (methodName, f){
var method = listenerInit.call( this , methodName);
if (method) {
var _nl = [];
while (method.listenersBefore.length) {
var _f = method.listenersBefore.shift();
if (_f != f)
_nl.push(_f);
}
method.listenersBefore = _nl;
}
})

Object.method( 'removeAfter' , function (methodName, f){
var method = listenerInit.call( this , methodName);
if (method) {
var _nl = [];
while (method.listenersAfter.length) {
var _f = method.listenersAfter.shift();
if (_f != f)
_nl.push(_f);
}
method.listenersAfter = _nl;
}
})


* This source code was highlighted with Source Code Highlighter .

たぶん、この方法は最適ではないので、私は最初に頭から取り出しました。
最後に、このレシピの最重要点である、 デコレータ回路のイベントリスナー間の接続は、同じlistenerInitです。
function listenerInit(methodName) {

var method = this [methodName];
if ( typeof method != "function" )
return false ;

// , ?
if (!method.listenable) {
this [methodName] = method.decorate( function (){
var decorator = arguments.callee;
decorator.listenable = true ;

var list = decorator.listenersBefore;
for ( var i = 0, l = list.length; i < l; i++) {
if ( typeof list[i] == "function" && list[i].apply( this , arguments) === false )
return ;
}

var ret = decorator.old.apply( this , arguments);
list = decorator.listenersAfter;
for ( var i = 0, l = list.length; i < l; i++)
list[i].apply( this , arguments);

return ret;
});
method = this [methodName];
}

method.listenersBefore = method.listenersBefore instanceof Array ? method.listenersBefore : [];
method.listenersAfter = method.listenersAfter instanceof Array ? method.listenersAfter : [];

return method;
}


* This source code was highlighted with Source Code Highlighter .

この関数は条件付きでブロックに分割できます。

ブロック1:検証-このオブジェクトにこのようなメソッドがある場合、だまされていますか?
var method = this [methodName];
if ( typeof method != "function" )
return false ;


* This source code was highlighted with Source Code Highlighter .

そして最後に、 return method 、つまり listenerInit(...)は、 falseまたは既に「装飾された」メソッドを返します。

ブロック2:適切なデコレーターを作成します(まだ定義されていない場合)。
このデコレータには何がありますか?
  1. listenersBeforeの配列からすべてのリスナーを開始しlistenersBefore 。 それらの少なくとも1つがBoolean false返す場合-実行を停止します
  2. 基本メソッドを呼び出す
  3. listenersAfter配列からすべてのリスナーを実行する
  4. デコレータは、ベースメソッドによって返された値を返します

ブロック3: method.listenersBeforeおよびmethod.listenersAfter初期化。

パン


変更1: listenerInitを見えないように非表示にしlistenerInit 。 これを行うには、JavaScriptクロージャーを使用します。
( function (){
// listenerInit(...) --
// .....
})()


* This source code was highlighted with Source Code Highlighter .

変更2: Objectなどの標準クラスの汚染は非常に悪いため、特定のクラスを変更できます。
YourClass.method( 'before' , function (methodName, f){
var method = listenerInit.call( this , methodName);
if (method)
method.listenersBefore.push(f);
})


* This source code was highlighted with Source Code Highlighter .

まあなど。 彼らが言うように-完璧に制限はありません!

それだけです! ケーキは、オーブンで(つまり、あなたの脳内で)30分間、一緒にくっついて、完了です。 いってらっしゃい!

どうですか


さて、特定の使用例:
//
var Num = function (x) {
this .x = x;
}
//
Num.prototype.x = null ;
Num.prototype.getX = function () {
return this .x;
};
Num.prototype.setX = function (x) {
return this .x = x;
}

//
var t = new Num(6);

// after
t.after( "getX" , function (){
alert( '! X == ' + this .x + '!' );
})

// after
t.after( "getX" , function (){
alert( ' !' );
})

// before,
var f = function (x){
if (x < 0 || x > 10) {
alert( '! [0, 10]' );
return false ;
}
}
t.before( "setX" , f)

// :
t.getX(); // ! X == 6! -> ' ! -> getX(...)
t.setX(100); // ! [0, 10] -> setX(100) -
alert(tx); // 6
t.setX(4); // , setX(4)
alert(tx); // 4
t.removeBefore( "setX" , f) // f(...)
t.setX(100); // , setX(100)
alert(tx); // 100


* This source code was highlighted with Source Code Highlighter .

ところで、上記のコードはすべてクロスブラウザです。これが私の仕事の原則です。

おわりに


私の料理を楽しんでください。 書いて、質問に答えてうれしいです。 そして私を信じてください-JavaScriptは、スクリプトではありますが、本当に強力で、柔軟性があり、非常にクールなプログラミング言語です。

更新:デコレータマークII


UPD: 06/09/2009
この記事が書かれてから時間が経ち、実装にエラーと欠点が見つかりました。 しかし、記事を再描画したくはありません。また、新しい記事を作成したくありません。 さあ、ここで会いましょう:デコレーターマークII!

1. Function.method(...)改善Function.method(...)
// ,
Function.prototype.method = function (methodName, f) {
if ( typeof f != "undefined" )
this .prototype[methodName] = f;
return this .prototype[methodName];
}


* This source code was highlighted with Source Code Highlighter .


2.改善restore(...)れたrestore(...)メソッド
// "" -,
Function.method( "restore" , function (fullRestore){
var ret = this .old || this ;
while (fullRestore && ret.old) {
ret = ret.old;
}
return ret;
})


* This source code was highlighted with Source Code Highlighter .


3. decorateMethod(...)メソッドを追加
//
Function.method( "decorateMethod" , function (methodName, decorator){
var f = this .method(methodName);
if (!f)
return null ;
f.name = methodName;
f = f.decorate(decorator);
return this .method(methodName, f);
})


* This source code was highlighted with Source Code Highlighter .


4.( 重要! )メソッドの修正/変更 decorate(...)
Function.method( "decorate" , function (decorator){
//
var oldFunc = this ;
// ! - .
// , -.
// ,
// -- original: oldFunc () decoratorInstance: f ( )
var f = function (){
return decorator.apply( this , [{original: oldFunc, decoratorInstance: f}].concat([].slice.apply(arguments)))
}
// - - decoratorInstance f
f.old = oldFunc;
// -.
// - .
f.prototype = this .prototype;
f.prototype.constructor = f;
// -. ? .
// . , decorateMethod: .
f.name = oldFunc.name;
//
return f;
})


* This source code was highlighted with Source Code Highlighter .

( decorate(...) ), .
:
// -
function strictArgs() {
var types = arguments;
return function () {
var params = arguments;
//...
return arguments.callee.old.apply( this , arguments);
}
}

* This source code was highlighted with Source Code Highlighter .

:
// -
function strictArgs() {
var types = arguments;
return function (dScope) {
var original = arguments[0].original; // dScope.original
var arguments = Array.prototype.slice.call(arguments, 1);
var params = arguments;
//...
return original.apply( this , arguments);
}
}

* This source code was highlighted with Source Code Highlighter .


, :)

decorate(...)
Function.method( "decorate" , function (decorator){
//
var oldFunc = this ;
// ! - .
// , -.
// ,
// -- original: oldFunc () decoratorInstance: f ( )
var f = function (){
return decorator.apply( this , [{original: oldFunc, decoratorInstance: f}].concat([].slice.apply(arguments)))
}
// - - decoratorInstance f
f.old = oldFunc;
// -.
// - .
f.prototype = this .prototype;
f.prototype.constructor = f;
// -. ? .
// . , decorateMethod: .
f.name = oldFunc.name;
//
return f;
})


* This source code was highlighted with Source Code Highlighter .

( decorate(...) ), .
:
// -
function strictArgs() {
var types = arguments;
return function () {
var params = arguments;
//...
return arguments.callee.old.apply( this , arguments);
}
}

* This source code was highlighted with Source Code Highlighter .

:
// -
function strictArgs() {
var types = arguments;
return function (dScope) {
var original = arguments[0].original; // dScope.original
var arguments = Array.prototype.slice.call(arguments, 1);
var params = arguments;
//...
return original.apply( this , arguments);
}
}

* This source code was highlighted with Source Code Highlighter .


, :)

decorate(...)
Function.method( "decorate" , function (decorator){
//
var oldFunc = this ;
// ! - .
// , -.
// ,
// -- original: oldFunc () decoratorInstance: f ( )
var f = function (){
return decorator.apply( this , [{original: oldFunc, decoratorInstance: f}].concat([].slice.apply(arguments)))
}
// - - decoratorInstance f
f.old = oldFunc;
// -.
// - .
f.prototype = this .prototype;
f.prototype.constructor = f;
// -. ? .
// . , decorateMethod: .
f.name = oldFunc.name;
//
return f;
})


* This source code was highlighted with Source Code Highlighter .


( decorate(...) ), .
:
// -
function strictArgs() {
var types = arguments;
return function () {
var params = arguments;
//...
return arguments.callee.old.apply( this , arguments);
}
}

* This source code was highlighted with Source Code Highlighter .

:
// -
function strictArgs() {
var types = arguments;
return function (dScope) {
var original = arguments[0].original; // dScope.original
var arguments = Array.prototype.slice.call(arguments, 1);
var params = arguments;
//...
return original.apply( this , arguments);
}
}

* This source code was highlighted with Source Code Highlighter .


, :)

decorate(...)
Function.method( "decorate" , function (decorator){
//
var oldFunc = this ;
// ! - .
// , -.
// ,
// -- original: oldFunc () decoratorInstance: f ( )
var f = function (){
return decorator.apply( this , [{original: oldFunc, decoratorInstance: f}].concat([].slice.apply(arguments)))
}
// - - decoratorInstance f
f.old = oldFunc;
// -.
// - .
f.prototype = this .prototype;
f.prototype.constructor = f;
// -. ? .
// . , decorateMethod: .
f.name = oldFunc.name;
//
return f;
})


* This source code was highlighted with Source Code Highlighter .


( decorate(...) ), .
:
// -
function strictArgs() {
var types = arguments;
return function () {
var params = arguments;
//...
return arguments.callee.old.apply( this , arguments);
}
}

* This source code was highlighted with Source Code Highlighter .
:
// -
function strictArgs() {
var types = arguments;
return function (dScope) {
var original = arguments[0].original; // dScope.original
var arguments = Array.prototype.slice.call(arguments, 1);
var params = arguments;
//...
return original.apply( this , arguments);
}
}

* This source code was highlighted with Source Code Highlighter .


, :)

decorate(...)
Function.method( "decorate" , function (decorator){
//
var oldFunc = this ;
// ! - .
// , -.
// ,
// -- original: oldFunc () decoratorInstance: f ( )
var f = function (){
return decorator.apply( this , [{original: oldFunc, decoratorInstance: f}].concat([].slice.apply(arguments)))
}
// - - decoratorInstance f
f.old = oldFunc;
// -.
// - .
f.prototype = this .prototype;
f.prototype.constructor = f;
// -. ? .
// . , decorateMethod: .
f.name = oldFunc.name;
//
return f;
})


* This source code was highlighted with Source Code Highlighter .


( decorate(...) ), .
:
// -
function strictArgs() {
var types = arguments;
return function () {
var params = arguments;
//...
return arguments.callee.old.apply( this , arguments);
}
}

* This source code was highlighted with Source Code Highlighter .

:
// -
function strictArgs() {
var types = arguments;
return function (dScope) {
var original = arguments[0].original; // dScope.original
var arguments = Array.prototype.slice.call(arguments, 1);
var params = arguments;
//...
return original.apply( this , arguments);
}
}

* This source code was highlighted with Source Code Highlighter .


, :)

decorate(...)
Function.method( "decorate" , function (decorator){
//
var oldFunc = this ;
// ! - .
// , -.
// ,
// -- original: oldFunc () decoratorInstance: f ( )
var f = function (){
return decorator.apply( this , [{original: oldFunc, decoratorInstance: f}].concat([].slice.apply(arguments)))
}
// - - decoratorInstance f
f.old = oldFunc;
// -.
// - .
f.prototype = this .prototype;
f.prototype.constructor = f;
// -. ? .
// . , decorateMethod: .
f.name = oldFunc.name;
//
return f;
})


* This source code was highlighted with Source Code Highlighter .


( decorate(...) ), .
:
// -
function strictArgs() {
var types = arguments;
return function () {
var params = arguments;
//...
return arguments.callee.old.apply( this , arguments);
}
}

* This source code was highlighted with Source Code Highlighter .

:
// -
function strictArgs() {
var types = arguments;
return function (dScope) {
var original = arguments[0].original; // dScope.original
var arguments = Array.prototype.slice.call(arguments, 1);
var params = arguments;
//...
return original.apply( this , arguments);
}
}

* This source code was highlighted with Source Code Highlighter .

, :)

decorate(...)
Function.method( "decorate" , function (decorator){
//
var oldFunc = this ;
// ! - .
// , -.
// ,
// -- original: oldFunc () decoratorInstance: f ( )
var f = function (){
return decorator.apply( this , [{original: oldFunc, decoratorInstance: f}].concat([].slice.apply(arguments)))
}
// - - decoratorInstance f
f.old = oldFunc;
// -.
// - .
f.prototype = this .prototype;
f.prototype.constructor = f;
// -. ? .
// . , decorateMethod: .
f.name = oldFunc.name;
//
return f;
})


* This source code was highlighted with Source Code Highlighter .


( decorate(...) ), .
:
// -
function strictArgs() {
var types = arguments;
return function () {
var params = arguments;
//...
return arguments.callee.old.apply( this , arguments);
}
}

* This source code was highlighted with Source Code Highlighter .

:
// -
function strictArgs() {
var types = arguments;
return function (dScope) {
var original = arguments[0].original; // dScope.original
var arguments = Array.prototype.slice.call(arguments, 1);
var params = arguments;
//...
return original.apply( this , arguments);
}
}

* This source code was highlighted with Source Code Highlighter .


, :)

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


All Articles