클로저 (Closure)

Sinf·2021년 11월 19일
0

javascript

목록 보기
4/10
post-thumbnail

자바스크립트와 파이썬을 주로 사용하다보니, 함수 안에 함수가 들어가는 것이 이상하다고 생각되지 않는다. 하지만, 자바에서 메인 메서드 내에 메서드를 정의하고 사용하려고 하면 에러가 난다.

자바스크립트에 내부함수가 외부함수의 맥락에 접근할 수 있는 것을 클로저라고 한다.

클로저

function outter() {
  let text = 'text in outter';
  function inner() {
  	console.log('use ' + text);  
  }
  inner();
}

위 코드에서 inner라고 정의된 함수에서 text라는 변수를 사용한다. 그 때, inner 함수 내에 정의된 text가 없기 때문에, 외부 함수인 outter에서 정의된 text를 찾아 출력하게 된다. 이렇게 내부 함수가 외부 함수의 맥락에 접근하는 것을 클로저(closure)라고 한다.

요상한 것

function outter() {
  let text = 'text in outter';
  function inner() {
  	console.log('use ' + text);  
  }
  return inner;
}

let call = outter();
call(); // use text in outter

위 코드에서 보면, inner 함수는 outter 내부에 정의된 함수로 보인다. outter 함수는 정의된 inner 함수를 반환한다.

전역 스코프에서 call라는 변수에 outter 함수의 실행 결과를 전달하고 (내부에 정의된 inner함수가 전달될 것) call을 실행하면, use text in outter라는 값이 호출된다.

그냥 넘어갈 수 있지만, 잘 생각해보면,
innertext라는 변수를 사용한다.
이는, outter 함수에 정의된 값을 가져온다.
하지만, outter 함수는 call을 호출하기 이전에 이미 사용되고 종료되었을 것인데, 어떻게 값을 가져왔을까?

클로저는 내부함수가 외부함수의 맥락에 접근할 뿐 아니라 내부함수에서 외부함수의 변수를 사용할 때, 그 변수가 내부함수가 종료될 때까지 유지된다는 것이다.

조금 더 요상한 것

closure에 대한 개념을 공부하다보면 setTimeout과 연결해서 설명하는 경우가 있다.

function countSec() {
  for (var i = 1; i <= 10; i++) {
    setTimeout(() => {
      console.log(i);
    }, i * 1000)
  }
};

countSec();
console.log('외부함수 종료');

위 코드에서 반복문을 통해 1부터 10까지 1초마다 출력하기 위해 짠 코드로 보인다. 하지만 결과는 11이 1초마다 출력될 것이다.

setTimeout(f, time)는 time(ms) 후 f를 실행하도록 하는 코드. (비동기적으로 실행됨)

setTimeouti의 값을 가져와 바로 사용한다. 그렇기 때문에 1초 간격으로 출력될 수 있는 것이다. (이 때, i는 반복문을 거듭하면서 11의 값으로 종료된다.)

하지만, setTimeout의 익명함수는 외부 함수가 종료된 이후에 호출된다. 그렇기 때문에 클로저가 형성되는데, 이 때 사라지지 않은 i의 값을 가져와 실행한다. 하지만 이미 i의 값은 11이 되어있어 11이 출력되는 것이다.

이해하기 쉽지 않음

  1. setTimeout의 첫번째 인자 익명함수에서 i를 사용한다. 그리고 두번째 인자에서 간격을 위해 i를 사용한다.
  2. 두번째 인자에서 간격을 위해 사용할 때는 i가 즉시 호출된다.
  3. 첫번째 인자 익명함수에서 i가 사용되는 것은 시간이 지나고 외부함수 countSec()가 종료된 이후이기 때문에 클로저가 형성되었다.
  4. 클로저를 통해 호출한 i의 값은 반복문을 거듭하면서 이미 11이 되어있어 콘솔에는 11의 값이 1초 간격으로 출력된다.

그럼? 어떻게 원하는대로 출력할까?

let을 사용하자.

varlet의 차이로, 반복문에서 var가 아닌 let으로 선언하면 원하는대로 출력할 수 있다.
let은 블록 스코프를 갖기 때문에 블록이 가지고 있는 특정 변수를 기억하고 있다가 그 변수를 참조한다.


참고

profile
주니어 개발자입니다. 🚀

0개의 댓글