Closureについて

やっていることが難しくなってきて、壁にぶつかりまくっている今日この頃。
3ヶ月くらい放置していたExercismを気分転換にやってみる。
その中で出てきたコンセプトが「Closure」。

Closureなんて聞いたこともないし、何のことかさっぱり分からない。
Exercismの課題も何をやらされているのかさえ分からない。
調べてみても、難しい言葉ばかりで説明の半分くらいしか理解できない💦
だけど、コード自体は見慣れたものばかりという不思議な感覚。
“function”とか、”array”とか、はコードの文法とかスペルの学習に近かったのに対し、
“Closure”はJavaScriptにおける”文脈”を理解するという領域の話のようだ。

JavaScriptにおける文脈とは?

「その動物、めっちゃ可愛いよね!」
動物園デートでこう言われても、前後の文脈や、その時の状況、身振り手振りなど、相手が一体どの動物の話をしているのか、分かっていれば会話が成立する。

“let”を使って「animalと言ったら”Platypus”のことですよ」と予め伝えているから、
ちゃんと”Platypus”に置き換えて、console.logしてくれている。

let animal = "Platypus";

function printConversation (){
  console.log(animal + " is so cute!");
}

printConversation(); // Output : "Platypus is so cute!"

animalの値が途中で置き換わった場合には、Outputも変わる、この部分も特に目新しい話ではない。

function printConversation (){
  console.log(animal + " is so cute!");
}

let animal = "Monkey";
printConversation(); // Output : "Monkey is so cute!"

animal = "Platypus";
printConversation(); // Output : "Platypus is so cute!"

上で見た例のようにfunctionを先に書いてもいいし、コードの順番を多少入れ替えても動く。これも、特に珍しくはない話。

let animal = "Monkey";
printConversation(); // Output : "Monkey is so cute!"

animal = "Platypus";

function printConversation (){
  console.log(animal + " is so cute!");
}

printConversation(); // Output : "Platypus is so cute!"

では以下の場合は?
printConversation()を実行した時にどちらが表示されるだろうか。
Monkeyか、Platypusか。

let animal = "Monkey";

function printConversation (){
  console.log(animal + " is so cute!");
}

animal = "Platypus";

printConversation(); // Outputはどうなる?

(答えは:Platypus

実際に自分がそんな順番でコードを書くかどうかは別として、確かに”直前”の値が呼ばれるという感覚でしかコードを書いていないとすると、直前というのが、functionを定義する直前なのか、functionを呼ぶ直前なのか、改めて聞かれると迷いが出てくるかもしれない。

JavaScriptにおける”文脈”をどのように理解するかは、見えないところでルールが決まっている。
どのようなルールかというとコードを書く順番や、変数を宣言するタイミングなどについてだが、そのルールを知らないと、コンピュータとの会話に誤解が生じ、こちらが思っていたのと違う結果が出てしまうかもしれない。

そして、JavaScriptの”文脈”を理解するときのルールの中でも特に分かりにくいものがClosureである

Closureはなぜ難しく感じるのか

Closureについて調べていくと私以外にもつまづく人が多いようだ。
私が難しいと感じたポイントは特に以下4つ。

(1)どちらの動物がConsole.logされるかというようなクイズに勘で正解してしまうと、何故そうなるかがよく分からなくても満足して次の問題に進んでしまう
(2)練習問題で書かされるコードがfunctionの中でfunctionをreturnしているものなど、突然見慣れない方法でコードを書くように求められる
(3)何故そうなるのかについてロジックの説明を見ると難解ワードばかりで挫折する
(4)Closureには広い意味と狭い意味がある??(ページによって書いてある定義が違う気がする)

カモノハシの力を借りて乗り越えよう!

Closureは親子の特別な関係

ClosureはJavaScriptコードにおける文脈を理解する時のポイントの一つであるが、どのようなものか?

インターネットでClosureについて調べると、functionの中でfunctionをreturnしているサンプルコードを必ず目にするが、functionの中でfunctionをreturnすることをClosureと呼んでいるのではなく、一番Closureというコンセプトを確認しやすい典型的な例だから使われているだけである。

以下の例では、①のfunctionは②のfunctionをReturnしている。
①の中でfoodは”yabby”(ザリガニ)と定義され、②のfunction内でfoodという変数が使われている。
③では別の変数に②のfunctionが入るが、その後④でfoodが”worm”(虫)と定義されて、⑤を実行。
この場合、JavaScriptでは②のfoodはどのような文脈で理解されるのか?という問題。

function platypusFamily() { // ①
    let food = "yabby";

    return function() {   // ②
        console.log("I wanna eat a "+ food +"!");
    };
}

let printConversation = platypusFamily(); // ③

let food = "worm";  // ④

printConversation(); // ⑤ Outputはどうなる?

⑤でprintConversation()を実行する時にfoodに何を当てはめるだろうか。
yabby(ザリガニ)か、worm(虫)か。(答えは:yabby

Closureをカモノハシの親子関係に例えてみる。①は親、②は子。子は親から食べ物(変数food)を与えられるという状態。この状態は、子カモノハシが巣穴から出てどこかへ散歩しに行くにせよ(どのスコープで呼び出されても)変わらない。
④はある種、Closureを説明しようとして余計に分かりにくくしているポイントだ。同じfoodという変数だが、①の中のfoodとは別の場所にあるもので、①のfoodを上書きしようとしているわけではないし、しようとしてもできない。本来は別の名前をつけるべき。このカモノハシ親子が暮らす場所にはエビの他にも虫もいるよ(どちらもカモノハシの好物)というだけ。
子カモノハシはお腹が空くと、親に食べ物をねだる。⑤”I wanna eat a yabby!” 。
親カモノハシがyabbyを持っていることを覚えているから。このカモノハシ親子の関係ではfoodと言ったらyabbyなのだ。この親子関係がClosureであり、JavaScriptでは親子関係(Closure)がある場合はfoodと言ったらyabbyという文脈で理解することになる。

ポイントは変数の扱い方

functionの外側で変数が定義されている場合
もし親カモノハシが子にyabbyを与えたことがなかったら。
① の中にfoodの定義がない場合を見てみる。ここでは⓪の変数foodが④で”worm”に上書きされるタイミングが頭を悩ませるが、子カモノハシは親からどちらのfoodも与えられていないので、JavaScriptはは子カモノハシが食べ物を欲しがった⑤のタイミングで考えて、foodと言えば”worm”のことだという文脈で処理する。

let food = "yabby"; // ⓪ functionの外側で定義

function platypusFamily() { // ① この中にfoodの定義がない場合

    return function() {   // ②
        console.log("I wanna eat a "+ food +"!");
    };
}

let printConversation = platypusFamily(); // ③

food = "worm";  // ④

printConversation(); // ⑤ Output:I wanna eat a worm!

パラメータで渡す場合
上の例と同じくfunctionの外側で変数が定義されているが、③でfoodをパラメーターとして渡していた場合、③のタイミングではfoodが”yabby”だったので、変数printConversationにplatypusFamily(food)を実行して結果を返した時点で親カモノハシに渡されたfoodは”yabby”と記憶され、JavaScriptは子カモノハシが欲しがっているのは”yabby”のことだという文脈で処理する。

let food = "yabby"; // ⓪ functionの外側で定義

function platypusFamily(food) { // ① foodのパラメータを受け取る

    return function() {   // ②
        console.log("I wanna eat a "+ food +"!");
    };
}

let printConversation = platypusFamily(food); // ③ パラメータを渡す

food = "worm";  // ④ ⓪のfoodを上書き

printConversation(); // ⑤ Output: I wanna eat a yabby!

Consoleで確認

Closureはそういう関係性だとか言われても、目に見えないと分かりづらかったりする。
そんな時は”debugger”を追加して、⑤の実行中のScopeという項目を見ると、ちゃんとClosureって書いてある!

Closureについて、あるいはせてめカモノハシの好物について、
どちらかでも分かれば今日はもういいんじゃないか?
というわけで本日はここまで!


記念すべき10回目の投稿テーマが「Closure」でした。
アプリ作りを再開するぞ!

platypus eats yabby at Healesville Sanctuary
a sign at Healesville Sanctuary

類似投稿