プログラミング初心者のための入門ブログエンジニアが未来を切り開く。JavaScriptクロージャーの仕組みとスコープ:関数型プログラミングにおける重要な概念の解説この広告は、90日以上更新していないブログに表示しています。React基礎クロージャー(Closure)の概念は関数型プログラミングにおいて重要であり、JavaScriptの面接でよく質問される事もあります。どこでも使用されていますが、クロージャーは把握するのがとても困難でもあります。クロージャーを学ぶまえに(スコープ)および(レキシカルスコープ)の基本を学ぶ必要があります。前提としてJSの変数および関数に精通しているかご確認下さい。この記事では、JavaScriptクロージャーの仕組みおよびJavaScriptスコープについて解説します。最後にスコープJavaScriptのスコープ(Scope)は、変数や関数がどこからアクセス可能かを定義する仕組みです。変数や関数がどこで宣言されたかによって、その変数や関数がどの範囲で有効かが決まります。変数のアクセス可能性は、スコープによって管理されます。そのスコープ内で定義された変数には自由にアクセスすることができます。そのスコープの外では、その変数にアクセスすることができません。JavaScriptでは、関数やコードブロックによってスコープが作成されます。スコープは、グローバルスコープとローカルスコープの2つのスコープがあります。グローバルスコーププログラムのどこからでもアクセスできる変数は、グローバルスコープに存在すると言われます。varの3つのキーワードのいずれかを使用して定義できます。CodePen.ここで、scope変数名はmainFunc関数内でも外部でもアクセスできます。このscopeには、コード内のどこからでもアクセスできます。したがって、scope変数はグローバルスコープに存在します。ローカルスコープ一方、ローカルスコープは、関数内で宣言する変数はすべて、ローカルスコープを持つと言われます。関数内でローカル変数にアクセスできます。関数内で定義された変数に外部または別の関数からアクセスしようとすると、エラーがスローされます。ローカルスコープの例を以下に示します。6)が導入される前は、JavaScriptにはグローバルスコープとローカルスコープの2種類のスコープしかありませんでした。letおよびconstキーワードの導入により、JavaScriptに新しいタイプのScopeが追加されました。それがブロックスコープです。これは、変数がブロックの中で宣言された場合に作られます。}で囲まれたコードブロックのことを指します。内で宣言された変数に、ブロックの外部からアクセスすることはできません。defined}上記では、変数xはif文のブロック内で宣言されており、そのブロック内でのみアクセスが可能です。ローカルスコープと同じでは?と疑問に思われましたか?あやふやにせず、明確に理解しましょう。ローカルスコープとブロックスコープは、本質的には同じです。ローカルスコープは、関数内で変数を定義し、その関数の内部でのみ、その変数にアクセスできるようにする方法として使用します。}で囲まれたブロック内で変数を定義し、そのブロックの内部でのみその変数にアクセスできるようにする方法です。ローカルスコープは、古くから存在している概念です。ブロックスコープは、if文やforループなどのブロックスコープ内で変数を宣言することができますが、ローカルスコープは主に関数内で変数を宣言するために使用されます。本質的には、ローカルスコープはブロックスコープでもあります。ブロックスコープはより細かく範囲を制限するための機能として、ローカルスコープと同様の機能を持っています。varキーワードでは機能しません。そのためにletまたはconstキーワードを使用できます。関数スコープ関数スコープは、関数内で宣言された変数が関数の内部でのみアクセス可能であることを意味します。関数スコープ内で宣言された変数は、関数の外部からはアクセスできません。こちらも、関数スコープとローカルスコープは似ている概念ですが、わずかに異なる点があります。関数スコープが変数の利用可能性にどのような影響を与えるか見てみましょう。以下の変数は、関数num()で作られたスコープに属しています。definedcount変数は、num()の範囲内で自由にアクセスすることができます。num()のスコープ外では、countはアクセスできません。definedを投げます。関数やコードブロックの内部で変数を定義した場合、この変数はその関数やコードブロックの内部でのみ使用することができます。ローカルスコープと関数スコープの違いは、ブロックと関数のスコープの違いにあります。先述したように、ローカルスコープはブロックスコープであり、関数スコープは関数内のスコープに限定されます。変数がどの範囲で有効かを制御する方法に違いがあるという事です。}で囲まれたブロック内でのみ有効であり、関数スコープは、関数内でのみ有効であるという違いです。スコープが変数を分離する、という性質がございます。異なるスコープが、同じ名前の変数を持つことができるので、これは素晴らしいことです。異なるスコープで共通の変数名(count、index、current、valueなど)を衝突することなく再利用することが可能ということです。下記でのfoo()とnum()の関数スコープは、それぞれ独自の、同じ名前のcount変数を持ちます。CodePen.foo()とnum()の関数スコープからのcount変数が衝突しません。関数スコープ内で同じ変数名が宣言された場合、その変数は同じ名前でも別の変数として扱われます。このように、関数スコープは変数を分離します。関数スコープが変数の分離にどのように影響を与えるかを理解することは、変数名の重複や意図しない変更を防止するために非常に重要となっています。このような原則を守ることで、コードの保守性を高めることができます。スコープチェーン関数が別の関数内にネストできるように、スコープは互いにネストできます。あるスコープを別のスコープにネストすることです。上記のname変数名はグローバル変数です。outerFun()のスコープ内で再宣言されると、同じ名前の変数で新しい値を持つ、新しいローカル変数が作成されます。outerFun()のスコープ内では、ローカル変数名が使用されます。変数が初期化されるたびに、自分のスコープでその変数を探します。ローカルスコープで変数が見つからなければ、親スコープで検索します。親スコープで見つからなければ、祖父母スコープを探し、グローバルスコープに到達するまで続けます。親スコープ(外部スコープ)の変数は、子スコープ(内側スコープ)の内部でアクセスできますが、その逆はできません。・外側のスコープの変数は、内側のスコープ内でアクセスできます。レキシカルスコープについて説明致しますが注意点がございます。関数スコープとレキシカルスコープは非常に似ており混同してしまう可能性がありますが、微妙に異なる概念です。そこを念頭に置いて学習に進んで下さい。Scope)または静的・スコープと呼ばれるスコープ機能を実装しています。JavaScriptにおけるレキシカルスコープとは、変数の有効範囲が定義される方法の一つで、コードの記述時点でスコープが決まることを意味します。レキシカルスコープとは、変数が存在する領域が、変数が定義または作成された場所によって決定されることを意味しているという事です。変数が、そこに呼び出された場所ではなく、作成されたスコープでのみ使用可能です。簡単に言えば、レキシカルスコープとは、内側のスコープ内で、外側のスコープの変数を使用できることです。CodePen.上記の例では、letキーワードを使用してmyInnerVarを宣言し、myFun関数内で使用しています。これにより、myInnerVarはmyFun内のブロックスコープ内でのみ有効になります。defined"というエラーが発生します。これで、レキシカルスコープは変数のスコープを決定する仕組みだという事が理解できたかと思います。レキシカルスコープとは関数がどこに宣言されたかに基づいて、その関数が参照できる変数の範囲が決まります。関数スコープでは、関数内で定義された変数がその関数内でのみアクセス可能であることを指します。関数の外部で、同じ名前の変数を定義しても、関数内での変数とは別の変数として扱われます。そこの違いとなります。letキーワードを使用すると、変数のスコープをより細かく制御できます。ブロックスコープの変数を使用することで、コードの読みやすさを向上させることができます。JSのクロージャーとは?JavaScriptにおけるクロージャー(Closure)と閉鎖は、実際には同じ概念を指しています。クロージャーは、外部スコープにある変数にアクセスできるようにすることで、その変数を保護する機能を提供することができます。その状態を「閉じられた状態」と表現します。そのため(閉鎖)または(クロージャー)と呼ばれます。したがって、クロージャーと閉鎖は同じものを指し、どちらもJavaScriptにおいて、外部スコープにある変数を内部スコープで保護するために使用されます。クロージャーは、レキシカルスコープの外で実行された場合でも、そのレキシカルスコープにアクセスする関数となります。外側の関数が実行を終了しても、その内側の関数が変数や関数を参照し続けるため、外側の関数内で定義された変数や関数が保持された状態を維持します。この機能により、変数や関数を外部から保護したり、プライベート変数を定義したりすることが可能となります。重要な事は、実行される場所に関係なく外部関数の実行後でも可能となります。CodePen.上記のように、クロージャーとは、関数が常にその周囲にアクセスできるという事です。外側の関数を呼び出すと、counter変数が(0)に設定されます。したがって、外側の関数が呼び出された後は、counter変数が使用できないと予想される場合があります。変数は外側の関数のスコープ内にあります。counter変数にアクセスできます。コードの一部が内部関数を呼び出すことができる限り、変数のcounterを記憶することができます。クロージャにより、関数はそのスコープ内の変数を記憶でき、スコープを使用すると関数とその外部にアクセスできるデータを組み合わせることができます。簡単に説明致しましょう。前述した通り、クロージャーとは、関数が外側のスコープの変数にアクセスできることです。関数の中に関数を作成してみましょう。outer()関数がinner()関数を返しています。outer()関数を呼び出し、その結果を定数に代入したのちに、定数に格納されている関数を呼び出しましょう。CodePen.上記で確認してみると、定数の(a)がinner()関数の外側で定義されていることがわかります。)の痕跡はなくなります。b)をログに記録しようとしているため、エラーが表示されると想定されます。)がinner()関数の外で指定されています。ご覧のとおり、Greetings()は明らかに(a)と(b)の両方にアクセス可能となっています。他の例でも見てみましょう。innerFunc();}outerFunc();innerFunc()スコープ内では、レキシカルスコープからouterVar変数にアクセスされます。innerFunc()の呼び出しは、そのレキシカルスコープ(outerFunc()のスコープ)内で行われることに注意してください。innerFunc()を、そのレキシカルの範囲外の関数exec()で呼び出すという、コード変更をしてみましょう。myInnerFunc();}exec();上記のように、innerFunc()は、そのレキシカル・スコープの外で、exec()関数のスコープ内で実行されます。重要な事は、innerFunc()は、レキシカルスコープ外で実行されても、そのレキシカルスコープからouterVarにアクセスすることができます。innerFunc()は、outerVar変数をレキシカル・スコープから閉じるため、これはクロージャーです。クロージャーは、内部関数が周囲のコードブロックがなくなった後でも、その周囲のコードブロック内の変数にアクセスできることを意味している事が理解できたかと思います。プライベート変数クロージャーの優れている点が、モジュールのカプセル化を実装する機能です。スタックのデータ構造を実装するタスクがあるとします。本来であれば、アイテムをスタックにpush(プッシュ)またはpop(ポップ)することしかできません。通常のJavaScript配列は、array.push()メソッドとarray.pop()メソッドの両方を提供しています。新しい要素をスタックの一番上にpushしていき、popしてスタックに挿入された要素を削除できます。これは一般的なデータ構造の1つであるStuckです。以下の例をご確認下さい。上記は問題がございます。0を使用してスタックを消去したり、スタック上で通常許可されない操作を簡単に行うことができてしまいます。0)でも削除が許可されます。pushかpopの操作しかできないようにする事が可能です。それが、クロージャーが役に立つ所となります。2)上記のように、push()とpop()メソッドは、items配列にアクセスするクロージャとして機能しています。items変数は、Stack()関数のローカルスコープ内で定義されているため、外部から直接アクセスすることはできません。これにより、items変数はprivate変数となります。push()メソッドは、外部から渡された要素をitems配列に追加します。pop()メソッドは、items配列の最後の要素を取り出して返します。このように、items配列に対する変更やアクセスは、Stack()関数内で定義されたpush()とpop()メソッドを介して行われます。よって、スタックから要素が取り出され、結果が表示されます。この場合、スタックの最後の要素である1が取り出されるため、1が出力されます。0)によって、スタックの長さが0に設定されますが、スタックの内容自体は影響を受けません。したがって、次のconsole.log(stack.pop())では、スタックは空の状態ですので、何も取り出すことができず、undefinedが出力されます。これにより、スタックの内部のデータを保護し、外部からの不正な操作を防ぐことができます。このアプローチはプライベート変数による、カプセル化を実現するためにクロージャを使用できる素晴らしい例となっています。プライベートメソッドJavaScriptでは、JavaやC++などの他の言語のように、すぐに使用できるプライベートメソッドを作成する方法はありません。それらが属するクラスであり、外部から直接アクセスおよび変更することはできません。クロージャーを使用してプライベート変数とセッターおよびゲッター関数を使用して基本的なファクトリ関数を作成できます。下記は、クラスやコンストラクターとは異なる点に注意して下さい。オブジェクトを返す関数にすぎません。Privateを作成しています。nameに直接アクセスしようとすると、関数スコープ内にあるため、これは外部からアクセスできずにundefinedが返されます。そこで、クロージャーを使えば、プライベート変数を実行させることができます。JavaScriptのクロージャーを使用することで、オブジェクト指向プログラミングの概念である、カプセル化と相性が良く、コードの保守性や再利用性を向上させることができます。イベントハンドラーそれでは、クロージャーを使用して、ボタンをクリックした回数をカウントする例を見ていきましょう。クロージャーを使用して、イベントハンドラー内で変数を保持し、その値を更新することが可能です。CodePen.ログとブラウザ上、両方で確認できるようになっております。addClickHandler()関数内でcount変数を定義し、その後、イベントハンドラー内で使用しています。count変数は、クロージャー内で定義されているため、イベントハンドラーが実行されるたびに、その値を保持しています。letキーワードを使用しているため、count変数はブロックスコープを持ちます。addClickHandler()関数の外部からはアクセスできません。このように、クロージャーを使用することで、イベントハンドラー内で変数を保持し、その値を更新することができます。下記の場合でも、ボタンがクリックされると、DOMコードの中のどこかでhandleClick()関数が実行されます。CodePen.定義した場所から遠く離れた場所で実行されています。クロージャーであるhandleClick()は、レキシカルスコープからcountを記憶し、クリックが発生するとそれを更新します。言えば、myTextも記憶されます。レキシカルスコープとクロージャーの違いレキシカルスコープとクロージャーの明確な違いを整理しときましょう。JavaScriptのクロージャーとレキシカルスコープは密接に関連しておりますが、異なる概念です。レキシカルスコープは、コードの静的な構造に基づいて、どの変数がどのスコープに属するかを決定する仕組みです。関数がどこに宣言されたかに基づいて、その関数が参照できる変数の範囲が決まります。この仕組みによって、変数の名前が衝突することを避けたり、関数の実行中に正しく変数を参照することができます。クロージャーは、関数が実行される環境(スコープ)を記憶して、その環境にある変数を参照できるようにする仕組みです。クロージャーを使うことで、関数が外部のスコープにある変数を参照したり、変更したりできます。これにより、関数が実行されるたびに新しい環境を作成することができます。レキシカルスコープは変数のスコープを決定する仕組みであり、クロージャーは関数が外部のスコープにある変数を参照できるようにする仕組みです。クロージャーはレキシカルスコープに依存しているため、両者はかなり密接に関連していることに注意してください。クロージャーの理解が分かった所で、JavaScriptでのクロージャーの利点は何でしょうか?JSでのクロージャの利点と欠点クロージャーには、JavaScriptプログラミングにおいていくつかの利点があります。その主な利点の1つは、プライベート変数とデータのカプセル化を作成できることです。これは、クロージャー内の変数と関数がクロージャーの外部からアクセスできないことを意味し、プログラムの他の部分によって変更またはアクセスされるのを防ぎます。クロージャーのもう1つの利点は、コードの重複を減らし、再利用可能なコードを作成するために使用できることです。クロージャー内に機能をカプセル化することで、複数の場所でコードを繰り返すことを避け、よりモジュール化された保守しやすいコードを作成できます。メモリ効率の向上にもなります。クロージャーは、関数の外部から変数にアクセスするために、変数をグローバルスコープに定義する必要がありません。そのため、関数内で必要な変数だけを定義することができ、メモリ効率が向上することがあります。クロージャーにはいくつかの欠点もあります。主な欠点の1つは、デバッグの複雑さです。クロージャーはスコープ外の変数にアクセスできるため、コード内のバグやエラーを追跡するのが難しい場合があります。クロージャーをデバッグするためのヒントは、console.logの使用と、デバッガーを使用したコードのステップ実行が含まれます。バグやエラーのリスクを最小限に抑えるために、クロージャーをできるだけシンプルで焦点を絞ったものにすることも重要です。クロージャーを誤用すると、予期しない問題が発生することがあります。そのため、クロージャーを正しく理解し、適切に使用することが重要となります。最後にJavaScriptプログラミングでクロージャーを使用する場合は、いくつかのベストプラクティスに従って、コードを保守しやすく理解しやすいものにすることが重要です。ベストプラクティスの1つは、クロージャーをできるだけ小さくし、焦点を絞ることです。これは、クロージャー内に機能を詰め込みすぎないようにし、その代わりとして、より小さく、より焦点を絞ったクロージャーに分割する必要があります。1つのベストプラクティスは、クロージャーの外部で変数を変更しないようにすることです。これによって、コードで予期しない動作やバグが発生する可能性があります。変数をパラメーターとしてクロージャーに渡し、必要に応じてクロージャーから値を返します。クロージャーを使用するためのベストプラクティスには、プライベート変数、カプセル化、およびコードの再利用性のためにクロージャーを使用することが含まれます。不必要なクロージャーを作成しないようにし、メモリ使用量を適切に管理することも重要です。クロージャーは、高度なJavaScriptプログラミングを行う際に理解する必要のある概念です。これら、ベストプラクティスに従い、クロージャーを慎重に計画することで、JavaScriptの強力な機能を最大限に活用できるようになるはずです。本日は以上となります。最後まで記事を読んで頂きありがとうございます。この記事が気に入ったらブックマークし、他の方にも共有してください。17:56ご訪問いただきありがとうございます。私はdeve.Kと申します。現役のReact.jsプログラマーでありWebアプリ開発に携わっています。当ブログでは、ITやプログラミングの学習を目的としたブログを運営しています。専門分野はJS/TS/Node.js/React/Next.jsです。質問や相談、または学びたい分野のリクエストなどがありましたら、お気軽にお問い合わせください。このブログでは、ITやプログラミングに関する情報を分かりやすく、かつ実践的な内容でお届けしています。皆様にとって有益な情報を提供し、より良い学習体験を得ていただけるよう、精一杯の努力をしてまいります。今後ともどうぞよろしくお願いいたします。[deve.K].はてなブログをはじめよう!dev04Kさんは、はてなブログを使っています。ブログを報告する引用をストックしましたストック一覧を見る閉じる引用するにはまずログインしてくださいログイン閉じる引用をストックできませんでした。再度お試しください閉じる限定公開記事のため引用できません。