What is Closure?
A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment). In other words, a closure gives you access to an outer function's scope from an inner function - MDN
클로져란 개념이 일급함수로 인해 파생되기 때문에, 자바스크립트를 처음 접하는 개발자들 중 상당수가 이 개념을 낯설어 합니다. 사실 자연스럽게 사용하고 있지만, 인지 못하는걸 수 있습니다. MDN의 정의에 따르면, 클로져가 내부함수에서 외부함수로의 접근을 허용하게 한다 합니다.
음? 저는 가장 먼저 떠오르는게, 전역 변수네요.
const globalVariable = 3;
function outerFunc(msg){
const varInsideOuterScope = 1;
function innerFunc(msg2){
console.log(varInsideOuterScope , globalVaribale); // 1 3
}
}
단, lexical environment(문맥 환경)을 기준으로 클로져가 생성된다고 합니다. 이게 대체 뭘까요?
Lexical Environment
문맥 환경이란, 말그대로, 물리적으로 코드의 위치, 즉 전적으로 개발자에 의해 결정되는 환경입니다.
이렇게 말하면 잘 이해가 안가니, 코드를 직접 보면서 이야기하자면,
const tmpVar = 'hello';
function aFunctionPlacedOnRootLevel(){ // 얘는 root level에 선언
function funcPlacedInsideRootLevelFunc1(){ // 얘는 root level -> lev 1 에 선언
function funcPlacedInsideLevel1Func(){ // 얘는 root -> 1 -> lev 2 에 선언
}
}
function funcPlacedInsideRootLevelFunc2(){ // 얘는 root -> lev 1 에 선언
}
}
말 그대로, 개발자가 어떻게 선언하느냐에 따라 달라집니다.
아! 그러면 결국, 클로져는 대충 블록 안에 있는것 끼리 대충 이러쿵 저러쿵 같은 scope환경에서 함수나 객체가 선언되거나 하면, 외부 환경을 참조할 수 있군! 맞는 말입니다만, 아직 클로져에 대해 명확하기 설명하지 못하기에, 동작원리에 대해 알아보겠습니다.
Clousure : Execution Context
그 악명 높은 클로져도, execution context관점에서 이야기 해보면 굉장히 당연하게 느껴집니다.
아래의 예시로 설명해보겠습니다.(MDN 예시)
function makeSizer(size) {
return function () {
document.body.style.fontSize = `${size}px`;
};
}
const size12 = makeSizer(12);
const size14 = makeSizer(14);
const size16 = makeSizer(16);
size12()
자바스크립트 컴파일러 기준 먼저 해당 파일을 실행시킵니다.
1. Global Execution Context(GEC)가 생성됩니다.
- GEC의 생성시점 때,
-- 1) function :: makeSizer로의 메모리 할당이 일어납니다.
-- 2) size12 , size 14 , size 16의 호이스팅 (메모리 할당 - undefined)
-- 3) Global Object (window | Global)이 생성됩니다
-- 4) Global Object 로의 참조 'this'가 생성됩니다
-- 5) varibale environment가 생성됩니다
2. GEC가 실행됩니다
3. makeSizer(12)의 호출이 일어납니다
- 해당 과정에서 makeSizer(12)에 대한 execution context가 생성됩니다.
-- 1) 외부 환경(outer environment)으로의 참조가 생성됩니다. (상위 외부 환경 : global)
-- 2) object 'this'가 생성됩니다
-- 3) variable environment가 생성됩니다.
-- 4) 해당 execution context생성 시, 익명 함수의 메모리 할당이 일어납니다
-- 5) argument 'size'의 메모리 할당이 일어나고 해당 size는 variable environment에 놓입니다.
- 익명 함수를 반환 합니다.
- 해당 execution context가 execution stack에서 제거됩니다.
4. makeSize(14)의 호출이 일어납니다.
-- 3과 동일
5. makeSize(16)의 호출이 일어납니다.
-- 3과 동일
6. size12() 가 호출 됩니다.
- 해당 과정에서 size12에 대한 execution context가 호출 됩니다.
- 외부 환경으로의 참조를 통해 size : 12px를 참조합니다. (Clousure) (해당 함수가 상위 스코프의 size: 12px를 참조하고 있기에, GC에 의해 수거되지 않습니다. 그렇기에, 비록 execution context(makeSize(12))가 제거되었더라도 참조할 수 있습니다.
In Practical
// umd module
// 여러 모듈들이 쓰는 패턴인데, 이왕 보시는김에 한번 이게 뭔지 맛보면 좋을거 같아 예시로 삼았습니다.(조금 과한 느낌이 없지않아있지만..)
// 보는 순서 (숫자를 따라가셔요)
(function(gObj, factoryFunc){
if (typeof define === 'function' && define.amd) {
define(['module1' , 'module2'] , factoryFunc);
}else if(typeof module === 'object' && module.exports){
//3. factory 함수 시행 결과로, sum함수가 module.exports에 반환됨
module.exports = factory([require('module1'),require(
'module2'
)]);
//4. 이후 import (sum) -> sum 시행시, 클로져로 인한 dep1 ,dep2 참조가능
}else{
gObj.annonymousName = factoryFunc([gObj.module1, gObj.module2])
}
})(typeof self !== 'undefined' ? self : this , function(...dependency){
//1. 함수 정의부
const [dep1 , dep2] = dependency
function sum(arg1 , arg2){
//2. dep1 , dep2에 대한 참조
return dep1(arg1) + dep2(arg2)
}
return {
sum
}
})
'Javascript' 카테고리의 다른 글
[Javascript] Symbol (0) | 2023.03.09 |
---|---|
[Javascript] Prototype (프로토타입) (0) | 2023.03.08 |
[Javascript] Factory function (팩토리 함수) (0) | 2023.03.02 |
[Javascript] IIFE(즉시 실행 함수 표현) (0) | 2023.01.02 |
[Javascript] function overloading(함수 오버로딩) (0) | 2022.12.22 |