Closure(클로저)
클로저?
- 클로저는 자바스크립트의 고유 개념이 아니다. 함수를 일급 객체로 취급하는 함수형 프로그래밍 언어(예: 하스켈, 리스프, 얼랭, 스칼라 등)에서 사용되는 중요한 특성이다.
- 클로저는 ESCMAScript 사양에 등장하지 않는다. 하지만 MDN에서 다음과 같이 정의하고 있다.
- 클로저는 ‘둘러싸여진 상태의 참조’와 함께 다발로 묶여진 함수의 콤비네이션이다. 바꿔 말하면, 클로저는 내부 함수로부터 외부함수에의 접근권한을 준다. 클로저는 함수 생성 시점에 언제나 생긴다.
렉시컬 스코프
-
자바스크립트 엔진은 함수를 어디서 호출했는지가 아니라 함수를 어디에 정의했는지에 따라 상위 스코프를 결정한다. 이를 렉시컬 스코프(정적 스코프)라 한다.
const x = 1; function foo() { const x = 10; bar(); } // foo 와 전역컨텍스의 사이의 클로저가 생성 function bar() { console.log(x); } // bar 와 전역컨텍스의 사이의 클로저가 생성 foo(); // ? => 1 bar(); // ? => 1
-
foo, bar 함수는 모두 전역에서 정의된 함수이다. 함수의 상위 스코프는 어디서 정의했느냐에 따라 결정되므로 foo 함수와 bar 함수의 상위 스코프는 전역이다. 렉시컬 환경의 “외부 렉시컬 환경에 대한 참조” 에 저장할 참조값, 즉 상위 스코프에 대한 참조는 함수 정의가 평가되는 시점에 함수가 정의된 환경(위치)에 의해 결정된다.
함수 객체의 내부 슬롯 [[Environment]]
- 함수는 자신의 내부 슬롯 [[Environment]]에 자신이 정의된 환경, 즉 상위 스코프의 참조를 저장한다.
- 이는 “외부 렉시컬 환경에 대한 참조”에 저장될 참조값이다.
클로저와 렉시컬 환경
-
외부 함수보다 중첩 함수가 더 오래 유지되는 경우 중첩 함수는 이미 생명 주기가 종료한 외부 함수의 변수를 참조할 수 있다. 이러한 중첩 함수를 클로저라고 부른다.
- 함수를 반환하는 함수여야 한다.
- 반환된 함수는 외부함수의 변수를 참조해야 한다. (클로저는 내부함수에서 참조하고 있는 변수만을 남겨둔다.)
const x = 1; function outer() { const x = 10; const inner = function () { console.log(x); }; return inner; } // outer 함수를 호출하면 중첩 함수 inner를 반환한다. // 그리고 outer 함수의 실행 컨텍스트는 실행 컨텍스트 스택에서 팝되어 제거된다. const innerFunc = outer(); innerFunc(); // 10
-
클로저는 중첩 함수가 상위 스코프의 식별자를 참조하고 있고 중첩 함수가 외부 함수보다 더 오래 유지되는 경우에 한정하는 것이 일반적이다.
클로저의 활용
-
클로저는 상태를 안전하게 변경하고 유지하기 위해 사용한다. 다시 말해 상태가 의도치 않게 변경되지 않도록 상태를 안전하게 은닉하고 특정 함수에게만 상태 변경을 허용하기 위해서이다.
// 카운트 상태 변경 함수 const increase = (function () { // 카운트 상태 변수 let num = 0; // 클로저 return function () { // 카운트 상태를 1만큼 증가 시킨다. return ++num; }; })(); console.log(increase()); // 1 console.log(increase()); // 2 console.log(increase()); // 3
-
클로저 활용 예시
// 함수를 반환하는 고차 함수 // 이 함수는 카운트 상태를 유지하기 위한 자유 변수 counter를 기억하는 클로저를 반환한다. const counter = (function () { // 카운트 상태를 유지하기 위한 자유 변수 let counter = 0; // 함수를 인수로 전달받는 클로저를 반환 return function (aux) { // 인수로 전달 받은 보조 함수에 상태 변경을 위임한다. counter = aux(counter); return counter; }; })(); // 보조 함수 function increase(n) { return ++n; } // 보조 함수 function decrease(n) { return --n; } // 보조 함수를 전달하여 호출 console.log(counter(increase)); // 1 console.log(counter(increase)); // 2 // 자유 변수를 공유한다. console.log(counter(decrease)); // 1 console.log(counter(decrease)); // 0
커링
- 여러 전달인자를 가진 함수를 함수를 연속적으로 리턴하는 함수로 변경하는 행위
- 함수의 일부만 호출하거나, 일부 프로세스가 완료된 상태를 저장하기에 용이
function createFoodRecipe (foodName) {
const getFoodRecipe = function (ingredient1, ingredient2) {
return `${ingredient1} + ${ingredient2} = ${foodName}!`;
}
return getFoodRecipe;
}
const highballRecipe = createFoodRecipe('하이볼');
highballRecipe('콜라', '위스키'); // '콜라 + 위스키 = 하이볼!'
highballRecipe('탄산수', '위스키'); // '탄산수 + 위스키 = 하이볼!'
highballRecipe('토닉워터', '연태고량주'); // '토닉워터 + 연태고량주 = 하이볼!'
Content Table
- 클로저?
- - 렉시컬 스코프
- - 함수 객체의 내부 슬롯 [[Environment]]
- - 클로저와 렉시컬 환경
- 클로저의 활용
- - 커링