function makeFunc() {
const name = "Mozilla";
function displayName() {
console.log(name);
}
return displayName;
}
const myFunc = makeFunc();
myFunc();
이 코드를 실행하면 makeFunc이 종료되어도 makeFunc의 내부 변수 name을 조회할 수 있다.
var name = 'Tom'
function myName() {
var name = 'Janet';
getName();
}
function getName() {
console.log(name);
}
myName(); // Tom
var name = 'Tom'
function myName() {
var name = 'Janet';
getName();
}
function getName() {
console.log(name);
}
myName(); // Janet
실행 컨텍스트는 복잡한 개념이라 자세히 설명해 보겠다.
실행할 코드에 제공할 환경 정보들을 모아놓은 객체
JS 엔진의 Call Stack에 담는 단위이다
처음 프로그램을 실행하면 전역 컨텍스트(Global Context)가 콜스택에 담긴다.
으로 이루어져 있다.
function outer() {
let a = 1;
function inner() {
console.log(a); // 여기서 a는 어디서 찾을까?
}
inner();
}
해당 예시에서도 스코프 체인이 아래와 같이 연결되어 있기 때문에 inner에서 a를 찾을 수 있는 것이다.
innerContext.LexicalEnvironment
└── outerContext.LexicalEnvironment
└── globalContext.LexicalEnvironment
ES6 이전에는 variable object / Scope chain / thisValue 로 표현했지만 최신 표준(ES6+)에서는 위와 같의 정의한다. (더 세분화)
ES5에서는 Variable Environment는 var, let, const, function 모두 저장했다.
ES6부터 LexicalEnvironment
에는 let
, const
, function
을 저장하고
Variable Environment에는 var 변수를 저장한다
프로그램 실행 초기에는 VariableEnvironment와 LexicalEnvironment가 같은 참조(=객체)를 가리킨다.
이후에 let/const가 보이면 새로운 Record가 생성될 수 있다. (var와 let/const는 선언 시기가 다르다)
시점 | VE와 LE 관계 | var | let/const/function |
---|---|---|---|
실행 컨텍스트 생성 초기 | 같은 객체를 참조함 | ✅ VE에 저장됨 | ❌ 아직 없음 |
let/const/function 선언 시 | LE에만 추가됨 | (VE에 이미 있음) | ✅ LE에만 저장됨 |
실행 단계 이후 | 둘은 구조상 따로 관리됨 (같은 객체였어도!) | var 있음 | let , const 없음 |
변수 검색 시 Lexical Environment에서만 탐색한다.
실행 컨텍스트는 함수가 실행이 종료되면 사라진다.
하지만, 클로저란 함수가 선언될 당시의 Lexical Environment를 기억해서
그 환경에 있는 변수들에 함수가 실행된 이후에도 접근할 수 있게 하는 기능
→ "렉시컬 환경이 GC(Garbage Collection) 대상이 되지 않고 살아남기 때문에" 가능한 기능이다.
function outer() {
let count = 0;
return function inner() {
count++;
console.log(count);
}
}
const fn = outer(); // 여기서 inner 함수가 반환됨
fn(); // 1
fn(); // 2
outer()
실행이 끝나면, 그 안에서 만든 count
변수는 메모리에서 사라져야 함inner()
함수가 count
에 접근하니까outer
의 LexicalEnvironment를 메모리에서 제거하지 않음count
가 살아있는 상태로 유지됨var food = '🍕';
function outer() {
var food = '🍔';
function inner() {
console.log('I like', food);
}
return inner;
}
var myFunc = outer(); // inner 함수 리턴받음
myFunc(); // 호출은 전역에서!
순서 | 설명 | 실행 컨텍스트 스택 상태 |
---|---|---|
1 | 프로그램 시작 | [Global] |
2 | outer() 호출 | [outer, Global] |
3 | outer() 내부 실행 완료 → inner 리턴 | [Global] |
4 | myFunc() 호출 (inner() 실행) | [inner, Global] |
5 | console.log(...) 실행 | 콘솔에 "I like 🍔" |
6 | inner() 종료 | [Global] |
7 | 종료 | [Global] 해제됨 |
outer가 종료되었지만, inner에서 outer를 참조하고 있기 때문에 outer의 실행 컨텍스트는 사라지지 않는다.
크롬에서 해당 코드를 디버깅 해보면
실제 스코프를 볼 수 있다.
outer는 스코프 체인이 local - global 이지만
inner는 스코프 체인이 local - outer - global 이다.
function makeAdder(x) {
return function (y) {
return x + y;
};
}
const add5 = makeAdder(5);
const add10 = makeAdder(10);
console.log(add5(2)); // 7
console.log(add10(2)); // 12
makeAdder는 함수를 만들어 내는 팩토리로 각각 5, 10을 더하는 함수를 만들수 있다. 여기서 클로저는 add5와 add10으로 같은 로직을 공유하지만 서로 다른 환경을 저장한다.const counter = (function () {
let privateCounter = 0;
function changeBy(val) {
privateCounter += val;
}
return {
increment() {
changeBy(1);
},
decrement() {
changeBy(-1);
},
value() {
return privateCounter;
},
};
})();
console.log(counter.value()); // 0.
counter.increment();
counter.increment();
console.log(counter.value()); // 2.
counter.decrement();
console.log(counter.value()); // 1.
이렇게 코드를 구성하면 외부에서 privateCouter 변수와 changeBy 함수에 접근 할 수 없지만 반환된 increment, decrement, value 함수를 통해 값의 증가, 감소, 조회 기능을 실행할 수 있습니다. 만약 여러개의 카운터를 생성한다 하더라도const makeCounter = function () {
let privateCounter = 0;
function changeBy(val) {
privateCounter += val;
}
return {
increment() {
changeBy(1);
},
decrement() {
changeBy(-1);
},
value() {
return privateCounter;
},
};
};
const counter1 = makeCounter();
const counter2 = makeCounter();
console.log(counter1.value()); // 0.
counter1.increment();
counter1.increment();
console.log(counter1.value()); // 2.
counter1.decrement();
console.log(counter1.value()); // 1.
console.log(counter2.value()); // 0.
각각의 클로저는 독립적인 환경을 조성하며, 서로 영향을 미치지 않습니다.// myModule.js
let x = 5;
export const getX = () => x;
export const setX = (val) => {
x = val;
};
import { getX, setX } from "./myModule.js";
console.log(getX()); // 5
setX(6);
console.log(getX()); // 6
정보의 접근 제한(캡슐화)
function outer() {
let getY;
{
const y = 6;
getY = () => y;
}
console.log(typeof y); // undefined
console.log(getY()); // 6
}
outer();
→ 클로저를 할당한 변수에 null을 할당하여 메모리를 해제할 수 있음.