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

*1:function ***(){}で定義される関数すべて

*2:Ajax.Updaterクラスinitilizeメソッド内のthis.options.onCompleteプロパティへのクロージャ登録(760行目)、Insertion.BeforeのinsertContentメソッド等