TIL 19. Java Script - Closure

신미영·2021년 4월 28일
0

Java Script

목록 보기
14/17

자바스크립트 고유의 개념은 아니지만 함수형 프로그래밍 언어(Functional Programming language)에서 사용되는 중요한 특성인 클로저(Closure)에 대해서 공부했다.



클로저(Closure)의 개념

클로저는 A라는 함수가 자신의 내부에 있는 변수뿐만 아니라 자신을 포함하고 있는 상위 스코프(외부 함수 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)라는 의미로서 붙여진 것을 알 수 있었다.



클로저(Closure)의 특징

1. 외부함수가 이미 반환되었어도 외부함수 내의 변수는 이를 필요로 하는 내부함수가 하나 이상 존재하는 경우 계속 유지된다.

클로저는 특히 자신을 포함하고 있는 외부함수보다 내부함수가 더 오래 유지되는 경우, 외부 함수 밖에서 내부함수가 호출되더라도 외부함수의 지역 변수에 접근할 수 있다는 점이 특징이다.
예를들어 외부함수가 내부함수를 반환하는 즉시실행함수라고 가정해 보자. 함수가 선언 즉시 실행되면 외부 함수의 life-cycle이 종료되어 콜스택(실행 컨텍스트 스택)에서 제거되고 함수 안의 변수도 유효하지 않게 된다. 그러나 클로저를 통해 다른 곳에서 외부함수의 내부 함수를 호출했을 때 외부함수의 변수가 동작하게 되는 것이다. 즉 클로저는 반환된 내부함수가 자신이 선언 되었을 때의 렉시컬 환경을 기억해 그 밖에서도 그 환경에 접근 할 수 있다.

아래와 같이 함수 B는 콜스택에서 제거되었으나 반환된 내부함수인 A가 rate 변수를 필요로 하기 때문에 계솟 유지된다.

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()함수에서 두 함수를 호출할때도 인자가 필요해 진다.

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)
}


클로저(Closure)의 활용

:: 최신 상태 유지

아래는 toggle버튼 클릭 시 .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를 생성해 increase, decrease메소드를 갖는 인스턴스를 생성한다. 이 때 두 메소드는 자신이 생성될 때의 렉시컬 환경을 공유하고 클로저로서 변수에 접근한다. 이때 let counter = 0;은 프로퍼티가 아닌 변수다. 이 변수는 생성자 함수 Counter 외부에서 접근할 수 없다. 따라서 외부에서 변수에 접근하여 값을 변경할 수 없는 것이다.

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'출력







profile
Hello World!

0개의 댓글