How JavaScript closures work, in plain English
JavaScriptは広く採用されている言語で、シンプルなランディングページから本番用のフルスタックアプリケーションまで、あらゆるものを構築するのに使用できます。 JavaScriptやプログラミング全般が進化するにつれ、開発者はオブジェクト指向プログラミング(OOP)のパラダイムがほとんどのユースケースでは望ましくないことに気づくようになりました。
クロージャは関数型プログラミングの世界で広く議論されているトピックですが、その定義は技術的な専門用語を使って曖昧にされていることがよくあります。 ここでは、JavaScriptのクロージャがどのように機能するのか、平易な言葉で説明できるように最善を尽くします。
このチュートリアルを終える頃には、以下のことを理解しているはずです:
- クロージャの識別方法
- クロージャとは何か、実行コンテキストとコールスタックに関連してどのように動作するか
- クロージャの一般的な使用例
Understanding JavaScript closures
まず、クロージャがどのようなものかを示します。
このコードを自分で実行してみてください。 技術的には、makeCounter
increment
increment
makeCount
count
count
increment
makeCounter
が終了した後でも利用できます。
例えば、家があって、それを囲むように庭があるとします。 庭への扉を一度開けて閉めると、二度と開けられなくなり、庭に近づけなくなります。 お腹が空いたあなたは、幸いなことに庭にオレンジの木とリンゴの木があります。 あなたは小さな袋を持って、オレンジとりんごを1つずつ取って家に戻ります。
さて、家の中に入れば、またお腹が空いたときに、袋からオレンジやりんごを取り出して食べることができます。 この例では、小さな袋がクロージャです。 クロージャには、家の中にいて二度と外に出られなくても、庭にいたときに利用できたすべての変数や関数が入っています。
これをコードで見てみましょう。
fruits
makeFruitGarden
fruits
consumeFruit
fruit
pop()
fruits
の配列から最後の要素)が返されます。
Understanding lexical scope
クロージャを真に理解するためには、”scope “という用語に精通している必要があります。
以下の例では、myName
という変数のスコープを「グローバル スコープ」と呼びます。
// global scopeconst myName = "John Doe"function displayName() { // local/function scope console.log(myName);};displayName()
この概念は、var
const
let
がブロックスコープであることについて書かれた記事を読んだときに言及されているのを見たことがあるかもしれません。 ここで注意したいのは、JavaScriptでは、関数は必ず自分のスコープを作るということです。
注意して見ていると、myName
displayName
はクロージャの一部だと思っているかもしれません。 これは正しいですね。
JavaScriptにはさまざまな種類のスコープがありますが、クロージャに関しては、知っておくべき3つのスコープがあります。
- グローバルスコープは、すべての人が住むデフォルトのスコープです。 あなたの通りと考えてください
- 外部関数スコープは、関数を返す関数です。 それは独自のスコープを持っています。 あなたの庭だと思ってください
- 内側/ローカル関数スコープは、クロージャになる返された関数です。
では、いくつかの使用例を見てみましょう。
Common use cases for closures
Currying
関数キューリングは、関数型プログラミングの強力なコンセプトの1つです。
関数のキューリングは、関数を変換することと表現でき、次のように実行されます。
add(1, 2, 3)
add(1)(2)(3)
へ。
function add(a) { return function(b) { return function(c) { return a + b + c; }; };};add(1)(2)(3) // returns 6
add
関数は1つの引数を取り、次々と入れ子になった2つの関数を返します。 curryingの目的は、たくさんの引数を取り、最終的に1つの値を得ることです。
高階の関数
高階の関数の目的は、関数を引数として取り、結果を返すことです。 map
reduce
のような配列メソッドは高階関数の例です。
const arrayOfNumbers = ;const displayNumber = (num) => { console.log(num);}arrayOfNumbers.forEach(displayNumber)
ここでのArray.prototype.forEach
displayNumber
arrayOfNumbers
の各要素に対して実行しています。 VueやReactなどのUIフレームワークを使ったことがある方は、高次関数と本質的に同じものである高次コンポーネントをご存知かもしれません。
では、高次関数とcurryingの違いは何でしょうか?
DOM 要素マネージャ
これは、DOM 要素のプロパティを取得したり設定したりするためによく使われるデザイン パターンです。
makeStyleManager
element
currentStyles
の変数と並んでクロージャの一部である、2つの関数にアクセスできるオブジェクトを返します。
結論
JavaScriptのクロージャは、専門的な経験を積んだ開発者でも理解するのが難しい場合があります。
コードベースでクロージャが使用されていて、それが奇妙に見えたり、意味をなさなかったりする場合、クロージャを識別できるようになっているはずです。
LogRocket.com
コードのデバッグは、常に面倒な作業です。
LogRocketでは、新しくユニークな方法でこれらのエラーを理解することができます。
LogRocket は、コンソール ログ、ページ ロード タイム、スタック トレース、ヘッダーとボディを持つ低速のネットワーク リクエスト/レスポンス、ブラウザ メタデータ、およびカスタム ログを記録します。 あなたの JavaScript コードの影響を理解することは、決して簡単ではありません!
無料でお試しください。
Further reading
- 「I never understood JavaScript closures」 – クロージャがどのように機能するかを理解するための、詳細で技術的なガイドです。
ExecutionContext
、コールスタック、辞書環境などに関してクロージャを理解したい人に最適です。 - MDN reference for JavaScript closures – 手早く記憶を更新する必要がある場合のための、クロージャのクイックリファレンスガイド (よくある落とし穴を含む)
- “Master the JavaScript Interview: クロージャとは何ですか?” – 関数型プログラミングの概念を理解している開発者に最適です。