클로저란?
함수와 그 함수의 렉시컬 환경과의 조합이다.
렉시컬 환경이란 뭘까요?
이전에 살펴보았듯이 함수는 실행되기전 실행 컨텍스트를 생성합니다.
실행 컨텍스트는 렉시컬 환경에다가 해당 함수의 변수, 함수 식별자들을 정의하고 값을 저장하며, 외부 렉시컬환경에 참조할수 있도록 외부 렉시컬 환경도 저장해 둡니다. 이로써 함수는 자신의 필요한 변수나, 함수, 클래스 들을 찾을 수 있었죠.
그럼 왜 클로저랑 관련이 있을까요? 한번 예시를 봐봅시다.
아래 코드는 어떤 값을 출력할까요?
const x = 999999;
function outerFun() {
const x = 1;
function innerFun() {
console.log(x);
}
return innerFun;
}
const innerFun = outerFun();
innerFun(); // 1?? 999999?? 뭐가 나올까요?
정답은 1입니다.
한번 순서대로 진행 과정을 자세히 알아봅시다.
우선 자바스립트 엔진이 1번째 줄에 왔i을 때에요. 이때 전역 실행 컨텍스트를 생성하고 렉시컬환경에 식별자들을 등록해주 겠죠? ( 선역적 환경 레코드, 객체 환경 레코드 등 자세한 저장 공간 이름들은 생략하고 렉시컬 환경에 등록된다고 할게요 너무 복잡해져서요..)
그 후 자바스크립트 엔진은 아래 사진과 같은 위치에 왔을 때 등록된 식별자들에게 값을 할당합니다. 맞죠?
(여기서 innerFun은 함수가 실행되기 전 이므로 const의 특성에 따라 아직 값이 할당 되기 전입니다.)
그리고 자바스크립트 엔진은 outerFun 함수를 호출하기전 outerFun 실행 컨텍스트를 만들고 outerFun 렉시컬 환경을 만듭니다. 아래 사진처럼요
그 다음 outerFun함수가 실행을 마치고 나면 outerFun 실행컨텍스트는 콜 스택에서 제거 될 거에요. 그 후 innerFun 변수에는 outerFun함수가 반환한 innerFun 함수가 할당 되고요. 맞죠? 근데 렉시컬 환경은 어떻게 될까요? 그림으로 봅시다.
outerFun 실행 컨텍스트가 실행을 마치고 제거 된 후에도 outerFun 함수의 렉시컬 환경은 남아 있네요???
그리고 자바스크립트 엔진은 innerFun 함수를 실행 전입니다. 왜 그럴까요? 현재 렉시컬 환경의 상태를 봐봅시다.
약간 복잡해 보이실수도 있는데, 어렵지 않습니다. 원래 함수 객체에는 [[Environment]] 라는 내부 슬롯이 있는데 이곳에 함수가 정의가 평가 될 때, 즉 함수가 실행되기전 자바스크립트 엔진에 의해 평가 될 때, 해당 함수의 상위 스코프를 (렉시컬 스코프)를 [[Environment]] 에다가 저장합니다. 아래 그림에서 outerFun 실행 컨텍스트가 콜 스택에서 제거 되어도 innerFun 라는 변수는 outerFun 함수의 실행 결과인 innerFun 함수를 참조 하며 이 innerFun 함수는 자신의 렉시컬 환경인 outerFun의 스코프를 참조 하기 떄문에 outerFun의 렉시컬 환경은 제거 되지 않습니다.
계속해서 자바스크립트 엔진이 이제 InnerFun 함수를 실행 시키면 렉시컬 환경을 만들고 innerFun의 [[Environment]]라는 내부 슬롯에 저장 되어있는 렉시컬 스코프를 참조하여 외부 렉시컬 환경 참조에 등록하고 innerFun 함수 안에는 x라는 식별자가 없으니 스코프 체인으로 인해 상위 렉시컬 환경에서 찾아 console.log(x)를 실행하게 되어 x는 1이 출력 되게 됩니다.
이후에는 더 이상 실행시킬 코드가 없어서 콜 스택에 순차적으로 제거되고 종료되게 됩니다.
후 실행과정을 그림과 코드와 함께 설명하려니깐 어렵네요… 어때요? 재밌지 않나요??
한번 요약해 봅시다. 흠.. outerFun 함수를 실행 시켜서 자기 자신 안에 정의되어있는 innerFun 함수를 반환하고 자신의 생명주기를 끝냈어도 자기 자신(outerFun)안에서 작성한 함수를 반환하고 그 반환된 함수(innerFun)이 자기 자신(outerFun)의 스코프를 참조를 하고 있으면 실행컨텍스트에서 제거 되어도 렉시컬 환경에는 남아 있네요. 그렇기 떄문에 innerFun함수는 outerFun의 x 라는 변수에 접근을 하게 될 수 있었던 거구요!
우리는 이러한 실행 과정을 알아봄으로써 “함수와 렉시컬 환경의 조합”이라는 말의 뜻을 알수 있게 되었습니다.(짝짝) 고생하셨습니다!. 이제 조금만더 힘냅시다.
우리는 어려운 개념 클로저를 알 수 있게 되었습니다. 그럼 이 복잡한 클로저를 이용하면 어떤 이점이 있을까요?
간단한 예제로 한번 봐봅시다. 억지스럽지만 너그럽게 봐주십쇼 ^^
우리는 하나의 가정을 해봅시다. 길 한복판에 싸구려 돈 가방을 두고 기부해달라고 써 놓고 지켜봐볼게요
그리고 코드로 나타내보겠습니다. 처음에는 1000원, 2000원 기부가 되더니 누군가 돈을 훔쳐버렸네요;; ㅠㅠ
const moneyBag = {
money: 0,
};
// 1000원 기부
moneyBag.money += 1000;
console.log(moneyBag.money); // 1000
// 2000원 기부
moneyBag.money += 2000;
console.log(moneyBag.money); // 3000
//누군가 돈을 훔침
moneyBag.money = 0;
console.log(moneyBag.money); // 0
흠.. 누가 훔쳐가 버리니 안전한 돈 가방으로 바꿔야 될 것 같네요 바꿔봅시다.
돈 가방을 안전한 돈 가방으로 바꿨습니다. 하지만 이번 돈 가방은 돈을 넣을 수가 없는 구조네요… 안전하긴 하지만 돈을 넣거나 뺄 수가 없어서 쓰레기나 똑같습니다. 다시 기능좋은 안전한 돈 가방으로 바꿔오겠습니다.
function safeMoneyBag() {
let money = 0;
return money;
}
// 돈을 넣을 수가 없음
console.log(safeMoneyBag());
console.log(safeMoneyBag());
기능좋은 안전한 돈가방으로 바꿔왔습니다.
코드의 결과를 보니 기부를 해도 누군가가 돈을 가져갈 수가 없네요! 돈 가방은 돈을 안전하게 보관하고 있으니 아무나 함부로 기부된 돈을 건들수가 없습니다! 우리는 안전하게 돈을 기부 받을 수가 있게 되었어요.
function functionalSafeMoneyBag() {
let money = 0;
function donate(val) {
money += val;
console.log(`${val}원이 기부됨, 현재 누적 금액: ${money}`);
}
return donate;
}
const donate = functionalSafeMoneyBag();
// 1000원 기부
donate(1000);
// 2000원 기부
donate(2000);
// 2000원 기부
donate(2000);
// 2000원 기부
donate(2000);
억지스로운 시나리오였지만 마지막 기능 좋은 안전한 돈 가방 코드를 보면 클로저가 보이시나요?
functionalSafeMoneyBag 함수의 실행이 끝나면 donate라는 함수를 반환합니다. 여기서 우리는 money를 직접적으로 수정할 수는 없지만 donate라는 함수를 반환했기 떄문에 이 함수로 돈을 기부할 수 있게 되었죠.
functionalSafeMoneyBag함수의 생명주기는 끝났지만(함수를 호출하고 실행이 끝났지만) donate 함수와 렉시컬 환경으로 인해 functionalSafeMoneyBag의 렉시컬 환경은 사라지지 않아서 money에 접근 할 수 있네요!
이렇게 우리는 간단한 예제를 봄으로써 클로저를 알 수 있게 되었습니다. 클로저는 아주 강력한 기능입니다. 하지만 하나 인지하고 계셔야 할 사항이 있습니다. 클로저를 형성한다는 것은 렉시컬 환경을 유지한다는 것이고 이것은 메모리를 사용한다는 것입니다. 다행히도 자바스크립트 엔진은 똑똑해서 외부 렉시컬 환경 참조를 안하게 되면 가비지 컬렉션(청소기라고 생각합시다 일단.)이 자동으로 메모리에서 제거합니다. 그래도 클로저를 너무 남발하게 되면 메모리를 사용한다는 것이니 조금 생각하면서 사용해야 됩니다.
이렇게 클로저 파트가 마무리 되었습니다. 다음에는 재밌는 파트로 찾아 뵙겠습니다. 감사합니다
'언어 > 자바스크립트' 카테고리의 다른 글
실행 컨텍스트(렉시컬 스코프, 렉시컬 환경, 스코프 체인, 호이스팅) 이 글로 끝내버리기 (0) | 2023.05.13 |
---|---|
BigInt 큰 숫자 다루기 (0) | 2023.04.12 |
자바스크립트 This 이걸로 끝내기 (0) | 2023.03.28 |
Iteration Protocol(for of, spread) (0) | 2023.03.21 |
이벤트 캡처링과 버블링 (0) | 2023.03.06 |