[programmers] TIL_DAY-14

김민기·2022년 4월 4일
0

Programmers_TIL

목록 보기
14/21
post-thumbnail

✅ Closure

 클로저는 지난번에 한번 정리를 했지만, 이번 기회에 다시 한번 정리 해본다. 클로저에 대해 검색해보면 다음과 같은 정의를 찾을 수 있다.

클로저는 함수와 그 함수가 선언됐을 때의 렉시컬 환경과의 조합이다.

 클로저는 자바스크립트에만 있는 고유 개념이 아니라 함수를 일급 객체로 취급하는 함수형 프로그래밍 언어에서 사용되는 중요한 특성이다.

function outFunction() {
  let x = 10;
  function inFunction() {
    console.log(x);
  }
  inFunction();
}

 이 코드에서 inFunction은 함수 스코프를 생성한다. 그리고 outFunction 함수 내부에서 선언되었기 때문에 스코프 체인에 의해 outFunction 스코프까지 참조 할 수 있다. 따라서 x = 10으로 선언된 값을 inFunction에서 참조해서 출력할 수 있다.

function outFunction() {
  let x = 10;
  function inFunction() {
    console.log(x);
  }
  return inFunction;
}

let inner = outFunction();
inner();

 outFunction을 실행하면 inFunction 함수를 리턴한다. 리턴 받은 inner를 실행하면 inFunction 함수가 실행되어 10이 출력된다. 아무런 문제가 없어보이지만 사실 이상한점이 있다. outFunction은 inFunction을 반환하고 완료(소멸)된다. 따라서 내부에 있는 변수 x 또한 유효하지 않게 되어 외부에서 x에 접근할 수 없다. 하지만 inner( )를 실행하면 자연스럽게 x를 참조해서 x의 값을 출력한다.

렉시컬 스코프를 기억하자! 함수가 어디서 호출이 되는 곳이 아닌 함수가 선언되는 곳에서 스코프가 결정된다.

 지난번에 정리한 랙시컬 스코프를 기억하면 쉽게 이해할 수 있다. inFunction이 호출되는 것은 outFunction의 외부 즉 전역 스코프다. 하지만 inFunction이 선언된 곳은 outFunction의 내부 함수 스코프이고 스코프 체인에 의해 outFunction 스코프를 참조할 수 있다.

클로저는 반환된 내부함수가 자신이 선언됐을 때의 환경(Lexical Environment)인 스코프를 기억하여 자신이 선언됐을 때의 환경 밖에서 호출되어도 그 환경에 접근할 수 있는 함수를 말한다.
간단히 말하면 클로저는 자신이 생성될 때의 환경을 기억하는 함수다.

test = function() {
  for(var i = 0; i < 3; i++) {
    setTimeout(function() {
      console.log(i);
    }, 1000 * i);
  }
} 

 클로저를 공부하다보면 자주 만나는 setTimout 예제다. 순차적으로 출력이 될 것으로 생각되지만 실제로는 모두 2가 출력된다.
 왜 그럴까? 자바스크립트 엔진에서 for문을 실행하고 비동기 콜백함수 setTimeout을 실행한다. 따라서 for문이 모두 실행되어 i는 2가되고, setTimeout이 실행될 때 참조하는 i는 모두 2가 된다. 이 문제를 해결하기 위해서는 클로저를 사용해야 한다.

test = function() {
 for(let i = 0; i < 3; i++) {
   setTimeout(function() {
     console.log(i);
   }, 1000 * i);
 }
}

test = function() {
 for(let i = 0; i < 3; i++) {
   (function(x) {
     setTimeout(function() {
       console.log(x);
     }, 1000 * x);
   })(i);
  }
}

  let을 사용하거나 즉시 실행함수를 사용하면 클로저를 만들 수 있다. let은 블록 스코프를 갖기 때문에 for문 루프마다 i 값이 저장되서 즉 클로저가 생성되어 setTimeout 함수를 실행할 때마다 다른 i값을 출력한다.

클로저는 상태를 안전하게 변경하고 유지하기 위해 사용된다. 상태가 의도치 않게 변경되지 않도록 상태를 안전하게 은닉하고 특정 함수에게만 상태 변경을 허용하기 위해 사용된다.

let num = 0;

const increase = () => { return ++num };

console.log(increase()); // 1
console.log(increase()); // 2
console.log(increase()); // 3

 increase 함수를 실행하면 정상적으로 num의 값이 1씩 증가하여 출력된다. 하지만 num이 외부에 있기 때문에 다른 곳에서 변경이 가능하다. 함수를 실행할 때마다 num이 값의 이전값과 동일하다는 것을 보장할 수 없다.

const increase = (function() {
  let num = 0;
  return function() {
    return ++num;
  }
})();

console.log(increase()); // 1
console.log(increase()); // 2
console.log(increase()); // 3
console.log(increase.num); // undefined

 즉시 실행함수를 만들어서 클로저를 만든다. increase는 리턴되는 익명함수를 갖고 있으며, 이 익명함수는 num에 접근이 가능하다. 따라서 이 익명함수를 통해서만 num의 값을 읽어오고 수정할 수 있다.

✅ 정리

  this, scope, 클로저를 정리하면서 자바스크립트의 동작원리가 생각보다 많이 복잡하고 그 만큼 중요하다고 느껴진다. 클로저를 정리하다보니 실행 컨택스트라는 단어가 많이 나오는데 이를 따로 한번 정리 해야겠다. 책이 왔으니 이제 좀 더 쉽게 공부할 수 있을거 같다 😄

0개의 댓글