JavaScript では関数も関数オブジェクトとして作成されます。
C 言語などでは関数はインスタンスではなくコードなのですが、JavaScript では関数オブジェクトというわけです。
関数については当サイトの JavaScript の関数 をみてください。
ここでは関数オブジェクトについて、もう少し突っ込んでみていきます。
ここで説明するクロージャをしっかり理解しておかないと、少し発展的なコードがチンプンカンプンということになってしまいますので、しっかり理解しましょう。
クロージャとは?
次の例を見てください。ここでは Person コンストラクタ を定義しています。
このコンストラクタが、「コンストラクタ内で宣言したローカル変数を参照する関数オブジェクト」をプロパティとしてもつオブジェクトを返していることに注意してください。
function Person(n, a){
var name = n;
var age = a;
return {
getName: function() {
return name;
},
setAge: function(i){
if( 0<= i ){
age = i;
}
},
getAge: function(){
return age;
}
}
}
var p = new Person('Hanako', 3);
document.write( p.getName() + '<br>');
p.setAge(-10);
document.write( p.getAge() + '<br>');
p.setAge(20);
document.write( p.getAge() + '<br>');
この実行結果は次の通りです。
Hanako ← name の値が返された 3 ← age の値が変わっていない 20 ← age の値が変わった
さて、このコードの動作の何が特徴的か、よく考えて見ましょう。Person コンストラクタを除いて、コードを上から順番に追っていきます。
var p = new Person('Hanako', 3);
Person コンストラクタにパラメータ 'Hanako' と 3 を渡して呼び出して、その結果を p に代入しています。
ここで、コンストラクタは次のように定義されています。
function Person(n, a){
var name = n;
var age = a;
return {
getName: function() {
return name;
},
setAge: function(i){
if( 0<= i ){
age = i;
}
},
getAge: function(){
return age;
}
}
}
つまり、コンストラクタの第一引数、第二引数はそれぞれ、Person コンストラクタ内のローカル変数 name, age にそれぞれセットされています。
コンストラクタから戻るときには、次のオブジェクトを返しています。
return {
getName: function() {
return name;
},
setAge: function(i){
if( 0<= i ){
age = i;
}
},
getAge: function(){
return age;
}
}
{ } で囲むのはオブジェクトリテラルでしたね。 不安な方は オブジェクト のページで復習しておいてください。
ここでは、getName、setAge、getAge というプロパティにそれぞれ関数オブジェクトをセットしています。このとき、関数の内部では コンストラクタのローカル変数である name や age を参照しています。
コンストラクタが返ったときにオブジェクトが返されますが、そのオブジェクトはローカル変数を参照している。
・・・あれ?ローカル変数はスコープを抜けたときに解放されて参照できなくなるのでは?
C 言語などをご存知の方はそう思われるかもしれませんね。まるで不具合のように思われるかもしれませんが、そうではありません。 これを機能させているのがクロージャです。
JavaScript の関数オブジェクトはそれが生成されたときの状態(コンテキスト)を保持します。このコンテキストには関数内部からアクセス可能です。 上の例で具体的に言えば、例えば getName 関数を呼び出し時にも変数 name は参照可能です。
関数とコンテキストの組合わせをクロージャ (closure) といい、JavaScript の関数オブジェクトはクロージャです。 このため上のコードが安全に動作するのです。
動作確認のために、setAge 関数と getAge 関数も書きましたが、これでも確かに変数 age にアクセスできていますね。