클로저

클로저란?

클로저는 함수와 그 함수가 선언된 렉시컬 환경과의 조합이다

여기서 중요하게 보아야 할 키워드는 '함수가 선언된 렉시컬 환경'이다.

const x = 1

function outerFunc(){
	const x = 10

	function innerFunc(){
		console.log(x); //10
	}

	innerFunc();
}
outerFunc();

outerFunc 함수 내부에서 중첩 함수 InnerFunc가 정의되고 호출되었다. 이때 중첩함수 InnerFunc의 상위 스코프는 외부 함수 outerFunc 의 스코프로, innerFunc 내부에서 자신을 포함하고 있는 외부 함수 outerFuncx 변수에 접근할 수 있다. 하지만 중첩된 함수가 아니라면 innerFuncouterFunc함수의 변수에 접근할 수 없다.

이 이유는 자바스크립트가 렉시컬 스코프를 따르는 언어이기 때문이다.

렉시컬 스코프(정적 스코프)란? 외부 렉시컬 환경에 대한 참조에 저장할 참조값 함수를 어디에 정의했는지에 따라 상위 스코프를 정할 수 있는 자바스크립트의 스코프

이처럼 선언된 함수(호출 위치x)의 위치에 따라서 상위 스코프가 정해지는 것이다. 따라서 위 예제의 outerFunc()를 보면 해당 함수는 가장 바깥에서 실행되었기 때문에 해당 함수의 스코프는 전역으로 볼 수 있다.

\[\[environment]]

위에서 말한 것처럼 함수가 정의된 환경(위치)와 호출되는 환경(위치)는 다를 수 있다. 따라서 렉시컬 스코프가 가능하기 위해서는 함수가 자신이 정의된 환경, 즉 상위 스코프를 기억해야 한다. 그렇기 때문에 함수는 자신의 내부 슬롯 \[\[environment]]에 자신이 저장된 환경(상위 스코프의 참조)을 저장해 놓는다.

가장 바깥의 함수의 경우 전역 렉시컬 환경을 참조하도록 맨 처음 함수 정의 평가가 실행될 때 저장되지만, 함수 안의 내부 함수는 바깥쪽 함수가 실행될 때 해당 외부 함수 렉시컬 환경의 참조가 저장된다. 이 부분이 이해가 가지 않는다면 함수가 호출될 때 실행되는 내부 로직을 보면 된다.

함수는 호출될 때 함수 내부로 코드의 제어권이 이동하며, 함수 코드를 평가한다.

  1. 함수 실행 컨텍스트 생성
  2. 함수 렉시컬 환경 생성 -> 요기서 상위 스코프를 결정한다
    1. 함수 환경 레코드 생성
    2. this 바인딩
    3. 외부 렉시컬 환경에 대한 참조 결정

그래서 클로저가 뭔데..

const x = 1;

function outer(){
	const x = 10;
	const inner = () => { console.log(x); }
	return inner
}

const innerFunc = outer();
innerFunc() //10

예제를 통해 보자. 해당 환경에선 중첩 함수로 inner() 가 들어가 있다. 그리고 outer()는 실행되면서 해당 함수를 반환하고 생명주기를 마감하지만, 막상 inner를 실행했을 때는 10이 나온다.

이 이유는 중첩 함수가 외부 함수보다 더 오래 유지되는 경우에 중첩 함수는 이미 생명주기가 종료한 외부 함수의 변수를 참조할 수 있기 때문이다. 이러한 중첩 함수를 클로저라고 부른다.

이러한 클로저의 경우는 계속해서 외부 함수의 렉시컬 환경을 기억하고, 참조하고 있으며, 이는 계속해서 해당 중첩함수가 존재하는 한 유지된다. 해당 렉시컬 환경을 내부 함수가 참조하고 있기 때문에 가비지 컬렉션의 대상이 되지 않기 때문이다.