클로저의 사전적 정의는 함수와 그 함수가 선언된 렉시컬 환경과의 조합이다
내가 이해한 바로 쉽게 바꿔보면 함수와 그 함수 외부에 선언된 변수와의 관계를 의미한다.
예시를 들어보면
function outer(){
const v = 100;
return function inner(){
console.log(v);
}
}
// 1
const innerFunc = outer();
// 2
innerFunc(); // 10
클로저를 알기전에 위 예시를 자세히 살펴보면 이상하다고 느낄만한 부분이 존재한다.
함수는 분명 종료되면 실행 컨텍스트 스택에서 제거되는데 그렇다면 함수내부에서 선언한 변수인 v
도 없어져야하는데 1에서 함수가 끝나고 나서 2에서 v
를 사용하는데 정상적으로 출력이 된다는 것이다.
이 이유에 대해서 천천히 정리해보려고 한다.
자바스크립트는 기본적으로 렉시컬 스코프(정적 스코프)를 따른다.
이 말의 의미는 함수선언시 그 함수의 스코프가 결정된다는 의미이다.
function testFunc1(){
const v = 10;
testFunc2();
}
function testFunc2(){
console.log(v);
}
testFunc1(); // Error v is not define
위 예시를 처음볼 때 필자는 10이 출력될거라고 예상했다.
하지만 오류가 나는데 이유는 렉시컬 스코프를 따르기 때문이다.
즉, testFunc1()
, testFunc2()
를 선언하는 시점에 자신의 스코프가 결정되어버리기 때문에 testFunc2()
를 어디서 호출하든 상관없이 전역변수로 v
가 존재하지않는 이상 에러가 나는 것이 당연하다.
[[Environment]]
렉시컬 스코프에서 함수의 스코프가 결정할 때 그 함수의 스코프의 참조를 저장하는 내부슬롯이다.
좀 더 정확하게 말하면 현재 실행중인 실행 컨텍스트의 렉시컬 환경을 가리키는 값을 넣는 내부슬롯이다.
함수들은 [[Environment]]
내부슬롯을 가지며 렉시컬 환경에 대한 참조값을 저장한다.
상위스코프 즉, 자신이 정의된 스코프라고 생각하면 된다.
일급객체는 4가지 조건을 만족하는 객체이다.
4가지중 한 가지인 함수를 반환값으로 사용할 수 있다를 만족해야 클로저가 존재할 수 있다.
가비지 컬렉터가 메모리할당을 해제하는 기준을 해당 식별자에 접근할 수 있는 식별자가 존재하는 여부에 의해 결정된다.
맨 처음 예시를 다시 가져와서 정리해보겠다.
function outer(){
const v = 100;
return function inner(){
console.log(v);
}
}
// 1
const innerFunc = outer();
// 2
innerFunc(); // 10
inner()
의 렉시컬 환경은 outer()
이며, outer()
는 함수를 반환하는 일급객체이다.
1에서 outer()
가 종료되면 실행 컨텍스트 스택에서 outer()
가 제거되지만 outer()
의 실행 컨텍스트가 제거되는 것은 아니다.
그리고 가비지 컬렉터에 의해서 할당이 해제되지도 않는데 그 이유는 outer()
의 반환값인 inner()
가 outer()
내부에서 선언한 변수를 사용하고 있기 때문에 outer()
의 실행 컨텍스트는 메모리할당해제대상에서 제외되기 때문이다.
2에서 inner()
를 호출할 때 inner()
의 [[Environment]]
를 이용해서 렉시컬 환경에 대한 참조를 얻어서 outer()
의 변수 v
에게 접근이 가능한 것이다.
이런 inner()
함수와 그 바깥에 존재하는 v
변수를 사용하는 함수를 클로저라고 정의한다.
즉, 이렇게 외부 변수를 사용하는 inner()
함수를 클로저라고 부른다.
그리고 이 때 v
변수를 자유변수라고 부른다