TIL 19. Java Script - Closure


JavaScript固有の概念ではないが,関数式プログラミング言語(Functional Programming Language)で用いられる重要な特性「closure」を学習した.

「シャーシ」(Close)の概念


Closerとは、Aという関数が内部の変数だけでなく、その内部に含まれる親Scope(外部関数B)にもアクセスできる変数です.ここでの外部関数には、モジュールを囲む外部関数とグローバルスキャンも含まれます.
function B (result) {
  const rate = 10; 
  const A = (value) = value * rate;  // A함수 외부에 있는 B함수에서 rate 변수 접근
  return result.map(A)
}
B([1, 2, 3]); // [10, 20, 30] 출력
const rate = 10;
function A(value) {
  return value * rate  // 전역 스코프에서 rate 변수 접근
}
A(1);  // 10 출력 
前述したように、モジュールによってアクセス可能な外部関数の変数(rate)を自由変数と呼び、ここでは、モジュール(closure)の名前が自由変数中の関数がインターリーブされ、クローズド(closed)と呼ばれることがわかる.

エンクロージャフィーチャー


1.外部関数が返されても、1つ以上の内部関数が必要でない限り、外部関数内の変数は変更されません.
モジュールの特徴は,特に内部関数が自分を含む外部関数よりも長い時間保持されている場合,外部関数が外部関数を呼び出しても外部関数の領域変数にアクセスできることである.
たとえば、外部関数が内部関数を返すと、直ちに関数が実行されるとします.宣言後すぐに関数が実行されると、外部関数のライフサイクルが終了し、呼び出しスタック(コンテキストスタックの実行)から削除され、関数の変数も無効になります.ただし、外部関数の内部関数をエンクロージャを介して別の場所から呼び出すと、外部関数の変数が機能します.つまり、エンクロージャは、宣言時に返された内部関数のディレクトリ環境を記憶し、外部から環境にアクセスできます.
下図に示すように、関数Bはcallスタックから除去されるが、返される内部関数Aにはレート変数が必要であるため、変わらない.
function B() {
  const rate = 10;
  const A = function () {document.write(rate); };
  return A;
}  // 함수 B는 함수 A를 반환하면서 life-cycle을 마감함

const inside = B();
inside();  // 10 출력
2.ある関数の内部で定義された直後に呼び出される使い捨て関数のパラメータを省略できます.
以下の場合、sayHi()関数の内部にはinformationNoitce()関数とlogInCount()関数があるので、内部関数を記述するパラメータを必要とせずに自由変数を導入することができ、関数呼び出し時にもパラメータを渡す必要がない.しかし、sayHi()関数を除外すると、informationNoitce()関数とlogInCount()関数にはそれぞれパラメータが必要になり、sayHi()関数でこの2つの関数を呼び出す場合にもパラメータが必要になります.
function sayHi(id, level, logIn) {
  const informationNoitce = function() {
  	alert(`${id}님은 level ${level} 입니다.`)
  }
  
  const logInCount = function() {
    console.log(`${logIn}`)
  }
  
  informationNoitce()  // 인자 없음
  logInCount()  // 인자 없음
}
3.エンクロージャが重複している可能性があるため、エンクロージャが多ければ多いほど、コードのメンテナンスが困難になります.
下図に示すように、rate変数を除外すると、showData()関数は閉パケットを生成し、calcRate()関数はオーバーラップ閉パケットを生成します.これにより、関数が非常に長い可能性が高くなり、変数が外部でletのキーワードとして使用されるため、外部で自由に変更できるという問題が発生する可能性がある.したがって,モジュールのオーバーラップをできるだけ回避したり,変数を関数のパラメータとして加えたりすることで,変数が外部で修正される可能性のある問題を回避できる.
let rate = 10;

function showData(value) {
  const calcRate = (value) => value * rate
  return value.map(calcRate)
}

エンクロージャの使用


:::最新のまま


以下は切り替えボタンをクリックしたときです.box要素を非表示または非表示にする機能を実現しました.toggle関数内で返される関数は、自由変数isShowに近い閉パケットである.このボタンをクリックして、イベントハンドラに割り当てられたイベントハンドラキャビネットを呼び出します.このときbox要素表示状態を表す変数isShowの値が変更されます.変数isShowは、エンクロージャによって参照されるため有効であり、自身の変更の最新の状態を維持し続けます.したがって、ボタンをクリックすると、truefalseが交互に表示されます.
<html>
<body>
  <button class="toggle">toggle</button>
  <div class="box" style="width: 100px; height: 100px; background: red;"></div>

  <script>
    const box = document.querySelector('.box');
    const toggleBtn = document.querySelector('.toggle');

    const toggle = (function () {
      // 변수
      let isShow = false;
      // 클로저
      return function () {
        box.style.display = isShow ? 'block' : 'none';
        isShow = !isShow;
      };
    })();

    toggleBtn.addEventListner('click', toggle);
  </script>
</body>
</html>

::グローバル変数の無効化


グローバル変数はいつでもアクセスでき、いつでも変更できるため、エラーが発生することがあります.モジュールを使用してグローバル変数を関数として記述すると、これらのエラーを回避できます.
下図に示すように、increaseBtnボタンのクリック時に1とカウントされる関数を作成すると、increase関数の外部でcount変数を宣言せず、内部で宣言する.ただし、count変数をエンクロージャ内部の領域変数として宣言する場合は、ボタンをクリックするたびにcount値が0に初期化されることに注意してください.
<html>
  <body>
  <button id="inclease">+</button>
  <p id="count">0</p>
  <script>
    const increaseBtn = document.getElementById('inclease');
    const count = document.getElementById('count');

    const increase = (function () {
      // 변수
      let counter = 0;
      // 클로저
      return function () {
        return ++counter;
      };
    }());

    increaseBtn.onclick = function () {
      count.innerHTML = increase();
    };
  </script>
</body>
</html>

::情報を非表示


サブ関数Counterが生成され、increasedecreaseの方法を有する例が生成される.この場合、両方のメソッドは、作成時のディレクトリ環境を共有し、モジュールとして変数にアクセスします.このとき、let counter = 0;はプロ選手ではなく変数です.この変数は、コンストラクション関数カウンタから外部にアクセスできません.したがって、変数に外部からアクセスして値を変更することはできません.
function Counter() {
  // 카운트를 유지하기 위한 자유 변수
  let counter = 0;

  // 클로저
  this.increase = function () {
    return ++counter;
  };

  // 클로저
  this.decrease = function () {
    return --counter;
  };
}

const counter = new Counter();

console.log(counter.increase()); // 0에서 +1이 되어 '1'출력
console.log(counter.decrease()); // 최신 상태인 1에서 -1 되어 '0'출력
  • ソース
    Engineering Blog by Dale Seo
    Webプログラミングチュートリアル/Poiemaweb