2007年11月26日月曜日

JavaScript の オーバーロード

jQuery のコア開発者として知られている、John Resig さんのブログに JavaScript Method Overloading という大変面白い記事がありました。

JavaScript のオーバーロード

ソースコードを見てみます。簡潔で素晴らしいコード。
// addMethod - By John Resig (MIT Licensed)
function addMethod(object, name, fn){
// 指定されたオブジェクトには,
// 既に同じ名前のメソッドが定義されている
// かもしれないので,一時変数に保存しておく
var old = object[ name ];

// オブジェクトのプロパティに関数定義を上書き.
object[ name ] = function(){
// 呼び出しされたメソッドの引数の数と関数に定義されている
// 引数の数が一致したとき呼び出されたメソッドを適用する.
if ( fn.length == arguments.length )
return fn.apply( this, arguments );

// 呼び出されたメソッドと,定義時の引数の数が
//一致しなかった場合には,一時保存しておいた
// 同名のプロパティをチェック.
// 一時保存されたプロパティが関数オブジェクトだった場合には
// プロパティに引数を適用
else if ( typeof old == 'function' )
return old.apply( this, arguments );
};
}
上記のコードを見ると分かるように,あるメソッドが呼ばれた場合,引数の数とメソッドが期待する引数の数をチェックします.引数の数が一致した段階で呼びだされたメソッドを実行しますが,一致しない場合には,引数の数が一致するまで addMethod でオーバーロードされたメソッドを辿ることになります.

オーバーロードされたメソッド,及び一番最初に定義されたメソッドを全て辿っても引数の数が一致するメソッドが見つからなかった場合には,処理が何も行われません.

この関数を以下のように使います。
// ユーザの検索処理を引数の数によって分岐
function Users(){

// 引数なしのとき
addMethod(this, "find", function(){
// Find all users...
});

// 引数がひとつのとき
// find メソッドをオーバーロード
addMethod(this, "find", function(name){
// Find a user by name
});

// 引数がふたつの場合
// find メソッドをオーバーロード
addMethod(this, "find", function(first, last){
// Find a user by first and last name
});
}

// ユーザのコンストラクタ呼出し
var users = new Users();

// 引数なし
users.find(); // Finds all

//引数 1つ
users.find("John"); // Finds users by name

// 引数 2つ
users.find("John", "Resig"); // Finds users by first and last name

// 引数 3つのときは定義していないので何もしない
users.find("John", "E", "Resig"); // Does nothing

fn.length??

このオーバーロードメソッドの肝は、
if ( fn.length == arguments.length )
の部分です。この部分では,引数の数が一致するまでメソッドを辿り、一致した段階でメソッドに引数を適用しています。
This isn't very well known, but all functions have a length property on them. This property equates to the number of arguments that the function is expecting. Thus, if you define a function that accepts a single argument, it'll have a length of 1, like so:

(function(foo){}).length == 1JavaScript Method Overloading
とあるように、関数は length プロパティを持っており、関数自体が期待する引数の数が保持されています。

このコードの注意点として,
  • 引数の数以外,例えば引数のデータ型や引数の名前などではオーバーロードできない
  • 関数呼び出しにオーバヘッドがあるから高パフォーマンスが求められる状況では適宜判断する
といったことがありますが,とても勉強になりました.
こういう素晴らしいコードを早く書けるようになりたいです.