자바스크립트의 완전히 기본적인 개념만 알던 비교적 과거에, 클로저란 개념은 정말 어렵고 난해했습니다.
하지만 자바스크립트의 근간을 이루는 렉시컬 스코프와 실행 컨텍스트를 이해하니 조금은 클로저란 개념에 가까워진 것 같습니다.
이번 글에서는 앞서 말한 실행 컨텍스트와 렉시컬 스코프의 동작 원리를 세부적으로 설명하면서 클로저를 이해하기 보다는
대략적으로 설명하면서 어색한 개념인 클로저와 조금 가까워지는 그런 글이 됐으면 좋겠습니다. (정리하기 귀찮아서 그런건 진짜 아닙니다.)
클로저 (Closure)
우선, 클로저는 자바스크립트 고유의 특징적인 개념은 아닙니다.
함수를 일급 객체로 취급하는 함수형 프로그래밍 언어와 렉시컬 스코프로 동작하는 언어의 특징을 사용한 일종의 패턴, 개념이라고 생각하시면 될 것 같습니다.
따라서 ECMAScript 사양에는 등장하지 않고, MDN에서 클로저에 대한 정의를 찾아볼 수 있습니다.
클로저는 함수와 그 함수가 선언된 렉시컬 환경과의 조합이다.
- mdn
정의가 조금 난해하고 추상적인 것 같습니다.
우선 mdn 정의의 포함된 렉시컬 환경에 집중하면 좋을 것 같습니다.
자바스크립트는 렉시컬 스코프를 따르는 프로그래밍 언어입니다.
렉시컬 스코프란 뭐냐?
간단하게 말해서 프로그램 내에서 결정되는 식별자의 스코프가 함수의 호출과는 관계 없이 함수를 선언할 때 결정된다는 것 입니다.
함수는 프로그램 내에서 언제든지 호출될 수 있는데, 호출 될 때 함수의 스코프가 결정된다면 동적 스코프가 되겠죠?
자바스크립트의 스코프는 이런 동적 스코프와 반대되는 렉시컬 스코프, 즉 정적 스코프로 동작하는 언어입니다.
그리고 이러한 렉시컬 스코프의 정보가 실행 컨텍스트 내의 렉시컬 환경이라는 컴포넌트에 실재적으로 저장됩니다.
아주 기초적인 클로저 예제 코드를 보면서 부연 설명을 하겠습니다.
const x = 1; // 전역 변수 x
function outer(){
const x = 10; // 함수 내부에서 선언된 지역 변수 x
const inner = function () { console.log(x);} // 함수 지역 변수를 참조하는 함수
return inner; // 해당 함수를 반환
};
const innerFunc = outer();
innerFunc(); // 10
전역에서 함수 선언문으로 선언된 outer 함수를 보겠습니다.
outer 함수 내부에는 지역 변수 x가 있고,
함수 표현식으로 선언된 중첩 함수 inner가 있습니다.
중첩 함수 inner는 외부 함수 outer 함수의 지역 변수인 x를 참조합니다.
outer의 중첩 함수 inner 함수가 outer 함수의 변수 x를 참조할 수 있는 이유는
스코프 체인에 의해서 스코프가 계층적으로 연결되어있기 때문입니다.
inner 함수에서 x를 참조할 때 자기 내부의 x가 없으면
스코프 체인을 통해서 자기 자신의 상위 스코프를 탐색하여, x를 찾아냅니다.
그리고 outer함수를 호출하여 innerFunc에 대입 합니다.
이 때, innerFunc함수를 실행하면 outer 함수의 지역 변수인 x를 출력합니다.
여기서 클로저의 특징이 발생하는 것 입니다.
outer 내의 변수 x는 outer 함수의 지역 변수입니다.
지역 변수의 생명 주기는 함수의 생명 주기와 일치합니다.
그렇기 때문에 outer 함수가 호출 될 때만 지역 변수 x가 유효하고,
외부에서 이를 참조할 수 없는 것 입니다.
하지만 innerFunc를 호출하면, outer 함수의 지역 변수 x = 10을 참조합니다.
어떻게 이러한 일이 이루어지는 것일까요?
이러한 원리는 앞서 말한 렉시컬 환경에 의해서 이루어집니다.
자바스크립트의 스코프는 런타임이 아닌 선언 단계에서 결정된다고 앞서 말씀 드렸죠.
우선 outer함수가 호출되면 inner 함수가 표현식으로 선언되는데,
이 때 inner 함수는 함수 객체의 자신의 코드 내용과 [[Enviroment]] 내부 함수에 outer 함수의 참조값이 저장됩니다.
그리고 outer 함수는 함수가 종료되면서 실행 컨텍스트에서 pop되지만,
inner 함수는 outer 함수의 스코프 참조 값을 가지고 있어, 참조되고 있는 outer 함수의 정보는 가비지 컬렉터에 의해 회수되지 않습니다.
따라서 outer 함수의 정보는 메모리 상에 존재하여 innerFunc는 지역 변수 x를 참조할 수 있지만, 외부에서는 지역 변수 x를 참조할 수 없게되는 것 입니다. 이러한 함수(innerFunc)를 클로저 함수라고 합니다.
이를 간단하게 정리하자면,
외부 함수(outer)보다 중첩 함수(inner)가 더 오래 유지되는 경우 중첩 함수는 이미 생명 주기가 종료한 외부 함수의 변수를 참조할 수 있다. 이러한 중첩 함수 (inner)를 클로져라고 부른다.
라고 정리할 수 있습니다.
클로저는 상태를 안전하게 변경하고 유지하기 위해 사용합니다.
다시 말해 프로그램 내에서 중요하게 사용되는 상태 데이터 변수를 안전하게 은닉하여, 특정 함수에게만 상태 변경을 허용하기 위해 사용합니다. 클로저의 다른 예를 보면서 마무리하겠습니다.
const increase = (function(){
let num = 0;
return function(){
return ++num;
}
}());
console.log(increase()); // 1
console.log(increase()); // 2
console.log(increase()); // 3
클로저의 대표적인 예인 클로저를 이용한 카운터 함수입니다.
해당 함수 내에 num 변수는 외부에서 참조가 불가능하며, 특정한 함수에 의해서만 조작이 가능합니다.
※ 해당 게시글은 위키북스의 모던 자바스크립트 Deep Dive를 참조해서 작성하였습니다.
'WEB > JAVASCRIPT' 카테고리의 다른 글
Promise, 프로미스 (0) | 2024.02.03 |
---|---|
Rest 파라미터, 스프레드 문법 (0) | 2023.04.10 |
this (0) | 2023.02.09 |
var, let, const (0) | 2023.02.07 |
스코프 (렉시컬 스코프, 스코프 체인 등 등) (0) | 2023.02.07 |