Closure - (b)


LexicalScopeで学習したように、「関数宣言のLexicalEnvironment」とは、関数定義位置のScope、すなわち親Scopeの実行コンテキストを表すLexicalEnvironmentのことである.

エンクロージャの動作原理

const x = 1;

// (1)
function outer () {
  const x = 10;
  const inner = function () { 
    console.log(x); // (2)
  };
  return inner;
}

const innerFunc = outer(); // (3)
innerFunc(); // (4) 10
上の例題を見てみましょう.
上記の例では、outer関数を評価して関数オブジェクトを作成する場合-(1)現在実行されている実行コンテキストのディレクトリ環境(すなわちグローバルディレクトリ環境)を上位coproとしてouter関数オブジェクトの内部スロットに格納します.
ネストされた関数innerは、現在実行されている実行コンテキストの集合環境、すなわちouter関数の集合環境を内部スロットに格納します.
outer関数の実行が終了すると、内部関数が返され、outer関数のライフサイクルが終了します.すなわち、outer関数の実行コンテキストは実行コンテキストスタックから削除されます.
outer関数の実行コンテキストは、実行コンテキストスタックから削除されます.
outer関数を消滅させるLexical環境ではありません.
=>outer関数の集合環境は埋め込み関数の内部スロットによって参照され、埋め込み関数は仮想集合のターゲットではないため、グローバル変数innerFuncによって参照されます.ゴミ収集器は、参照されているメモリ領域を勝手に解放しません.
outer関数から返される内部関数を呼び出す-(4)内部関数の実行コンテキストを作成し、実行コンテキストスタックに配置します.
オーバーラップ関数の内部は外部関数の外部よりも生存時間が長い
この場合、外部関数(outer)よりも長く生存するオーバーラップ関数(inner)は、外部関数が生存するか否かに関係なく、その定義された位置(outer関数の内部で定義されたオーバーラップ関数)によって決定される親スキャン(=outer関数のディレクトリ環境)を記憶する.
この程度なら、Closerに対する理解は完了していると思います.
JAvascriptのすべての関数は親スキャンを覚えているので、理論的にはすべての関数はCloserです.しかし、通常、すべての関数がモジュールと呼ばれるわけではありません.これらの例を見てみましょう.

すべての関数がモジュールではない


例1

function foo() {
  const x = 1;
  const y = 2;
  
  // 일반적으로 클로저라고 하지 않는다.
  function bar() {
    const z = 3;
    
    // 상위 스코프의 식별자를 참조하지 않는다.
    console.log(z);
  }
  
  return bar;
}

const bar = foo();
bar();
上記の例のbar(ネスト関数)はfoo(外部関数)よりも寿命が長いが、親スキャン(foo)の識別子(変数)は参照されない.
上図の識別子を参照しない場合、ほとんどのモダンブラウザは、上図の上図を最適化することによって記憶しません.参照しない識別子を覚えるのはメモリの無駄だからです.
したがってbar関数をCloserと呼ぶことはできない.

例2

function foo() {
  const x = 1;
  
  // bar 함수는 클로저였지만 곧바로 소멸한다.
  // 이러한 함수는 일반적으로 클로저라고 하지 않는다.
  function bar() {
    // 상위 스코프(foo)의 식별자를 참조한다.
    console.log(x);
  }
  bar();
}
foo();
上記の例のオーバーラップ試験バーは、親スキャン(foo)の識別子を参照するため、キャビネットである.しかし、ネスト関数barは外部関数fooの外部に戻らない.
すなわち,オーバーラップ関数barのライフサイクルは外部関数fooよりも短い.この場合、ネスト関数barはモジュールであるが、外部関数よりも早く消失するため、モジュールの本質に合致しない.すなわち、ライフサイクルが終了した外部関数の識別子を参照することができる.
したがって、barはCloserとは呼べません.

例3

function foo() {
  const x = 1;
  const y = 2;
  
  // 클로저
  // 중첩 함수 bar는 외부 함수보다 더 오래 유지되며 상위 스코프의 식별자를 참조한다.
  function bar() {
    // 상위 스코프(foo)의 식별자를 참조한다.
    console.log(x);
  }
  return bar;
}
const bar = foo();
bar();
上記の例のネストされた関数バーは、親ミラーの識別子(fooのx)を参照しているので、キャビネットです.その後、外部関数の外部に戻り、外部関数よりも長く生存します.
上記のように、通常、ネスト関数が親スキャンの識別子を参照し、ネスト関数が外部関数よりも長く保持されている場合には、
しかし、閉じたオーバーラップ関数barは、位相走査のx,y識別子においてxのみ参照される.この場合、ほとんどのモダンブラウザは、最適化によって、上位スキャンの識別子の中でモジュールが参照する識別子だけを覚えています.
キャビネットで参照される親ミラー変数(上記の例ではfoo関数のx)をフリー変数(free variable)と呼ぶ.
Closerは「関数は自由変数に対して閉じている」という意味です.
より分かりやすいのは、これを「自由変数に縛られた関数」と呼ぶことができることだ.

利用モジュール


エンクロージャの主な用途
エンクロージャは、安全にステータスを変更および維持するために使用されます.すなわち,状態の予期せぬ変更を防止するために,状態(情報非表示)を安全に非表示にし,特定の関数のみが状態を変更できるようにする.
方法1>
numをグローバル変数にし、インクリメンタル関数からアクセスします.
=>動作は良いが、エラーが発生する可能性がある.動作を正しくするには、いくつかの仮定が必要です.
  • カウント状態(num変数の値)は、インクリメント関数を呼び出す前に一定に保たなければならない.
  • このため、カウント状態(num変数の値)はインクリメンタル関数でのみ変更できます.ただし、カウントステータスはグローバル変数で管理されるため、誰もがいつでもアクセスおよび変更でき、=>エラーが発生する可能性があります.
    方法2>
    領域変数を使用して、インクリメンタル関数にカウント状態変数(num)を作成します.
    =>num変数の状態は増分関数のみ変更できます.ただし、インクリメント関数を呼び出すたびに変数値が初期化されます.以前の状態を保存できません.
    方法3>エンクロージャの使用
    // 카운트 상태 변경 함수
    const increase = (function () {
      // 카운트 상태 변수  
      let num = 0;
      
      // 클로저
      return function () {
        // 카운트 상태를 1만큼 증가시킨다.
        return ++num;
      };
    }());
    
    console.log(increase()); // 1
    console.log(increase()); // 2
    console.log(increase()); // 3
    上記のコードが実行されると、直ちに実行関数が呼び出され、直ちに実行関数が返す関数がインクリメンタル変数に割り当てられる.increate変数に割り当てられた関数は、定義された場所によって決定される親スキャンが直ちに関数を実行するディレクトリ環境(numが存在するディレクトリ環境)を格納するストレージモジュールです.
    インスタント実行関数は呼び出し後に破棄されますが、インスタント実行関数が返すモジュールはインクリメンタル変数に割り当てられて呼び出されます.このとき,直ちに関数を実行して戻ったキャビネットは,自分が定義した位置によって決定された親スキャンプログラムが直ちに関数を実行する集合環境を覚えている.
    したがって、即時実行関数が返すモジュールは、カウント状態を維持するために自由変数numを参照して変更することができる.
    インスタント実行関数は1回のみ実行されるため、インクリメンタルが呼び出されるたびにnum変数は初期化されません.またnum変数は非表示のプライベート変数であり、外部から直接アクセスできないため、グローバル変数を使用する場合のように予期せぬ変更を心配する必要がないため、より信頼性の高いプログラミングが可能です.
    同時に増加・減少する可能性のあるコードの末尾を見てみましょう.
    const counter = (function () {
      // 카운트 상태 변수
      let num = 0;
      
      // 클로저인 메소드를 갖는 객체를 반환한다.
      // 객체 리터럴은 스코프를 만들지 않는다.
      // 따라서 아래 메서드들의 상위 스코프는 즉시 실행 함수의 렉시컬 환경이다.
      return {
        increase() {
       		return ++num;
        }
        decrease() {
          return num > 0 ? --num : 0;
        }
      };
    }());
    
    console.log(increase()); // 1
    console.log(increase()); // 2
    
    console.log(decrease()); // 1
    console.log(decrease()); // 0