클로저는 함수와 그 함수가 선언된 렉시컬 환경과의 조합이다
여기서 중요하게 보아야 할 키워드는 '함수가 선언된 렉시컬 환경'이다.
const x = 1
function outerFunc(){
const x = 10
function innerFunc(){
console.log(x); //10
}
innerFunc();
}
outerFunc();
outerFunc 함수 내부에서 중첩 함수 InnerFunc가 정의되고 호출되었다.
이때 중첩함수 InnerFunc의 상위 스코프는 외부 함수 outerFunc 의 스코프로, innerFunc 내부에서 자신을 포함하고 있는 외부 함수 outerFunc의 x 변수에 접근할 수 있다.
하지만 중첩된 함수가 아니라면 innerFunc는 outerFunc함수의 변수에 접근할 수 없다.
이 이유는 자바스크립트가 렉시컬 스코프를 따르는 언어이기 때문이다.
렉시컬 스코프(정적 스코프)란? 외부 렉시컬 환경에 대한 참조에 저장할 참조값 함수를 어디에 정의했는지에 따라 상위 스코프를 정할 수 있는 자바스크립트의 스코프
이처럼 선언된 함수(호출 위치x)의 위치에 따라서 상위 스코프가 정해지는 것이다.
따라서 위 예제의 outerFunc()를 보면 해당 함수는 가장 바깥에서 실행되었기 때문에 해당 함수의 스코프는 전역으로 볼 수 있다.
위에서 말한 것처럼 함수가 정의된 환경(위치)와 호출되는 환경(위치)는 다를 수 있다. 따라서 렉시컬 스코프가 가능하기 위해서는 함수가 자신이 정의된 환경, 즉 상위 스코프를 기억해야 한다. 그렇기 때문에 함수는 자신의 내부 슬롯 \[\[environment]]에 자신이 저장된 환경(상위 스코프의 참조)을 저장해 놓는다.
가장 바깥의 함수의 경우 전역 렉시컬 환경을 참조하도록 맨 처음 함수 정의 평가가 실행될 때 저장되지만, 함수 안의 내부 함수는 바깥쪽 함수가 실행될 때 해당 외부 함수 렉시컬 환경의 참조가 저장된다. 이 부분이 이해가 가지 않는다면 함수가 호출될 때 실행되는 내부 로직을 보면 된다.
함수는 호출될 때 함수 내부로 코드의 제어권이 이동하며, 함수 코드를 평가한다.
const x = 1;
function outer(){
const x = 10;
const inner = () => { console.log(x); }
return inner
}
const innerFunc = outer();
innerFunc() //10
예제를 통해 보자.
해당 환경에선 중첩 함수로 inner() 가 들어가 있다. 그리고 outer()는 실행되면서 해당 함수를 반환하고 생명주기를 마감하지만, 막상 inner를 실행했을 때는 10이 나온다.
이 이유는 중첩 함수가 외부 함수보다 더 오래 유지되는 경우에 중첩 함수는 이미 생명주기가 종료한 외부 함수의 변수를 참조할 수 있기 때문이다. 이러한 중첩 함수를 클로저라고 부른다.
이러한 클로저의 경우는 계속해서 외부 함수의 렉시컬 환경을 기억하고, 참조하고 있으며, 이는 계속해서 해당 중첩함수가 존재하는 한 유지된다. 해당 렉시컬 환경을 내부 함수가 참조하고 있기 때문에 가비지 컬렉션의 대상이 되지 않기 때문이다.