動機と目的
Node.js, JavaScriptを久しぶりに使おうとして、さっぱり忘れていることに気づいた。なので、ちょっと思い出すために調べたことをメモ代わりに残す。(といった使い方も Qiita は良いのだろうか?)
当面の目的は以下のTimerを使えるようになること。
Node.js API - JavaScript のタイマー機能と非同期呼び出し | プログラマーズ雑記帳
最初にクロージャというのがわからなかった。以下の記事に解説されているが、
JavaScriptでクロージャ入門。関数はすべてクロージャ? - Qiita
そもそも以下のサンプルが読めない(読み解くことができない)。
var module = (function() {
var count = 0;
return {
increment: function() {
count++;
},
show: function() {
console.log(count);
}
};
})();
module.show(); // 0
module.increment();
module.show(); // 1
console.log(count); // undefined
そういえば無名関数というのがあったのだな、とぼんやりと思い出す。
最初のvar moduleの定義の最後に })(); とやたらカッコが書かれているのが、なんのことか分からない。最初の}は、function() { を閉じるためのカッコ、次の)は module = ( を閉じるためのカッコである。しかし、その次の()というのが何だ?、と思った。
で、調べてみて、分かったことを以下に記述する。
関数の受け渡し、定義即実行、プロパティ、クロージャなど
まず、JavaScriptでは関数を、値と同じように受け渡しできる。
function f1() {};
var a = f1;
最初のはf1という関数の定義で、何もやらない関数である。この関数をaに代入している。こういうことが可能である。これを短く書くと、以下となる。
var a = function f1() {};
さらに、この例では関数f1は、aとして呼び出されるため、f1という名前は無駄でもある。よって、以下のように関数名すら定義から消すことができる。
var a = function () {};
さて、では関数が代入されたaを実行するには、どうすれば良いのか、というと、以下のようにする。
var a = function () {};
a();
aというのは = 以下の、無名の関数への「参照」であるから、実行するには()を付ける。動作確認するために、出力を加えてみる。
var a = function () { console.log("TEST"); };
a();
ここで、関数の定義の後ろに()をつければ、定義した直後に実行できる。これが良く使われる。つまり、関数の定義と実行を一括で記述する簡便な方法だ。
var a = function () { console.log("TEST"); } ();
こうすると、実行された結果がaに代入される。この関数は何もreturnしないので、aには何も代入されない。
var a = function () { console.log("TEST"); } ();
console.log(a);
上記のコードを実行すると、TESTの後に、「undefined」が出力される。aには何も入っていない、という意味である。
以下のように関数の中で returnを定義すれば。aには実行結果として、1が代入されることになる。
var a = function () {
return 1;
} ();
console.log(a);
最初に述べたように、JavaScriptでは関数を値と同じように受け渡しできるため、この戻り値も関数にすることができる。
var a = function () {
return function () {
console.log("TEST");
}
} ();
こうすると、aには、関数を実行した結果、return の後ろの関数がaに代入されることになる。よって、以下のように戻ってきた関数を実行することができる。
var a = function () {
return function () {
console.log("TEST");
}
} ();
a();
最初のコードを理解するには、さらにプロパティを知る必要がある。return文の中に、プロパティが定義されている。return文に2つのプロパティを入れると、例えば以下のようになる。
var a = function () {
return {
propA: function () { console.log("A"); },
propB: function () { console.log("B"); }
};
} ();
a.propA();
a.propB();
aはpropAと、propBという2つのプロパティを持つ関数である。それぞれのプロパティには、別の関数が割り当てられている。
ここまで理解できると、クロージャを解説している最初のプログラムがわかるようになる。
var module = (function() {
var count = 0;
return {
increment: function() {
count++;
},
show: function() {
console.log(count);
}
};
})();
module.show(); // 0
module.increment();
module.show(); // 1
// console.log(count); // undefined
ちなみに、module = (の(は不要である。 ここで不思議がらねばならないのは、countという変数の値が、保持されている点である。関数の中で定義された関数は、その属しているスコープの変数も一緒に保持するようになっている。
moduleという関数は、countの値も含めて更新されている。これがクロージャである。値も含めて保持していることを使うと、次のようなプログラムを書くこともできる。
function counter() {
var count = 0;
return {
increment: function() { count++;
},
show: function() {
console.log(count);
}
};
};
var mod1 = counter();
var mod2 = counter();
mod1.show(); // 0
mod1.increment();
mod1.show(); // 1
mod2.show(); // 0
mod2.increment();
mod2.show(); // 1
counterの実行結果、つまりreturnの後ろの関数群を mod1 と mod2 という2つの変数に代入する。
それぞれ代入時点で、var count = 0でcountが初期化される。
その後、incrementが呼ばれるたびに内部に保持しているcountが+1される。
(mod1がオブジェクト、その属性値がcountという感じになる。)
外から直接操作できない count は、まるでプライベート変数のようである。
(ちなみに、JavaScriptにはプライベート変数は無いらしい)
なるほど。
まとめ
-
JavasScriptでは、関数も代入したり、引数や、返り値に使うことができる。
-
関数を定義した直後に実行することもできる。
-
実行オプション付きの関数を、変数に代入すると、実行結果のreturnの中身が代入される。
-
関数をreturnすると、関数が代入される。
-
return内にプロパティを定義すれば、プロパティ付きのreturnを返すことができる。
-
関数内部で定義した関数を、代入した変数(オブジェクト)では、中の変数の値も保持し続ける。
-
この変数値も保持し続ける関数をクロージャと呼ぶ。
-
クロージャを使うとプライベート変数のようなものが実現できる。
-
JavaScriptは、スクリプト言語としての便利さを追求したものだと分かった。
-
あと、Qiitaは、プログラムを記述するのに、とても便利だと分かった。