JavaScriptではスコープとクロージャーが重要ですが、私が最初にそれらを学習し始めたとき、それらは私を混乱させました。 以下は、これらの用語を理解するのに役立つこれらの用語の説明です。
スコープから始めましょう
範囲
JavaScriptのスコープにより、使用可能な変数が決まります。 スコープには、 グローバルとローカルの 2つのタイプがあります。
グローバルスコープ
変数がすべての関数または中括弧( {}
)の外側で宣言されている場合、 グローバルスコープで定義されていると見なされます 。
注:これは、WebブラウザーのJavaScriptにのみ当てはまります。 グローバル変数の宣言方法はNode.jsでは異なりますが、この記事ではNode.jsについては説明しません。
const globalVariable = 'some value';
グローバル変数が宣言されると、コード内のどこでも、関数内でもこの変数を使用できます。
const hello = 'Hello CSS-Tricks Reader!'; function sayHello () { console.log(hello); } console.log(hello); // 'Hello CSS-Tricks Reader!' sayHello(); // 'Hello CSS-Tricks Reader!'
グローバルスコープで変数を宣言できますが、そうすることはお勧めしません。 これは、2つ以上の変数に同じ名前が割り当てられている場合、名前が交差する可能性があるためです。 変数がconst
またはlet
を介して宣言されている場合、名前の共通部分が発生するたびに、エラーメッセージが表示されます。 この動作は望ましくありません。
// ! let thing = 'something'; let thing = 'something else'; // , thing
var
を使用して変数を宣言すると、宣言の後の2番目の変数が最初の変数を上書きします。 この動作も望ましくありません。 コードはデバッグが複雑になります。
// ! var thing = 'something'; var thing = 'something else'; // - console.log(thing); // 'something else'
したがって、グローバル変数ではなく、常にローカル変数を宣言する必要があります。
ローカルスコープ
コードの特定の部分でのみ使用される変数は、ローカルスコープに配置されていると見なされます。 このような変数はlocalと呼ばれます。
JavaScriptは、2種類のローカルスコープを識別します。
- 機能範囲
- およびブロックスコープ 。
まず、 関数のスコープを検討します
機能範囲
関数内で宣言された変数は、関数内でのみ使用可能です。 関数の外部のコードにはアクセスできません。
以下の例では、 hello
変数はsayHello
関数のスコープ内にあります。
function sayHello () { const hello = 'Hello CSS-Tricks Reader!'; console.log(hello); } sayHello(); // 'Hello CSS-Tricks Reader!' console.log(hello); // , hello
ブロックスコープ
const
またはlet
を介して中括弧{}
内で宣言された変数は、中括弧内でのみ使用できます。
次の例では、 hello
変数が中括弧のスコープ内にあることがわかります。
{ const hello = 'Hello CSS-Tricks Reader!'; console.log(hello); // 'Hello CSS-Tricks Reader!' } console.log(hello); // , hello
ブロックスコープは、 関数のスコープの特殊なケースです。 関数は中括弧で宣言されます(暗黙的な戻り値を持つ矢印関数を使用する場合を除く)。
可視性のアップグレード
「 関数宣言 」(約Transl。: 関数名(パラメーター){...}の形式の関数)として宣言された関数は、常に現在のスコープで上がります。 したがって、以下の2つの例は同等です。
// , sayHello(); function sayHello () { console.log('Hello CSS-Tricks Reader!'); } // , function sayHello () { console.log('Hello CSS-Tricks Reader!'); } sayHello();
関数が「 関数 式 」として宣言されている場合(transl。:var var f = function () {...}
の形式のvar f = function () {...}
)、そのような関数は現在のスコープで上昇しません。
sayHello(); // , sayHello const sayHello = function () { console.log(aFunction); }
これらの2つの選択肢があるため、関数を上げると混乱する可能性があるため、実際には推奨されません。 関数を使用する前に、必ず最初に関数を宣言してください。
関数は他の関数のスコープにアクセスできません
関数は、ある関数が別の関数で使用されている場合でも、別々に宣言されている場合、他の関数のスコープにアクセスできません。
以下の例では、 second
関数はfirstFunctionVariable
変数にアクセスできません。
function first () { const firstFunctionVariable = `I'm part of first`; } function second () { first(); console.log(firstFunctionVariable); // , firstFunctionVariable . }
ネストされたスコープ
関数が別の関数で宣言されると、内部関数は外部関数の変数にアクセスできます。 この動作は、 字句スコープの描写と呼ばれます。
同時に、外部関数は内部関数の変数にアクセスできません。
function outerFunction () { const outer = `I'm the outer function!`; function innerFunction() { const inner = `I'm the inner function!`; console.log(outer); // I'm the outer function! } console.log(inner); // , inner }
スコープがどのように機能するかを視覚化するために、一方向のミラーを想像できます。 反対側にいる人は見ることができますが、裏側(鏡側)にいる人はあなたを見ることができません。

一部の可視領域が他の領域に埋め込まれている場合、これは上記の動作原理を備えた一連のガラス表面として表すことができます。

スコープに関するすべてを理解していれば、クロージャーとは何かに対処する準備ができていると言えます。
短絡
別の関数内の関数を呼び出すたびに、クロージャーを作成します。 彼らは、内部関数はクロージャーであると言います。 通常、クロージャの結果は、外部関数のその後の変数が利用可能になることです。
function outerFunction () { const outer = `I see the outer variable!`; function innerFunction() { console.log(outer); } return innerFunction; } outerFunction()(); // I see the outer variable!
内部関数は外部関数の戻り値であるため、戻り値を関数の宣言と組み合わせることにより、コードをわずかに削減できます。
function outerFunction () { const outer = `I see the outer variable!`; return function innerFunction() { console.log(outer); } } outerFunction()(); // I see the outer variable!
クロージャのため、外部関数へのアクセスが表示されるため、通常は次の2つの目的で使用されます。
- 副作用の制御;
- プライベート変数を作成します。
クロージャーで副作用を制御する
関数呼び出し後に値を返すことに加えて、いくつかの追加のアクションが実行されると、副作用が現れます。 Ajaxリクエスト、タイマー、さらにはconsole.logなど、多くのことが副作用になります。
function (x) { console.log('A console.log is a side effect!'); }
クロージャーを使用して副作用を制御する場合、通常、クロージャーはコードを混乱させる可能性のある副作用(Ajaxリクエストやタイマーなど)に注意を払います。
明確にするために、例を考えてみましょう
友達の誕生日にケーキを作りたいとします。 書かれた関数には「ケーキが焼かれている」と1秒後に表示されるため、ケーキの調理には1秒かかります。
注:簡潔にするために、ES6の矢印関数を以下で使用します。
function makeCake() { setTimeout(_ => console.log(`Made a cake`), 1000); }
ご覧のとおり、このような関数にはタイマーの形で副作用があります。
次に、友達がケーキの味を選ぶ必要があるとしましょう。 これを行うには、 makeCake
関数に「テイストを追加」を追加します。
function makeCake(flavor) { setTimeout(_ => console.log(`Made a ${flavor} cake!`), 1000); }
関数を呼び出した後、ケーキはちょうど1秒後に焼かれます。
makeCake('banana'); // Made a banana cake!
問題は、たとえば、味の明確化の直後にケーキを焼く必要はないが、必要なときにケーキを後で焼く必要があることです。
この問題を解決するために、ケーキの味を保存するprepareCake
関数を作成できます。 次に、 prepareCake
を介してmakeCakeLater
クロージャをmakeCakeLater
ます。
この瞬間から、必要なときにいつでも返された関数を呼び出すことができ、ケーキはすぐに調理されます。
function prepareCake (flavor) { return function () { setTimeout(_ => console.log(`Made a ${flavor} cake!`), 1000); } } const makeCakeLater = prepareCake('banana'); // ... makeCakeLater(); // Made a banana cake!
したがって、クロージャーは副作用を減らすために使用されます-必要に応じて内部クロージャーをアクティブにする関数が呼び出されます。
クロージャーを持つプライベート変数
ご存知のように、関数内で作成された変数には外部からアクセスできません。 これらは使用できないため、 プライベート変数とも呼ばれます 。
ただし、このようなプライベート変数へのアクセスが必要な場合があり、これにはクロージャーが使用されます。
function secret (secretCode) { return { saySecretCode () { console.log(secretCode); } } } const theSecret = secret('CSS Tricks is amazing'); theSecret.saySecretCode(); // 'CSS Tricks is amazing'
上記の例では、 saySecretCode
は、元のシークレット関数の外側にsecretCode
を表示する唯一の関数(クロージャー)です。 このため、このような関数はprivilegedと呼ばれます。
DevToolsを使用したスコープのデバッグ
ChromeおよびFirefox開発ツール(DevTools)を使用すると、現在のスコープ内の変数を簡単にデバッグできます。 この機能を適用するには2つの方法があります。
最初の方法 : debugger
キーワードをコードに追加して、さらにデバッグするためにブラウザーでJavaScriptコードの実行を停止します。
以下はprepareCake
例です:
function prepareCake (flavor) { // debugger debugger return function () { setTimeout(_ => console.log(`Made a ${flavor} cake!`), 1000); } } const makeCakeLater = prepareCake('banana');
DevToolsを開き、Chromeの[ソース]タブ(またはFirefoxの[デバッガ]タブ)に移動すると、使用可能な変数を確認できます。
クロージャー内でデバッガーを移動することもできます。 今回はスコープ変数がどのように変化するかに注目してください。
function prepareCake (flavor) { return function () { // debugger debugger setTimeout(_ => console.log(`Made a ${flavor} cake!`), 1000); } } const makeCakeLater = prepareCake('banana');
2番目の方法:行番号をクリックして、ソース(またはデバッガー)タブのコードにブレークポイントを直接追加します。
結論:
- スコープとクロージャーは、見た目ほど理解しにくいものではありません。 一方向の鏡の原理を通して見ると、それらは非常に単純です。
- 関数で変数が宣言された後、変数へのアクセスは関数内でのみ可能です。 このような変数は、この関数のコンテキストで定義済みと呼ばれます。
- 別の関数内で内部関数を宣言すると、内部関数はクロージャーと呼ばれます。 この関数は、外部関数の変数へのアクセスを保持します。