Functionクラスに対する拡張(1)
既存のFunctionクラスにメソッドを二つ追加しています。prototypeプロパティへの追加なので、インスタンスであるすべての関数で使用可能となります。
bindメソッド
【抜粋】 Function.prototype.bind = function() { var __method = this, args = $A(arguments), object = args.shift(); return function() { return __method.apply(object, args.concat($A(arguments))); } }
構造解析
thisは自オブジェクト、つまりFunctionクラスのインスタンスである関数*1です。$Aは後述する関数で、引数一つをArrayオブジェクトに変換します。argumentsは列挙可能ですがArrayオブジェクトではありません。shiftメソッドは既存ですが、後述のArrayクラスに対する拡張で再定義されています。ただ、ver1.5.0_rc0以降では再定義は削除されてます^^;
その後、無名関数が返却されます。第一引数で指定されたオブジェクトのプロパティ・メソッドを用い、第二引数以降に無名関数自体の引数を加えてこれを引数とし、自オブジェクトの関数を実行する、というものです。
メソッドの内部関数でthisを使用しても、元のオブジェクトを意味しません。よってメソッドで変数__methodにthisを代入し、それを内部関数で使用しています。
クロージャとレキシカル・スコープ
返却された関数でも、元のメソッドの変数を使用することができます。メソッドが関数を返却した後も、その変数と値は保持されているようです。(返却された関数が生きている間)
以下に例を示します。
【例】 function Test(){ this.value = 1; } Test.prototype.method = function() { var obj = this; //objにthisを保持 var i = 0; return function(p) { alert("this.value:" + this.value); alert("obj.value:" + obj.value); i += p; alert("i:" + i); } } var test = new Test(); var func1 = test.method(); var func2 = test.method(); func1(11); //"this.value:undefined", "obj.value:1", "i:11" と表示 //関数ごとに元のメソッドの変数の値が保持される func2(55); //"this.value:undefined", "obj.value:1", "i:55" と表示 //同じ関数であれば同じメソッドの変数が使用される func1(22); //"this.value:undefined", "obj.value:1", "i:33" と表示 func1 = null; //メソッドの変数も解放される
この特性は後述するEnumerableオブジェクトでよく利用されています。
2006/10/20追記。こうした特性を持つスコープ(変数有効範囲)の概念を「レキシカル・スコープ」というようです。また、ここで返却されるような関数を「クロージャ」というようです(単なる内部関数はクロージャとは言わないということかな)。
【参考】 クロージャとは - はてなキーワード クロージャとレキシカルスコープ - Backstage of theater.js
イベントハンドラへの関数バインド時に便利
このbindメソッドは、イベントハンドラに関数をバインドする際によく使われています。イベントハンドラに関数をバインドする場合、通常は関数の参照のみの指定で、引数を指定することができません。
【例】 function init(msg){ alert(msg); //IEなら"undefined"、NC等なら"object Event"と表示される } window.onload = init; //init("hello!"); とはできない
この場合、引数msgは、IEなら未指定、NC等であればイベントオブジェクトとなります。これを解決するため以下の方法がよく取られます。
【例】 function init(msg){ alert(msg); //"hello!"と表示される } function callInit(){ init("hello!"); } window.onload = callInit;
これをより汎用化し、thisを使えるようにしたのがFunction.prototype.bindメソッドであるといえます。(なんちゅう乱暴な説明^^;)
【例】 function Test(){ this.value = 1; } Test.prototype.method = function(msg) { alert(msg); //"hello!"と表示される alert(this.value); //"1"と表示される } var test = new Test(); window.onload = test.method.bind(test, "hello!");
2006/10/20 追記。以下も参考にしてください。
イベントへの関数登録について - Backstage of theater.js
apply関数について (2006/07/14 追記)
前述「Classオブジェクト」で書いたように、apply関数について以下の場合、第一引数はあまり意味を持ちません。(一度、意味があるようなことを書いてしまいましたが、間違いです・・・)
【例】 obj.method.apply(obj, args); //objのメソッド(=obj.method)をobjのメソッドとして(=第一引数)実行するという意味。
威力を発揮するのは、あるオブジェクトのメソッドを、別のオブジェクトのメソッドとして実行する場合です。例としては、イベント登録時や、引数や返却値としてクロージャを使う場合、bind関数により自オブジェクトのメソッド・プロパティを使用させることができるようになります。prototype.jsでも、以降そのような使われ方をしている箇所があります。*2