クロージャ 【プログラミング発展】
関数の戻り値に関数を設定できることはご存知だと思います。この仕様を踏まえて、より発展的な使い方を見ていきます。
また、クロージャではスコープの概念が非常に重要です。
コードはJavascriptですが、原理はどの言語でも同じです。
例
function f(a) { const b = 50; return (c) => { console.log(a + b + c); } } const v = f(5); const v2 = f(100); v(3); v2(500);
58 650
v
では5 + 50 + 3
が、v2
では100 + 50 + 500
が計算されています。これについて見ていきましょう。
v
はf(5)
の結果が代入されます。このとき、返される関数の本体は、a == 5
とb == 50
という状態を束縛した関数が作られています。その関数が戻り値なのでv
に代入されます。
その後、v(3)
とすると、a == 5
とb == 50
を束縛した関数に対し、c = 3
となるので、a + b + c
は5 + 50 + 3
になります。
関数は、実行ごとに独立して実行されるということが重要です。
つまり、let v = f(5);
では、a == 5
、b == 50
である独立した関数のもので、return (c) => ...
が作成されるということです。
JavaやC++など、デバッグ時にメモリスタックが表示されるものだと、実際にメモリが独立しているのを見たことがあるかもしれません。
v2
も同様ですので考えてみてください。
プライベートな値を持つオブジェクトを作ってみる
Javascriptのクラスはプライベートの値を扱えません。しかし、クロージャを使えば模倣できます。
function f() { const hello = "Hello, "; let lang = ""; const world = " World!!"; return { setLang: (l) => { lang = l; }, output: () => { console.log(hello + lang + world); }, } } const js = f(); js.setLang("Javascript"); js.output(); const cpp = f(); cpp.setLang("C++"); cpp.output();
Hello, Javascript World!! Hello, C++ World!!
このように、f
を実行するたびに3つの変数の状態を束縛した関数が作成されます。そして、それらの変数はスコープがf
内だけのため、外から直接変更したり、読み取ったりすることができません。つまりプライベートな変数です。
もちろん、同様に手法でプライベートなメソッドも作成できます。
function f() { const privateMethod = () => { // ... } return { a: () => { privateMethod(); }, b: 100, } } const hoge = f(); // hoge.privateMethod(); // Uncaught TypeError: hoge.privateMethod is not a function
このprivateMethod
はf
内では使えますが、外では使えないためプライベートです。
外のスコープ
クロージャで囲まれたものは、外のスコープを参照できます。
let d = 10; function f(a) { return b => { return c => { return a + b + c + d; } } } console.log(f(5)(4)(3));
22
このように、c => ...
は、外側のa
, b
, d
が参照できます。自分がいるブロックより外側の変数を参照できるというわけです。
もちろん、グローバルなスコープにある変数を書き換えるなどすると、従来どおり他の実行にも影響するので注意が必要です。
まとめ
クロージャーにおける変数などの挙動を紹介しました。束縛という新しい概念が出てきており、しかも極めて重要な概念です。Javascriptにおける即時関数もある意味クロージャの1種で、スコープを制限する役割があります。
実際にものを作る過程で、様々な例を試してみるのが近道と言って良いかもしれません。
