자바스크립트와 파이썬을 주로 사용하다보니, 함수 안에 함수가 들어가는 것이 이상하다고 생각되지 않는다. 하지만, 자바에서 메인 메서드 내에 메서드를 정의하고 사용하려고 하면 에러가 난다.
자바스크립트에 내부함수가 외부함수의 맥락에 접근할 수 있는 것을 클로저라고 한다.
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
라는 값이 호출된다.
그냥 넘어갈 수 있지만, 잘 생각해보면,
inner
는 text
라는 변수를 사용한다.
이는, 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를 실행하도록 하는 코드. (비동기적으로 실행됨)
setTimeout
은 i
의 값을 가져와 바로 사용한다. 그렇기 때문에 1초 간격으로 출력될 수 있는 것이다. (이 때, i
는 반복문을 거듭하면서 11의 값으로 종료된다.)
하지만, setTimeout
의 익명함수는 외부 함수가 종료된 이후에 호출된다. 그렇기 때문에 클로저가 형성되는데, 이 때 사라지지 않은 i
의 값을 가져와 실행한다. 하지만 이미 i
의 값은 11이 되어있어 11이 출력되는 것이다.
이해하기 쉽지 않음
그럼? 어떻게 원하는대로 출력할까?
let
을 사용하자.var
와 let
의 차이로, 반복문에서 var
가 아닌 let
으로 선언하면 원하는대로 출력할 수 있다.
let
은 블록 스코프를 갖기 때문에 블록이 가지고 있는 특정 변수를 기억하고 있다가 그 변수를 참조한다.