클로저를 알기 전에 스코프 체인
에 대해서 알아두어야합니다.
자바스크립트는 스크립트가 실행되면 내부적으로 Global 객체를 생성합니다. 이 객체는 인스턴스화 하거나 메소드 호출이 불가능하며, 스크립트에서 글로벌 변수와 함수를 관리하기 위해 생성되는 객체입니다.
로컬 변수도 글로벌 객체처럼 어떤 객체에 의해서 관리가 되는데, 이 객체는 Call 객체(Activation 객체)
라고 합니다. Call 객체
도 인스턴스화나 메소드 호출이 불가능하고, 함수가 호출될 때 마다 로컬 변수들을 관리하기 위해서 자동적으로 생성되는 객체입니다.
그동안은 이 객체들은 자동적으로 생성되고 관리되어서 언급도 안하고 신경도 안썼지만, 이제는 좀 알아두어야합니다. 이 객체들을 알면 자바스크립트의 변수 메커니즘을 이해할 수 있기 때문입니다.
자 그럼 다시 돌아와서 스코프 체인
은 Global 객체와 Call 객체들을 생성 순서대로이어놓은 것을 말합니다. 스코프 체인
에서 변수의 참조는 체인의 선두(가장 내부의 변수)에서 시작해 가장 처음 만나는 변수 값을 그 값으로 채택하는 구조를 가집니다.
다음 코드를 보시면 스코프 체인
이 어떤 느낌으로 이루어지는지 볼 수 있습니다.
변수 a, b, c는 가장 내부의 함수에서 호출되는데, 호출 위치에서 가장 선두 체인인 inner -> outer -> global순서로 참조할 변수를 찾아나감을 볼 수 있습니다.
let c = 'Global';
const outer = () => {
let b = 'Local, Outer';
const inner = () => {
let a = 'Local, inner';
console.log(c);
console.log(b);
console.log(a);
}
inner();
}
outer();
Closure
(이하 클로저)는 로컬 변수를 참조하는 함수 내부의 함수입니다. 말로만 하니까 어렵죠? 실제 코드를 보겠습니다.
const closure = init => {
let cnt = init;
return function () {
return ++cnt;
}
}
let c = closure(1);
console.log(c());
console.log(c());
console.log(c());
이 코드의 실행결과가 어떨 것 같나요? 1을 인수로 준 것이 세번 출력하니까 '2, 2, 2'?
결과로는 '2, 3, 4'가 나왔습니다. 이러한 동작이 클로저입니다. 코드를 자세히 살펴보겠습니다.
closure() 함수는 init을 cnt에 저장하고, 그것을 1증가 시키고 반환하는 것 처럼 구성되어있습니다. 하지만 return 구문을 보시면, 그 값을 바로 반환하는 것이 아니라, 익명 함수를 통해서 값을 반환합니다. 그래서 함수가 종료되어도 익명 함수가 계속해서 cnt를 참조하고 있기 때문에 cnt의 값이 유지되는 것입니다. 그래서 첫 실행에 cnt가 2가 된 것이 계속 유지되어 3, 4와 같이 결과가 나타난 것 입니다.
아직 어렵나요? 좀 더 풀어서 설명하자면, c에는 closure()를 할당했습니다. closure()가 익명 함수를 반환하므로, 결과적으로는 c에 익명 함수가 할당된 상태인 것 입니다. 그런데 이 익명 함수는 스코프 체인으로 인해 closure의 로컬 변수 cnt를 참조하고 있으므로, c가 선언된 동안 cnt값이 계속해서 참조되어 초기화 되지 않고 남아있다는 것 입니다.
익명 함수로 보관되는 스코프 체인은 다음 세 값들을 보관합니다.
위 예제에서는 cnt가 클로저 함수의 Call 객체입니다.
위에서 본 동작대로라면 클로저는 함수임에도 불구하고 마치 기억공간을 제공하는 것 처럼 값을 보관할 수 있는 것 처럼 보입니다. 그래서 클로저는 다음과 같은 동작도 할 수 있습니다.
const closure = init => {
let cnt = init;
return function () {
return ++cnt;
}
}
let c1 = closure(1);
let c2 = closure(900);
console.log(c1());
console.log(c2());
console.log(c1());
console.log(c2());
이번에도 동작을 한 번 예상해보세요. 결과가 어떨까요? 같은 로컬 변수 cnt를 참조하니까 5가 될까요?
이번에는 같은 클로저 함수를 사용했지만 서로 다른 값을 보관하고 유지합니다. 이 이유는 스코프 체인과 Call 객체 때문입니다. 처음에 Call 객체를 소개할 때 이런 이야기를 했습니다. Call 객체는 함수가 호출될 때 마다 생성된다. 따라서 Call 객체가 클로저 함수 호출마다 cnt를 관리하기 위해 별로도 생성된다는 것 입니다.
그래서 c1과 c2가 같은 클로저 함수를 호출해도 스코프 체인, Call 객체는 따로 생성되어 두 변수에 할당된 익명 함수가 참조하는 로컬 변수 cnt는 별개의 지역 변수가 된 것 입니다.