メモ化された fibonacci.js
var fibonacci = function () {
var memo = [0, 1];
var fib = function (n) {
var result = memo[n];
if (typeof result !== 'number') {
result = fib(n -1) + fib(n -2);
memo[n] = result;
};
return result;
};
return fib;
}();
for (var i = 0; i <=100; i +=1) {
document.writeln('// ' + i + ': ' + fibonacci(i) +'<br>');
}
入れ子状になった関数の戻り値を、配列やオブジェクトに記録(メモ化)すると、再帰的な関数の呼び出し回数を大幅に減らすことができます。メモ化についてはクロージャを使ってメモ化 – maru source で、フィボナッチ数列を例に詳しく解説されていますので割愛して、今回は関数のクロージャについていまいち良く分からなかったので調べてみました。
8.6 クロージャ
最近の多くのプログラム言語と同じように、JavaScript では構文スコープを使います。つまり、関数を実行するときには、関数が定義されたときに有効であった変数スコープを使います。関数が呼び出されたときに有効な変数スコープではありません。(中略) 関数オブジェクトと、関数の変数の名前解決に使われるスコープを組み合わせたものを、コンピュータサイエンスの分野ではクロージャと呼んでいます。
正確に言えば、JavaScript の関数はすべてクロージャです。
ここまでは何を言っているのか良く解りませんね。実際にコードを見てみましょう。
クロージャを理解するためには、まず入れ子型関数の構文スコープについて考えてみましょう。
var scope = "global scope"; // グローバル変数。
function checkscope() {
var scope = "local scope"; // ローカル変数。
function f() { return scope; } // ここでのスコープ中の値が戻される。
return f();
}
checkscope() // => "local scope"
checkscope()関数は、ローカル変数を宣言し、その後、この変数の値を返す関数を定義し呼び出されます。checkscope() を呼び出したときに、”local scope” が戻されることに疑問を抱かないと思います。ここで、このコードを少し変更してみましょう。次のコードでは何が返されるかわかりますか。
var scope = "global scope"; // グローバル変数。
function checkscope() {
var scope = "local scope"; // ローカル変数。
function f() { return scope; } // ここでのスコープ中の値が戻される。
return f;
}
checkscope()() // 何が返されるか?
今回のコードでは括弧がcheckscope()から外に移動しただけです。入れ子型の関数を呼び出して戻りを返すのではなく、入れ子型の関数オブジェクト自身を返しています。括弧をコードの最後の行に動かすことで、入れ子型の関数を定義した場所の外側で呼び出した場合、どうなるでしょう。構文スコープの基本となる規則を思い出してください。JavaScript の関数は、定義されたときに有効なスコープチェーンを使って、実行される、という規則です。入れ子型の関数 f() は、変数 scope の値が “local scope” にバインド(束縛)されているスコープチェーンの下で定義されました。この変数バインドは、 f を実行するときも有効です。どこで呼び出されたとしても、これは変わりません。したがって、先のコードの最後の行が返すのは “local scope” です。”global scope” ではありません。
先の fibonacci.js の例では入れ子になったローカル 関数、 fib = function (n) がfibonacci(i)で呼び出されて、ローカル変数が返されたというわけです。();