[JavaScript] 클로저

olwooz·2023년 1월 7일
0

JavaScript

목록 보기
3/5

클로저

A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment).
클로저는 함수와 그 함수가 선언된 렉시컬 환경과의 조합이다.

클로저는 JavaScript의 고유 개념이 아니라, 함수를 일급 객체로 취급하는 함수형 프로그래밍 언어의 중요한 특성이다.
enclosed - '에워싸인' 이라는 의미인데, 함수가 자신 외부에 존재하는 식별자들과 함께 하나로 감싸져 사용된다고 생각하면 closure라는 이름이 조금 더 이해가 갈 것이다.
이 글은 클로저를 이해하기 위해 알아야 하는 개념들, 클로저의 원리, 클로저의 활용을 다룬다.

렉시컬 스코프

JavaScript 엔진은 함수를 어디서 호출했는지가 아니라 어디에 정의했는지에 따라 상위 스코프를 결정한다. 이 특성을 렉시컬 스코프 또는 정적 스코프라고 부른다.

함수 객체의 내부 슬롯 [[Environment]]

렉시컬 스코프가 가능하려면 상위 스코프를 기억할 필요가 있다.
함수는 정의가 평가되고 객체가 생성될 때 자신의 내부 슬롯 [[Environment]]에 현재 실행 중인 실행 컨텍스트의 렉시컬 환경을 상위 스코프, 즉 외부 렉시컬 환경에 대한 참조로 저장한다.

클로저와 렉시컬 환경

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

const x = 1;

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

const innerFunc = outer(); // 3.
innerFunc(); // 4. (x === 10)

위 코드의 실행 순서는 다음과 같다.

  1. outer 함수 평가, 함수 객체 생성 → outer[[Environment]]에 전역 렉시컬 환경 저장
  2. inner 평가 (함수 표현식이므로 런타임에 평가) → inner[[Environment]]outer의 렉시컬 환경 저장
  3. outer 생명 주기 종료, 실행 컨텍스트 스택에서 제거되지만 렉시컬 환경은 소멸하지 않음, outer 함수의 렉시컬 환경 → inner 함수의 [[Environment]] 내부 슬롯에 의해 참조 → 전역 변수 innerFunc에 의해 참조되고 있기 때문에 가비지 콜렉션의 대상이 아니기 때문
  4. inner 호출 → inner 함수의 실행 컨텍스트 생성 & 스택에 push, outer의 생존 여부와 상관 없이 outer 내부 식별자 참조 & 값 변경 가능

클로저의 본질은 생명 주기가 종료된 외부 함수의 식별자를 참조할 수 있다는 것이다. 따라서 보통 중첩 함수가 상위 스코프의 식별자를 참조하고 있고 외부 함수보다 더 오래 유지되는 경우를 클로저라고 한다.
이론적으로 모든 함수는 클로저지만 일반적으로 상위 스코프의 식별자를 참조하지 않는 경우에는 메모리 낭비를 막기 위해 모던 브라우저에서 최적화를 통해 상위 스코프를 기억하지 않기 때문에 이런 경우는 클로저가 아니라고 볼 수 있다.
또한 상위 스코프의 식별자를 참조하지만 외부 함수보다 일찍 소멸되는 경우에도 클로저라고 하지 않는다.

클로저의 활용

클로저는 주로 상태를 안전하게 변경하고 유지하기 위해 사용된다. 의도치 않은 상태 변경을 막기 위해 특정 함수에게만 상태 변경을 허용한다.

const increase = (function() {
	let num = 0;

	return function() {
		return ++num;
	};
}());

console.log(increase()); // 1
console.log(increase()); // 2
console.log(increase()); // 3

클로저로 숫자를 증가시키는 함수를 구현한 모습이다.

위 코드에서 즉시 실행 함수가 호출되면 즉시 실행 함수가 반환한 함수가 increase 변수에 할당된다. 이때 increase 변수에 할당된 함수가 바로 즉시 실행 함수의 렉시컬 환경을 기억하는 클로저이다. 즉시 실행 함수는 호출 이후 소멸하지만 클로저increase 변수에 할당되어 호출되고, 언제 어디서 호출하든지 num을 참조하고 변경할 수 있다.

즉시 실행 함수는 한 번만 실행되기 때문에 increase가 여러 번 호출된다고 해서 num이 초기화되지 않고, num은 외부에서 직접 접근할 수 없는 변수이기 때문에 의도치 않은 변경이 일어나지 않는다.

세 줄 요약

  1. 클로저는 외부 함수의 식별자를 참조하며 외부 함수보다 오래 유지되는 중첩 함수다.
  2. 클로저는 JavaScript가 렉시컬 스코프를 따르기 때문에 발생한다.
  3. 클로저는 상태를 안전하게 변경하고 유지하기 위해 사용된다.

0개의 댓글