web 2.0 EXPO の JavaScript Labs で、リクルートの川崎さんが講演されていました。口頭では触れていませんでしたが、講演資料の中で無名コンストラクタが紹介されていました。
無名コンストラクタを使うと、無名関数と同様に、グローバルの名前空間を汚さず、コードを実行できます。長くなりますが、C とも比べながら、順を追って、僕なりに説明してみたいと思います。
無名関数
C では関数に必ず名前をつけないといけません。一方、JavaScript では、無名関数が用意されています。
たとえば、
function foo(x) { return x + 1; }
と
var foo = function(x) { return x + 1; };
は、(名前に "." が含まれていないなら)等価です。
function で定義した関数を変数に代入しなければ、関数に名前がなく、無名関数となります。
無名関数の実行
var foo = function(x) { return x + 1; }; (foo)(1);
のように、わざわざ foo という名前を付けなくても定義した関数を実行できます。これは、関数呼び出しの関数名前の部分に、関数の定義を書くだけです。
(function(x) { return x + 1; })(1);
関数の定義は、括弧で囲む必要があります。括弧で囲まない以下の例は、エラーとなります。
function(x) { return x + 1; }(1);
無名関数の利用例
グローバルの名前空間を汚さずにコードを実行したい場合、無名関数を利用できます。
(function() { var weekOfDays = ['日', '月', '火', '水', '木', '金', '土' ]; var today = new Date(); var weekOfDay = today.getDay(); alert('今日は' + weekOfDays[weekOfDay] + '曜日です'); })();
変数 today などは、関数のローカル変数ですから、グローバルの名前空間を利用しなくて済む訳です。
new の内側
JavaScript のコンストラクタは、単なる関数です。その関数の中で、this を使うと、それが新しいオブジェクトを参照するので、そのオブジェクトのプロパティを設定できます。
var Func = function(x) { this._x = x; }; var obj = new Func(1); // => {_x: 1}
JavaScript を学んだとき、this を含まない関数に対し new するとどうなるんだろうと疑問に思った方もいるかもしれません。疑問を解消するために、new の内部で何が起こっているか調べてみましょう。
JavaScript で new Func(args) と実行すると、内部で次のような工程で新しいオブジェクトが作られます。
- 新しいオブジェクトを作る
- Func.prototype がオブジェクトなら、新しいオブジェクトのプロトタイプを Func.prototype に設定する
- 新しいオブジェクトにコンストラクタを適応する
- 上記の返り値がオブジェクトなら、上記の返り値を返す
- さもなくば、新しいオブジェクトを返す
new を関数 New で無理矢理表すと以下のような感じになります。
function New(Func, args) { var obj = {}; if (Func.prototype instanceof Object) { obj.__proto__ = Func.prototype; } var ret = Func.apply(obj, args); if (ret instanceof Object) { return ret; } else { return obj; } }
無名コンストラクタ
new の内側を見ると、this を含まない関数に new してもよさそうです。その関数は、新しいオブジェクトに対して実行されますが、関数側ではそのオブジェクトに関与しないだけの話です。
var Foo = function() { var weekOfDays = ['日', '月', '火', '水', '木', '金', '土' ]; var today = new Date(); var weekOfDay = today.getDay(); alert('今日は' + weekOfDays[weekOfDay] + '曜日です'); }; new Foo(); // => Foo が実行され、新しく作られたオブジェクトは捨てられる
new には無名関数を指定できますから、Foo という名前は不要です。
new function() { var weekOfDays = ['日', '月', '火', '水', '木', '金', '土' ]; var today = new Date(); var weekOfDay = today.getDay(); alert('今日は' + weekOfDays[weekOfDay] + '曜日です'); }; // => Foo が実行され、新しく作られたオブジェクトは捨てられる
これはグローバルな名前空間は汚さずに、コードを実行したことに他なりません!