JavaScriptのスコープ&クロージャを学ぶ

以下のページでJavaScriptを体系的に学んでいます。 www.mojage.club

本記事は、JavaScriptのスコープ&クロージャについて纏めています。

Sponsored Link

スコープ&クロージャ

JavaScriptにおいてスコープは、どのコードを実行するか判別するためのものです。 ローカルスコープで定義された変数に対しては、それを定義した関数内とその中に入れ子になった関数からのみ参照することが出来ます。 グローバルスコープで定義した変数はどこからでも呼び出すことが出来ます。

    var foo = 0; // global scope
    console.log(foo); // 0
     
    var myFunction = function() {
      var foo = 1; // local scope
      console.log(foo); // 1 
      var myNestedFunction = function() {
        var foo = 2; // local scope
        console.log(foo); // 2
      }(); 
    }(); 
    eval('var foo = 3; console.log(foo);'); // eval() scope

JavaScriptにブロックスコープはない

ifforなどのステートメントはスコープを作成しません。

    var foo = 1;
    
    if (true) {
      foo = 2;
      for(var i = 3; i <= 5; i++){
        foo = i;
        onsole.log(foo);  // 3, 4, 5
      }
    }

ファンクション内でvarを使わずに作成された変数

これはグローバルスコープで定義されたことになります。

    var foo = function(){
      var boo = function(){
        bar = 2;
      }();
    }();
    console.log(bar);  // 2
    
    var foo = function(){
      var boo = function(){
        var doo = 2;
      }();
    }();
    console.log(doo); // undefined

スコープチェーン

スコープ内に目的の変数が存在しない場合、上位のスコープを検索していき、最終的にはグローバルスコープを参照します。

    var x = 10;
    var foo = function(){
      var y = 20;
      var bar = function(){
        var z = 30;
        console.log(z + y + x);
      }();
    }()
    
    foo(); // 60

スコープチェーンは最初に見つけた値を返します。

    var x = false;
    var foo = function(){
      var x = false;
      var bar = function(){
        var x = true;
        console.log(x);
      }();
    }()
    
    foo(); // true

スコープチェーンは、呼び出す時ではなく、ファンクションを定義するときの位置に基づいて決定されます。

クロージャ

スコープチェーンがファンクションを定義した際に決定されるのであれば、以下の様なことが出来ます。これをクロージャと呼びます。

    var countUoFromZero = function(){
      var count = 0;
      return function(){
        return ++count;
      };
    }();  // !
    
    console.log(countUpFromZero());  // 1
    console.log(countUpFromZero());  // 2
    console.log(countUpFromZero());  // 3

これは// !の箇所で実行し、まず一つ目のネストされた匿名関数を返しています。その後、console.log(countUpFromZero());で呼び出されるのはreturn +``+count;のみです。しかしレキシカルスコープによって、元のcountUpFromZero();が定義されたときの親ファンクションにあるvar count = 0;を参照できます。よって一度目の返り値は1。重要なことは、実際に実行している処理は++count;のみの為、var count = 0;が実行されることはありません。よって、次回以降この関数を呼ぶたびに2, 3,,,と数字が増えていきます。