1️⃣これは何?
ソフトウェアエンジニアの皆様におかれましては、日々技術力アップのために研鑽を続けておられることと思います!Webの発達に伴い、色々な情報に溢れている現代ですが、どうやって質の良い学習をするのが良いか、実際に特定のお題を使って試して行きたいと思います💪
2️⃣なんでClosure?
なんとなく、、笑。中級者向けっぽいトピックかなぁと思いました。一般的なプログラミング言語における逐次・分岐・繰り返しを一通り学習した方のネクストステップのお題としてはちょうど良いかな、と思った次第です。
3️⃣TL; DR
勉強の仕方として、Qiitaや個人のBlogなどを読むのも良いのですが、まずは公式ドキュメントを読む癖をつけましょう。これだけで得られる知識の精度がかなり変わってきますし、何よりも体系的な知識が得られます。Blogなどの情報は古いことも多々あります。進化の早いプログラミング言語やライブラリほど公式ドキュメントを参照した方が結果的に効率的だったりします。なお、JavaScriptの場合、MDNを読む癖をつけるのが一番の早道です。MDNは主要なドキュメントも日本語化されてますし、取り組みやすいと思います。
4️⃣早速読んでいきましょう
MDNをちゃんと読む
めげずに読みましょう。初学者の場合、読んでいくとどんどん分からない単語が出てくると思います。これをめげずに調べて行きます。 Yak shavingと揶揄されることもありますが、負けずに調べて行きます。この記事を見ていてぶつかりそうなキーワードを記録して行きます。
レキシカルスコープ??😇
ググってみると以下の記事にヒットしました。
Wikipediaによると「静的スコープ」の中で登場していることがわかります。
静的スコープ(せいてきスコープ、英: static scope)とは、プログラミング言語におけるスコープの一種。字句のみから決定できるため、字句スコープまたはレキシカルスコープ (lexical scope) ともいう[1]。
なるほど。そもそもスコープってなんでしたっけ笑?Wikipediaに記載がありますね。
ブロックなどの構造を持つプログラミング言語では、あるブロックの内側のローカル変数は、そのブロックの外側からは「見えない」というものが多い。ただし、以前のJavaScript (ECMAScript) のように、サポートされるのは関数内ローカルのみで、ブロックローカルというスコープは無いものもある[2]。
ブロックの内側の変数はブロックの外側からは見えない、というのは直感的に分かりやすいですね。Wikipediaにもありますが、今回は一般的なC++言語で試してみます。
#include <iostream>
using namespace std;
int main() {
int i = 100;
{
int j = 200;
cout << i << j << endl;
}
cout << i << j << endl;
return 0;
}
上記はコンパイルエラーになります。変数 j
はブロックの中からしか参照できませんよ、っていうことです。わかりやすいですよね。これがブロックローカル、というものです。
/Users/kuzukawa/CLionProjects/study/main.cpp:9:23: error: use of undeclared identifier 'j'
std::cout << i << j << std::endl;
^
1 error generated.
逆に、こういうコードは普通に通ります。
#include <iostream>
using namespace std;
int main() {
int i = 100;
{
int i = 200;
cout << i << endl;
}
cout << i << endl;
return 0;
}
int i
が2回宣言されていますね。これを実行すると以下のように出力されます。
200
100
なるほど。手を動かしながら試してみると動きが良くわかりますね。ここまででようやく、
ブロックなどの構造を持つプログラミング言語では、あるブロックの内側のローカル変数は、そのブロックの外側からは「見えない」というものが多い。
の意味がわかってきました。次に以下の文章を理解していきます。
ただし、以前のJavaScript (ECMAScript) のように、サポートされるのは関数内ローカルのみで、ブロックローカルというスコープは無いものもある[2]。
え?なに言ってるんすか笑?あっ、注釈がついてますね。こちらもちゃんと読んでみましょう。
ECMAScript 6 からは、キーワードvarで宣言した関数内ローカルな変数の他に、letで宣言した変数はブロックローカルになる。
ECMAScript6って何やねん!JavaScriptの話しをしてるんじゃないんかい!!という気持ちになると思うので、ここで今度はECMAScriptとは何か、を調べていきます。幸運にもWikipediaに記事がありました。
ECMAScript(エクマスクリプト)は、Ecma Internationalもとで標準化されたJavaScriptの規格である。ISO/IEC JTC 1はISO/IEC 16262[3]として、日本もJIS X 3060:2000として規格を定めている[4][5]。
なるほど、JavaScriptの規格なんですね。Wikipediaからの切り貼りですが、ざっくりとしたEditionごとの変更履歴は以下のようです。Edition1の公開からもう25年も経ってるんですねぇ、、そういうところも意識しながら読んでいくと熱い気持ちになりますね笑。
Edition | 公開日 | 以前のバージョンとの違い | 編集者 |
---|---|---|---|
1 | 1997年6月 | 初版 | Guy L. Steele, Jr. |
2 | 1998年6月 | Editionとしての仕様はそのままであり、ISO/IEC 16262 international standardに完全な対応をした | Mike Cowlishaw |
3 | 1999年12月 | 正規表現、よりよい文字列の取り扱い、新しいコントロール構文、try/catch例外処理、より厳格なエラー処理、数字のその他の書式化フォーマット | Mike Cowlishaw |
4 | 放棄 | 4th Editionは放棄された。言語の複雑化に関する政治的な差異による。いくつかの成果は5thの基礎として採用され、いくつかは6thの基礎となっている。 | |
5 | 2009年12月 | "strictモード"、初期化時に発生しがちなエラーを回避するための追加仕様の追加。多くの曖昧な部分、および仕様に準拠しつつも現実世界の実装の融通の利く振る舞いを明確にした。いくらかの新機能、getterやsetter、JSONライブラリのサポート、より完全なオブジェクトの属性のリフレクション[7] | Pratap Lakshman, Allen Wirfs-Brock |
5.1 | 2011年6月 | Pratap Lakshman, Allen Wirfs-Brock | |
6 (2015) | 2015年6月 | クラス、モジュール、イテレータ、for/ofループ、Pythonスタイルのジェネレータ、アロー関数、2進数および8進数の整数リテラル、Map、Set、WeakMap、WeakSet、プロキシ、テンプレート文字列、let、const、型付き配列、デフォルト引数、Symbol、Promise、分割代入、可変長引数 | Allen Wirfs-Brock |
7 (2016) | 2016年6月 | 冪乗演算子、Array.prototype.includes | Brian Terlson |
8 (2017) | 2017年6月 | 非同期関数 (async/await)、SharedArrayBufferとAtomics、String.padStart/padEnd、Object.values/entries、Object.getOwnPropertyDescriptors、関数の引数における末尾のカンマ許容 | |
9 (2018) | 2018年6月 | オブジェクトに対するスプレッド構文、非同期イテレーション、Promise.prototype.finally、正規表現への機能追加 | Brian Terlson |
10 (2019) | 2019年6月 | Array.prototype.flat、Array.prototype.flatMap、Object.fromEntriesの追加、他 | Brian Terlson, Bradley Farias, Jordan Harband |
11 (2020) | 2020年6月 | オプショナルチェイニング演算子?.、Null合体演算子??、BigIntの追加、他 | Jordan Harband, Kevin Smith |
なるほど、Edition 6で大きな変更が加えられたんですね。アロー関数や、 let
、 const
などもここから導入されたわけですね。ちょっと脇道にそれますが、この Edition 6 (とNode)が昨今のJavaScriptの爆発的な普及につながっているような気がしています。ECMAScriptの過去の履歴を全部拾っていくと気が遠くなるので、ECMAScriptについての整理は一旦、ここまでにしましょう。
もう一度先ほどの文章に戻ります。
ただし、以前のJavaScript (ECMAScript) のように、サポートされるのは関数内ローカルのみで、ブロックローカルというスコープは無いものもある[2]。
ECMAScript 6 からは、キーワード
var
で宣言した関数内ローカルな変数の他に、let
で宣言した変数はブロックローカルになる。
ちょっと読めるようになりましたね笑。
以前のJavaScript (ECMAScript)のように、
っていうのは、 var
を利用した場合、という意味ですね。JavaScriptにおいては、どうやら var
と let
ではスコープの概念が異なるようです。ここからはまた、手を動かして確認していきます。先ほどC++で書いた内容と全く同じコードをJavaScriptで書いてみます。
var i = 100;
{
var i = 200;
console.log(i);
}
console.log(i);
実行してみると、以下の結果となりました。
200
200
なんと、、、ブロック内で書いた値が優先されています。これはどういうことなんでしょう。更に以下のコードを実行してみます。
var i = 100; // グローバル
{
var i = 200; // ブロック内ローカル(ただし、スコープはグローバル)
console.log(i);
}
var i = 300; // グローバル(同一変数の再定義)
console.log(i);
実行してみると、以下の結果になりました。
200
300
ぐおっ、なるほど、、動かしてみると var
を使わない方が良い理由が良くわかりますね笑。ただし、「関数内ローカルはサポートされる」らしいので、念の為こちらも確認しておきましょう。
var i = 100; // グローバル
{
var i = 200; // ブロック内ローカル(ただし、スコープはグローバル)
console.log(i);
}
var i = 300; // グローバル(同一変数の再定義)
console.log(i);
function f() {
var i = 1000; // 関数内ローカル
console.log(i);
}
f();
実行してみると、以下の結果になりました。
200
300
1000
なるほど。先ほどの文章のとおり「関数内ローカルはサポートされている」ことがわかりました。
次にようやく let
を使った場合の動作確認に入ります。先ほどのコードを全て let
に変えて動かしていきます。
let i = 100; // グローバル
{
let i = 200; // ブロック内ローカル(ただし、スコープはグローバル)
console.log(i);
}
let i = 300; // グローバル(同一変数の再定義)
console.log(i);
function f() {
let i = 1000; // 関数内ローカル
console.log(i);
}
f();
これを実行すると、、
/Users/kuzukawa/WebstormProjects/study/study.js:6
let i = 300;
^
SyntaxError: Identifier 'i' has already been declared
at wrapSafe (internal/modules/cjs/loader.js:1001:16)
at Module._compile (internal/modules/cjs/loader.js:1049:27)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:1114:10)
at Module.load (internal/modules/cjs/loader.js:950:32)
at Function.Module._load (internal/modules/cjs/loader.js:790:12)
at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:75:12)
at internal/main/run_main_module.js:17:47
Process finished with exit code 1
おぉ!変数 i
はすでに宣言されてますよ!ってエラーが出ましたね。これですよー。次にブロックスコープがちゃんと効くのか、コードからグローバル変数の再定義部分を取り除いて再度実行してみましょう。
let i = 100; // グローバル
{
let i = 200; // ブロック内ローカル(ただし、スコープはグローバル)
console.log(i);
}
console.log(i);
function f() {
let i = 1000; // 関数内ローカル
console.log(i);
}
f();
実行してみると以下の結果になりました。
200
100
1000
おぉ!きちんとブロックスコープも効いています。なるほど、ようやくJavaScriptにおける var
と let
のスコープにおける挙動の違いがわかってきました。
ここまでやってようやくレキシカルスコープの意味がわかってきました。参考までですが、レキシカルスコープの対義語としてダイナミックスコープという概念がありますが、興味のある方は以下のWikipediaでも読むと雰囲気は掴めると思います。以下の記載の通り、JavaScriptを学ぶだけであればダイナミックスコープを学習する必要はないので、スコープの話はいったん、ここまでとします。
動的スコープの例としては古典LISPやEmacs Lisp、LOGO、Perl(「local」宣言した変数)、Bash(関数内で「local」宣言した変数)などがある。
ちなみに、コンパイラとかに興味のある方々はyaccとかlexとかいう言葉を聞いたことがあるかもしれません。このlexは lexical analyser generator(字句解析を行うプログラム) の略なんですよ。レキシカル、の意味もわかってきましたね。
関数の中に関数!?🤯
ふぅ、やっとレキシカルスコープの意味が分かりましたので、MDNに戻ります(先が長い・・)。あれ?サンプルコードをみてみると、以下のようなコードが記載されています。
function init() {
var name = 'Mozilla'; // name は、init が作成するローカル変数
function displayName() { // displayName() は内部に閉じた関数
alert(name); // 親関数で宣言された変数を使用
}
displayName();
}
init();
関数の中に関数がいますけどこれなんですか笑?JavaとかC言語とかだけをやって来た方々はここで軽くパニックになるかもしれませんが、JavaScriptとかRubyとかPythonだとこういうことができるんですよ。動きを確かめていくしかないですね。ちなみに、まだクロージャの説明の頭書きのところまでしか読み進められていません😭。いかに体系的に学ぶことが重要か、段々わかって来ましたね😇。雰囲気だけで書いてきたコードに土下座して謝りましょう。
まぁ、しょうがない。手を動かして理解を深めていきましょう。
function init() {
var name = 'Mozilla'; // name は、init が作成するローカル変数
function displayName() { // displayName() は内部に閉じた関数
alert(name); // 親関数で宣言された変数を使用
}
displayName();
}
init();
上記をTerminal上で動かしてみます。
/Users/kuzukawa/.asdf/shims/node /Users/kuzukawa/WebstormProjects/study/study.js
/Users/kuzukawa/WebstormProjects/study/study.js:5
alert(name); // 親関数で宣言された変数を使用
^
ReferenceError: alert is not defined
at displayName (/Users/kuzukawa/WebstormProjects/study/study.js:5:9)
at init (/Users/kuzukawa/WebstormProjects/study/study.js:7:5)
at Object.<anonymous> (/Users/kuzukawa/WebstormProjects/study/study.js:9:1)
at Module._compile (internal/modules/cjs/loader.js:1085:14)
at Object.Module._extensions..js (internal/modules/cjs/loader.js:1114:10)
at Module.load (internal/modules/cjs/loader.js:950:32)
at Function.Module._load (internal/modules/cjs/loader.js:790:12)
at Function.executeUserEntryPoint [as runMain] (internal/modules/run_main.js:75:12)
at internal/main/run_main_module.js:17:47
Process finished with exit code 1
はい?書いてあるものをそのままコピペしたのにエラー出ましたけど??あぁ、なるほど、なんかいっつも console.log()
を使ってたからとりあえず直して動かすか、、って、ちょっと待ってください。ここも深掘りしていきます。
alert()
ってなに?
MDNを読んでいきます。
Window.alert()
メソッドは、オプションの指定されたコンテンツと OK ボタンを持つ警告ダイアログを表示します。
alert()
は正式には Window.alert()
なんですね。じゃあこの Window
というのは何なんでしょう?またMDNです。yak shavingですね😭
Web ページに於いては、window オブジェクトはグローバルオブジェクト でもあります。これは次の事を意味します。
ズバッと書いてありますね。「Webページに於いては」と書いてあります。従ってTerminal上で動かすJavaScriptアプリケーションでは window
オブジェクトは当然使えない、と考えて良さそうです。更に、以下の記載もありました。
ブラウジングコンテキストに於いて
window
は最上位オブジェクトである為、window
オブジェクトのメンバへのアクセス時には "window." 接頭辞を省略する事が可能となっています。
なるほどー。だから alert()
と書くだけでもきちんとWebページ上では動作するんですね。以下のように、Webページ上ではいずれでもきちんと alert()
が動作することも確認できますね。
ブラウザ上で動作するJavaScriptとNode.js等の非ブラウザ環境で動作するJavaScriptでは利用可能な global object
がかなり異なります。Node.jsで利用可能な global object
については以下をご覧ください。
上記を見ていただけるとわかりますが window
オブジェクトは含まれていません。なので、上記 alert()
は動かない、ということですね。ちなみに、ブラウザ上で動作するJavaScriptでは以下のような感じです。(https://stackoverflow.com/questions/38746211/will-typeof-window-object-always-be-true より)
これでようやく、ナゾが解けました。コンソールアプリケーション上では alert()
は使えないわけですね!前述のNode.js ドキュメントに記載がありますが、 console
オブジェクトはGlobalです。なので、ここでは alert()
の代わりに console.log()
を使っていきましょう。ちなみに console
オブジェクトが持っている機能の全量は以下のドキュメントを参照してください。一度は軽くでも目を通しておくと、将来、役に立つことがあるかもしれませんよ笑。
function init() {
var name = 'Mozilla'; // name は、init が作成するローカル変数
function displayName() { // displayName() は内部に閉じた関数
console.log(name); // 親関数で宣言された変数を使用
}
displayName();
}
init();
これを実行すると、以下のように出力されました。
Mozilla
なるほど。処理は読めますね。 init()
を呼び出すと、その関数の中で displayName()
を呼び出し、ローカル変数 name
の内容が出力される、と。MDNに記載されている文章も読み解いていきます。
init()
関数はローカル変数name
を作成し、それから関数displayName()
を定義しています。displayName()
はinit()
の中で定義されている内部関数で、その関数本体の内部でしか利用できません。displayName()
自体はローカル変数を持っていませんが、外側の関数で宣言された変数にアクセスできるので、displayName()
では親関数init()
で宣言された変数name
を使用できます。しかし、displayName()
に同じローカル変数があればそれが使われます。
なるほどね。var
のスコープの話も勉強したので、上記は読み解くことができるようになったと思います。
コードを JSFiddle で実行して、
displayName()
関数のalert()
文が、親関数で定義されたname
変数の値を表示するのを確認してください。これはレキシカルスコープ、つまり関数がネストされている時にパーサーが変数名を解決する方法を示す例です。レキシカルという言葉の触れるところは、変数のスコープはソースコード内の位置によって決定され、ネストされた関数は外側のスコープで宣言された変数にアクセスすることができます。
これも読み解くことができますね。着実に力がついていることがわかります笑。
やっとClosure🏃♀️
やっとクロージャの節まで辿り着きました。今回は一つづつ拾っているのですごく時間がかかってしまっていますが、安心してください!こういう学習を通して、知識はあなたの体の中に確実に浸透していきます。将来、他のドキュメントを読んだときにはスラスラ読み解く力が備わっていることでしょう。
続けます。またコードが出てきたので、とりあえず動かしてみましょう。
function makeFunc() {
var name = 'Mozilla';
function displayName() {
console.log(name);
}
return displayName;
}
var myFunc = makeFunc();
myFunc();
動かしてみると、以下のように出力されました。
Mozilla
ん?さっきと全く同じじゃん😇何これ??と思うところですが、その下の文章を注意深く読んでいきます。
このコードを実行すると、前の
init()
の例と全く同じように文字列 "Mozilla" が JavaScript の警告ボックスに表示されます。前の例とは異なる、興味深い点は、内部関数displayName()
がそれが実行される前に外側の関数から返されているという事です。
あ?
このコードが動作するということは直感的に理解できないかもしれません。いくつかのプログラミング言語では、関数内部のローカル変数はその関数が実行されている間だけ存在します。一旦
makeFunc()
の実行が完了したら、name
変数はもう必要とされなくなると考えた方が筋は通っています。ただこのコードが期待したとおりに動くという事は、これは明らかに JavaScript にはあてはまりません。
んん?
この理由は、JavaScript の関数はクロージャとなるためです。クロージャは関数とその関数が作られた環境という 2 つのものの組み合わせです。この環境は、クロージャが作られた時点でスコープ内部にあったあらゆる変数によって構成されています。この場合、
myFunc
はmakeFunc
が実行された時に作られたdisplayName
関数のインスタンスへの参照です。displayName
のインスタンスはレキシカル環境への参照を保持し、そこにname
変数が存在します。このため、makeFunc
が実行された時に、name
変数が残っていて "Mozilla" がalert
に渡されます。
ゲホゲホ、、
なんか宇宙語なんですけど、、、どうすれば良いんだー😭正直、お手上げです。文章からはもう調べるキーワードも見つけられない感じです。ちょっとググるか。うーん、、色々出てきますがいまいちヒントになる物が出てこないです。しょうがないので今回だけ裏技で行きます笑。本当に手がかりが見つけられない場合、こういうところでつまづいてしまうんですよね。
第一級関数 (First-class Function)
プログラミング言語ではたまに「第一級(First-class)ほげほげ」という言葉が出てきます。Wikipediaも調べていきます。
第一級オブジェクト(ファーストクラスオブジェクト、first-class object)は、あるプログラミング言語において、たとえば生成、代入、演算、(引数・戻り値としての)受け渡しといったその言語における基本的な操作を制限なしに使用できる対象のことである。ここで「オブジェクト」とは広く対象物・客体を意味し、必ずしもオブジェクト指向プログラミングにおけるオブジェクトを意味しない。第一級オブジェクトは「第一級データ型に属す」という。
この言葉は1960年代にクリストファー・ストレイチーによって「functions as first-class citizens」という文脈で初めて使われた。
読みやすいですね。要するに、生成・代入・演算・受け渡しといった、通常変数に対して操作するようなことができるオブジェクトのことを「第一級オブジェクト」と呼ぶそうです。そうすると、第一級関数というのは、「関数に対して生成・代入・演算・受け渡し等ができる」という意味ですね。例えばJavaにおいては関数(メソッド)は変数に代入したり、戻り値・引数に使用することができないため、Javaは第一級関数の性質を満たしていない言語、と言えます。この辺りの言語ごとの違いも意識しておく必要がありそうです。しかし、登場したのが1960年台っていうのがスゴいですね笑。歴史を感じる。
上記MDNに記載されているサンプルコードを読んでみましょう。
関数を変数に代入
const foo = () => {
console.log("foobar");
}
foo(); // 変数を使用して呼び出し
// foobar
普段、アロー関数を自然に使っていると思いますが、これは正確には const foo
に無名関数を代入している、ということですね。呼び出すときは変数名に ()
をつけることで関数を呼び出すことが出来ると。なるほどー。
せっかくなのでこのMDNページをきちんと読んでいきます。
引数として関数を渡す
function sayHello() {
return "Hello, ";
}
function greeting(helloMessage, name) {
console.log(helloMessage() + name);
}
// `sayHello` を `greeting` 関数の引数として渡す
greeting(sayHello, "JavaScript!");
// Hello, JavaScript!
少しだけ複雑になってきました。 greeting()
の第一引数は変数ですが、ロジックの中で ()
をつけて関数として呼び出していますね。これも関数が第一級オブジェクトゆえに実現できるんですね😊。
メモ: 他の関数へ引数として渡される関数は、コールバック関数 と呼ばれます。 sayHello はコールバック関数です。
他の関数に引数として渡す関数のことをコールバック関数と呼ぶんですね。コールバック関数、ってよく聞くかもしれませんが、これでようやくコールバック関数の正しい定義を理解できました。
関数を返す
function sayHello() {
return () => {
console.log("Hello!");
}
}
読める!読めるぞぉ、、!! sayHello()
を呼ぶと無名関数が返却されるのですね。
この例では、関数を他の関数から返す必要があります。 - 関数を返すことができるのは、 JavaScript では関数を値として扱っているからです。
なるほど。JavaScriptでは関数を値として扱うんですね。第一級関数の意味がわかってきました。その下に以下のようなメモも記載されていました。
メモ: 関数を返す関数は高階関数と呼ばれます。
ありがとうございます!勉強になります。高階関数という言葉も聞いたことはあるかもしれませんが、すごく端的にわかりますね。しかも、ここまで手を動かして確認してきたので肌で理解できるようになったと思います。素晴らしい。
ちなみに、この高階関数ってどうやって呼び出すのでしょうか?もう少し動作を確認して行きましょう。
function sayHello() {
return () => {
console.log("Hello!");
}
}
sayHello();
これを動かしてみると以下のような結果になりました。
/Users/kuzukawa/.asdf/shims/node /Users/kuzukawa/WebstormProjects/study/study.js
Process finished with exit code 0
何にも出力されていないですね、、でももう分かりますね。JavaScriptでは関数を値として扱うんでしたよね。なので、 sayHello
は単純に値として関数を返却しているだけであり、返却される無名関数を呼び出したければ、以下のように書けば良いわけです。
function sayHello() {
return () => {
console.log("Hello!");
}
}
sayHello()();
変数を関数として呼び出すには ()
をつければ良いのでした。なので、 sayHello()();
と書くことで、返却された無名関数を即時に呼び出すことができるわけです。動作させると以下の結果が得られます。
/Users/kuzukawa/.asdf/shims/node /Users/kuzukawa/WebstormProjects/study/study.js
Hello!
Process finished with exit code 0
いいですね!もう少し読みやすく書きたければ以下のようにすれば良いでしょう。
function sayHello() {
return () => {
console.log("Hello!");
}
}
const sayHelloFunction = sayHello();
sayHelloFunction();
いい感じです。ここまでの学習で、予期せず第一級関数、高階関数の概念を学ぶことができました。
Closureもう一度🚀
先ほどのClosureのコードのサンプルに戻ります。
function makeFunc() {
var name = 'Mozilla';
function displayName() {
console.log(name);
}
return displayName;
}
var myFunc = makeFunc();
myFunc();
あれ?読めますね、、すごい。実行すると
Mozilla
と出力されるのもなんとなく理解できるようになっている気がします。文章も読み解いて行きます。
このコードを実行すると、前の
init()
の例と全く同じように文字列 "Mozilla" が JavaScript の警告ボックスに表示されます。前の例とは異なる、興味深い点は、内部関数displayName()
がそれが実行される前に外側の関数から返されているという事です。
ふむふむ。確かに内部関数 displayName()
は実行される前に外側の makeFunc()
から返却されていますね。理解できます。
このコードが動作するということは直感的に理解できないかもしれません。いくつかのプログラミング言語では、関数内部のローカル変数はその関数が実行されている間だけ存在します。一旦
makeFunc()
の実行が完了したら、name
変数はもう必要とされなくなると考えた方が筋は通っています。ただこのコードが期待したとおりに動くという事は、これは明らかに JavaScript にはあてはまりません。
確かに。関数内部のローカル変数は通常、その関数の実行が終わったら破棄されるイメージがありますよね。ただ、JavaScriptにおいてはそうではない、と言っているわけですね。
この理由は、JavaScript の関数はクロージャとなるためです。クロージャは関数とその関数が作られた環境という 2 つのものの組み合わせです。この環境は、クロージャが作られた時点でスコープ内部にあったあらゆる変数によって構成されています。この場合、
myFunc
はmakeFunc
が実行された時に作られたdisplayName
関数のインスタンスへの参照です。displayName
のインスタンスはレキシカル環境への参照を保持し、そこにname
変数が存在します。このため、makeFunc
が実行された時に、name
変数が残っていて "Mozilla" がalert
に渡されます。
読めるようになってる!!なるほど、「関数とそのレキシカル環境が保持される」というのがClosureを実現する概念なわけですね。
次のサンプルコードも読んでいきます。
function makeAdder(x) {
return function(y) {
return x + y;
};
}
var add5 = makeAdder(5);
var add10 = makeAdder(10);
console.log(add5(2)); // 7 と表示される
console.log(add10(2)); // 12 と表示される
これも今なら読み解けますね。 makeAdder()
の引数 x
はレキシカル環境として保持されるわけです。Closureを実現するためには、必ずしも関数内の変数として保持しておく必要もない、ということが理解できます。
一旦締めます🙇🏻♂️
ここまででまだ、Closureのページの冒頭10%くらいしか読み進められていませんが、最後までこの調子で記事を続けていくと膨大な量になってしまうので、一旦読み解くのはここまでにしておきます。ここまででも基本的なClosureの意味は理解できたと思います。ここから先は各自読む!笑。
5️⃣結局何が言いたいの?
技術力を上げていくための銀の弾丸はありません。地道に、一つづつわからないことをしっかり理解していく、ということが要求されているように思います。諦めず、じっくり勉強することがソフトウェアエンジニアには必要です。私は20年以上この業界で働いていますが正直、知らないことだらけです。真摯に技術に向き合うこと、そのためには正しい情報を正しく読み解く力が必要だと思うんですよね。そんな気持ちを記事にしてみた、ということです。
6️⃣あなたの学習を助けるツール
あなたの学習を助けるツール、それは「記録する」ことです。できる限りノートを取りましょう。ノートはどこで書いても良いです。私も何度もノートを取る場所を過去、変えています。最強のノートテイキングアプリはどれだー😭。って思って常にノートを取る場所を探しています笑。基本的には検索ができて、全体を見通せるものを使うのが良いと思っています。
参考までに私が使っているノートアプリをご紹介しますね。
思考をダンプする場所
1. Notes.app
私はMacユーザーなのですが、思考を出力する場所は今はNote.appです。スマートフォンとPCで同期することができるのと、シンプルなアプリなのでいつでも思いついたことを書けます。昔はGoogle Keepを使っていたこともありましたが、Note.appの方がなんとなくUIがシンプルで私は好みです。私はゼロ秒思考、ってやつをずっと実践しています。その時思いついたことを1分でばーっと書く!みたいなやつですね。これは本当にオススメです。思考をダンプすることによってメモリに空きが出来て精神的なゆとりと、思っていることややらなければいけないことの見通しが文字通り可視化できます。
情報を整理する場所
これがすごく悩ましいんですよね。今も一本に統一できていません。いろんなツールが一長一短で、なかなかコレだー!!っていう最強のアプリに出会えていない状況です。こんな感じ。
1. Evernote
昔はEvernoteを使っていましたが、なんか使いづらくてやめてしまいました(特にemacs指の私には最後まで書き心地が慣れませんでした)。それでも過去の膨大なNoteが今もEvernote上にあったりします笑。
2. Typora
生のMarkdownを書いていた時代もありましたが生産性が低いのでこれもやめました。(画像を貼ったりするのが面倒だし、ファイルがn個に分裂する)。あと、GoogleDriveにおいてもプレビューしてくれないんですよね。GoogleWorkspace上でMarkDownが本格的にサポートされるようになったらまたここに戻るかもしれません。
3. Notion
一瞬使っていましたが、これもなんか違う、、と思ってやめました笑。あっという間に無料で使える枠を使い切ってしまったので、それ以来使ってません笑。モダンなUIでいいとは思うんですが、私には合わなかったです。
4. Clickup
これも少しだけ使ってみましたが、エディタの機能が弱くてやめてしまいました。悪くないとは思うのですが、うーん。非エンジニアの方にはあっさりした味付けなので良いのかもしれません。
5. Dropbox paper
Dropboxいいんですが、書き始めるまでの距離がちょっとだけ遠いんですよね。なのでやめてしまいました。ストレージとして使うには良いのですが、メモを取る場所としてはちょっと私には会いませんでした。コラボレーション機能とか、優れているんですけどね。
6. Confluence
なんかまた最近、泣く子も黙る勢いを感じます。エンタープライズな香りがしますが、私は今はここに落ち着いています。すごく書きやすいしよくできていると思います。LaTexサポートもあるし、サードパーティとの連携もいっぱいあるし、ここで何かを書いていて困ったことが今のところないです。Atrassian様さすが✨
7. Roam Research
ずーっと気になっているんですがかなり強気な価格設定なので手が出ていないです笑。2023年はRoamCult(熱狂的なファンをそう呼ぶらしい笑)になっているかもしれません笑。
理想的なメモの取り方
メモはいつでもすぐに見返せる場所で取ります。調べたことはなるべく綺麗にまとめましょう。.txt
でメモを取ることだけはやめてください笑。後から見返すことが私だったらほぼ不可能です。最低でもMarkdown程度の見栄えが付けられる場所で取ることをお勧めします。綺麗にまとめられたノートは一冊の本のようになり、後から読み返しても惚れ惚れするような気持ちになれます。
また、ノート内から、調べた先のURLへのリンクをきちんと貼りましょう。個人的には同じキーワードで2回、ググったら負け、と思って日々ノートを取っています笑。
7️⃣終わりに
勉強していくのって、以下の図のようなイメージだと思っており、ピンポイントに情報を集めても体系的な学びにつながらないような気がします。「情報」ではなく「知識」を貯めることを意識して勉強するのがいいんじゃないかなぁ、というのが今回のまとめです。
最後まで読んでいただき、ありがとうございました😊
Happy hacking!