jQueryのドル記号「$」は、理不尽なくらいに大活躍します。あまりにも何でもできるので、「ありゃ何なんだ?」と思いますよね。$(...) という形で呼び出せるので、関数なのは確かです。JavaScriptでは、ひとつの関数名に対して呆れるくらいにオーバーロード(多義的定義)ができます。
内容:
コンストラクタあるいはクラスとしての関数
関数は、オブジェクトのコンストラクタとして使用できます。
function Point(x_, y_) { this.x = x_ || 0; this.y = y_ || 0; }
お尻にアンダースコアが付いた引数名 x_, y_ は省略可能のつもりです。new Point()、new Point(2)、new Point(2, 3) とかすると、2次元の点を表すオブジェクトが生成されます。
JavaScriptでは、正式なクラスはありませんが、コンストラクタ関数名をクラス名のように使えます。例えば、pt instanceof Point とすれば、オブジェクトptがクラスPointのインスタンスかどうかを判定できます。
メソッドとしての関数
クラスのメソッドは、コンストラクタ関数のprototypeに対して関数を定義すればいいですね。
Point.prototype.moveTo = function(x, y) { this.x = x; this.y = y; }; Point.prototype.toString = function() { return "(" + this.x + ", " + this.y + ")"; };
これで、Pointのインスタンスであるptに対して、pt.moveTo(3, 4) とか pt.toString() というメソッド呼び出しが可能になります。
Point.prototype.moveTo は関数なので、pt.moveTo(3, 4) は Point.prototype.moveTo.apply(pt, [3, 4]) としても同じです。applyの第1引数ptは、関数Point.prototype.moveToにthisとして渡されます。
名前空間あるいはモジュールとしての関数
関数はオブジェクトでもあるので、そのプロパティとして値や関数を設定することができます。これにより、値や関数をグループにまとめることができます。グルーピングに使われるオブジェクトは名前空間と呼ばれます。名前空間はモジュール化に利用するので、モジュールと呼んでもいいと思います。
Point.norm = function(pt) { return Math.sqrt(pt.x*pt.x + pt.y*pt.y); };
Point.normは、引数で与えられた点の「ノルム=原点(0, 0)からの距離」を求める関数です。Pointクラスのスタティックメソッドと思ってもいいですし、Pointモジュールに属する関数と思ってもかまいません。
関数/オブジェクトとしての関数
関数はもちろん関数です。つまり、Point() のように、newを付けないで呼び出すことができます。コンストラクタ関数にnewを付けずに呼び出すと、通常はundefinedが戻り値として返ります。しかし、別な値を返すようにすることもできます。
「JavaScriptでnewが不要なコンストラクタ」において、 if (!(this instanceof クラス名)) という条件により、new付きで呼び出されたか/そうでないかを判定する方法を紹介しました。この方法を使うと、次のようなことができます。
function Point(x_, y_) { if (!(this instanceof Point)) { return Point.help; } this.x = x_ || 0; this.y = y_ || 0; } Point.help = "\n" + "new Point(x, y) -- 新しい点を生成、x, y は省略可能\n" + "Point() -- このヘルプテキストを出力\n" + "pt.moveTo(x, y) -- 位置を変更\n" + "pt.toStringo() -- 文字列化\n" + "Point.norm(pt) -- (0, 0) からの距離を求める\n" ;
Point() をnewなしで呼び出すと、ヘルプテキストを出力します。そのヘルプテキストは、Pointオブジェクトのhelpプロパティに保存しています。
試してみる
今までに出てきたコード断片をまとめましょう。
function Point(x_, y_) { if (!(this instanceof Point)) { return Point.help; } this.x = x_ || 0; this.y = y_ || 0; } Point.help = "\n" + "new Point(x, y) -- 新しい点を生成、x, y は省略可能\n" + "Point() -- このヘルプテキストを出力\n" + "pt.moveTo(x, y) -- 位置を変更\n" + "pt.toStringo() -- 文字列化\n" + "Point.norm(pt) -- (0, 0) からの距離を求める\n" ; Point.prototype.moveTo = function(x, y) { this.x = x; this.y = y; }; Point.prototype.toString = function() { return "(" + this.x + ", " + this.y + ")"; }; Point.norm = function(pt) { return Math.sqrt(pt.x*pt.x + pt.y*pt.y); };
firebugのコンソールで試してみると次のようになります。
>>> Point()
"
new Point(x, y) -- 新しい点を生成、x, y は省略可能
Point() -- このヘルプテキストを出力
pt.moveTo(x, y) -- 位置を変更
pt.toStringo() -- 文字列化
Point.norm(pt) -- (0, 0) からの距離を求める
">>> var pt = new Point()
undefined>>> pt.toString()
"(0, 0)">>> pt.moveTo(3, 4)
undefined>>> Point.norm(pt)
5