JavaScriptのクロージャを理解するのに役立つ簡単なガイド

JavaScriptのクロージャは、多くの人が頭を悩ませている概念の1つです。次の記事では、クロージャとは何かを明確な用語で説明し、簡単なコード例を使用して重要な点を説明します。

閉鎖とは何ですか?

クロージャーはJavaScriptの機能であり、内部関数は外部(囲んでいる)関数の変数(スコープチェーン)にアクセスできます。

クロージャーには3つのスコープチェーンがあります。

  • 独自のスコープ(中括弧の間に定義された変数)にアクセスできます。
  • 外部関数の変数にアクセスできます
  • グローバル変数にアクセスできます

初心者にとって、この定義は単なる専門用語のように思えるかもしれません!

しかし、本当に閉鎖とは何ですか?

シンプルな閉鎖

JavaScriptの簡単なクロージャーの例を見てみましょう。

関数outer(){
   var b = 10;
   関数inner(){
        
         var a = 20;
         console.log(a + b);
    }
   戻り値;
}

ここには2つの関数があります。

  • 変数bを持ち、内部関数を返す外部関数outer
  • aという名前の変数を持ち、その関数本体内で外部変数bにアクセスする内部関数inner

変数bのスコープは外部関数に限定され、変数aのスコープは内部関数に限定されます。

次に、outer()関数を呼び出し、outer()関数の結果を変数Xに格納します。次に、outer()関数をもう一度呼び出して、変数Yに格納します。

関数outer(){
   var b = 10;
   関数inner(){
        
         var a = 20;
         console.log(a + b);
    }
   戻り値;
}
var X = outer(); // outer()が最初に呼び出された
var Y = outer(); // 2回目に呼び出されるouter()

outer()関数が最初に呼び出されたときに何が起こるかを段階的に見てみましょう:

  1. 変数bが作成され、そのスコープはouter()関数に制限され、その値は10に設定されます。
  2. 次の行は関数宣言なので、何も実行しません。
  3. 最後の行で、return innerはinnerという変数を探し、この変数innerが実際に関数であることを検出し、関数innerの本体全体を返します。
    [returnステートメントは内部関数を実行しないことに注意してください。関数は()が続く場合にのみ実行されますが、returnステートメントは関数の本体全体を返します。]
  4. returnステートメントによって返されるコンテンツはXに格納されます。
    したがって、Xは以下を保存します。
     関数inner(){
     var a = 20;
    console.log(a + b);
    }
  5. 関数outer()は実行を終了し、outer()のスコープ内のすべての変数は存在しなくなりました。

この最後の部分は理解することが重要です。関数の実行が完了すると、関数スコープ内で定義された変数は存在しなくなります。

関数内で定義された変数の寿命は、関数実行の寿命です。

これが意味することは、console.log(a + b)では、変数bはouter()関数の実行中にのみ存在するということです。外部関数の実行が終了すると、変数bは存在しなくなります。

関数が2回目に実行されると、関数の変数が再度作成され、関数の実行が完了するまで有効になります。

したがって、outer()が2回目に呼び出された場合:

  1. 新しい変数bが作成され、そのスコープはouter()関数に制限され、その値は10に設定されます。
  2. 次の行は関数宣言なので、何も実行しません。
  3. return innerは、関数innerの本体全体を返します。
  4. returnステートメントによって返されるコンテンツはYに格納されます。
  5. 関数outer()は実行を終了し、outer()のスコープ内のすべての変数は存在しなくなりました。

ここで重要な点は、outer()関数が2回目に呼び出されると、変数bが新たに作成されることです。また、outer()関数が2回目の実行を終了すると、この新しい変数bは再び存在しなくなります。

これが実現する最も重要なポイントです。関数内の変数は、関数の実行中にのみ存在し、関数の実行が完了すると存在しなくなります。

ここで、コード例に戻ってXとYを見てみましょう。実行時のouter()関数は関数を返すため、変数XとYは関数です。

これは、JavaScriptコードに次を追加することで簡単に確認できます。

console.log(typeof(X)); // Xは関数型です
console.log(typeof(Y)); // Yは関数型です

変数XとYは関数なので、実行できます。 JavaScriptでは、X()やY()などの関数名の後に()を追加することにより、関数を実行できます。

関数outer(){
var b = 10;
   関数inner(){
        
         var a = 20;
         console.log(a + b);
    }
   戻り値;
}
var X = outer();
var Y = outer();
// outer()関数の実行の終了
バツ(); // X()が最初に呼び出された
バツ(); // 2回目に呼び出されるX()
バツ(); // 3回目に呼び出されるX()
Y(); // Y()が最初に呼び出された

X()およびY()を実行すると、本質的に内部関数が実行されます。

X()が最初に実行されたときに何が起こるかを段階的に調べてみましょう。

  1. 変数aが作成され、その値は20に設定されます。
  2. JavaScriptはa + bを実行しようとします。ここからがおもしろいところです。 JavaScriptは、作成したばかりのが存在することを知っています。ただし、変数bはもう存在しません。 bは外部関数の一部であるため、bはouter()関数の実行中にのみ存在します。 outer()関数はX()を呼び出すかなり前に実行を終了したため、外部関数のスコープ内の変数は存在しなくなり、変数bはもう存在しません。

JavaScriptはこれをどのように処理しますか?

閉鎖

内部関数は、JavaScriptのクロージャーにより、囲んでいる関数の変数にアクセスできます。つまり、内側の関数は、外側の関数が実行された時点で外側の関数のスコープチェーンを保持しているため、外側の関数の変数にアクセスできます。

この例では、outer()関数が実行されたときに、内部関数はb = 10の値を保持し、それを保持(閉鎖)し続けました。

スコープチェーンを参照し、外側の関数が実行された時点でbの値をクロージャーで囲んでいたため、スコープチェーン内に変数bの値があることに気付きます。

したがって、JavaScriptはa = 20およびb = 10を認識し、a + bを計算できます。

これを確認するには、上記の例に次のコード行を追加します。

関数outer(){
var b = 10;
   関数inner(){
        
         var a = 20;
         console.log(a + b);
    }
   戻り値;
}
var X = outer();
console.dir(X); // console.log()の代わりにconsole.dir()を使用します

Google ChromeでInspect要素を開き、コンソールに移動します。要素を展開して、実際にClosure要素を確認できます(下の3行目から最終行に表示)。 outer()関数の実行が完了した後でも、b = 10の値はClosureに保持されることに注意してください。

変数b = 10はClosure、JavascriptのClosuresで保持されます

ここで、最初に見たクロージャーの定義を再検討し、それが今より意味があるかどうかを見てみましょう。

したがって、内部関数には3つのスコープチェーンがあります。

  • 独自のスコープへのアクセス—変数a
  • 外側の関数の変数へのアクセス—変数b、囲まれている
  • 定義されている可能性のあるグローバル変数へのアクセス

アクションのクロージャー

クロージャーのポイントを特定するために、次の3行のコードを追加して例を強化します。

関数outer(){
var b = 10;
var c = 100;
   関数inner(){
        
         var a = 20;
         console.log( "a =" + a + "b =" + b);
         a ++;
         b ++;
    }
   戻り値;
}
var X = outer(); // outer()が最初に呼び出されます
var Y = outer(); // outer()が2回目に呼び出されます
// outer()関数の実行の終了
バツ(); // X()が最初に呼び出された
バツ(); // 2回目に呼び出されるX()
バツ(); // 3回目に呼び出されるX()
Y(); // Y()が最初に呼び出された

このコードを実行すると、console.logに次の出力が表示されます。

a = 20 b = 10
a = 20 b = 11
a = 20 b = 12
a = 20 b = 10

このコードを順を追って調べて、実際に何が起こっているのかを確認し、実際にクロージャーを確認しましょう!

var X = outer(); // outer()が最初に呼び出されます

関数outer()は最初に呼び出されます。次の手順が実行されます。

  1. 変数bが作成され、10に設定されます
    変数cが作成され、100に設定されます
    独自の参照のために、これをb(first_time)およびc(first_time)と呼びましょう。
  2. 内部関数が返され、Xに割り当てられます
    この時点で、変数bは、b = 10のクロージャーとして内部関数スコープチェーン内に囲まれています。これは、innerが変数bを使用するためです。
  3. 外部関数は実行を完了し、その変数はすべて存在しなくなります。変数bはinner内のクロージャーとして存在しますが、変数cはもはや存在しません。
var Y = outer(); // outer()が2回目に呼び出されます
  1. 変数bが新たに作成され、10に設定されます
    変数cが新たに作成されます。
    変数bとcが存在しなくなる前にouter()が一度実行されたとしても、関数の実行が完了すると、それらは真新しい変数として作成されることに注意してください。
    参照用にこれらのb(second_time)とc(second_time)を呼び出しましょう。
  2. 内部関数が返され、Yに割り当てられます
    この時点で、変数bはb(second_time)= 10のクロージャーとして内部関数スコープチェーン内に囲まれています。これは、内部が変数bを使用するためです。
  3. 外部関数は実行を完了し、その変数はすべて存在しなくなります。
    変数c(second_time)はもう存在しませんが、変数b(second_time)はinner内のクロージャーとして存在します。

次に、次のコード行が実行されたときに何が起こるかを見てみましょう。

バツ(); // X()が最初に呼び出された
バツ(); // 2回目に呼び出されるX()
バツ(); // 3回目に呼び出されるX()
Y(); // Y()が最初に呼び出された

X()が初めて呼び出されたとき、

  1. 変数aが作成され、20に設定されます
  2. a = 20の値、bの値はクロージャー値からのものです。 b(first_time)、したがってb = 10
  3. 変数aとbは1ずつ増加します
  4. X()は実行を完了し、その内部変数(変数a)はすべて存在しなくなります。
    ただし、b(first_time)はクロージャーとして保存されているため、b(first_time)は存在し続けます。

X()が2回目に呼び出されると、

  1. 変数aが新たに作成され、20に設定されます
     X()が最初に実行を完了したときに存在しなくなったため、変数aの以前の値はもはや存在しません。
  2. a = 20の値
    bの値はクロージャー値から取得されます— b(first_time)
    また、前回の実行からbの値を1増やしたため、b = 11であることに注意してください。
  3. 変数aとbは再び1増加します
  4. X()は実行を完了し、そのすべての内部変数(変数a)は存在しなくなります
    ただし、b(first_time)は、クロージャーが存在し続けるため保持されます。

X()が3回目に呼び出されると、

  1. 変数aが新たに作成され、20に設定されます
    X()が最初に実行を完了したときに存在しなくなったため、変数aの以前の値はもはや存在しません。
  2. a = 20の値、bの値はクロージャー値から— b(first_time)
    また、前回の実行でbの値を1増やしたため、b = 12であることに注意してください。
  3. 変数aとbは再び1増加します
  4. X()は実行を完了し、その内部変数(変数a)はすべて存在しなくなります
    ただし、b(first_time)はクロージャーが存在し続けるため保持されます

Y()が初めて呼び出されたとき、

  1. 変数aが新たに作成され、20に設定されます
  2. a = 20の値、bの値はクロージャー値からの値— b(second_time)、したがってb = 10
  3. 変数aとbは1ずつ増加します
  4. Y()は実行を完了し、そのすべての内部変数(変数a)は存在しなくなります
    ただし、b(second_time)はクロージャーとして保持されているため、b(second_time)は存在し続けます。

おわりに

クロージャは、JavaScriptの最初の把握が難しい微妙な概念の1つです。しかし、それらを理解すると、物事は他の方法ではあり得ないことを理解します。

これらの段階的な説明が、JavaScriptのクロージャーの概念を本当に理解するのに役立つことを願っています!

その他の記事:

  • JavaScriptの「自己呼び出し」機能のクイックガイド
  • JavaScriptでの関数スコープとブロックスコープの理解
  • JavaScriptでPromiseを使用する方法
  • JavaScriptで簡単なSpriteアニメーションを作成する方法