function outer() {
let outerVar = 'I am from outer';
function inner() {
console.log(outerVar);
}
inner();
}
outer(); // 출력: I am from outer
inner 함수는 outer 함수 내부에 정의되어 있다. inner 함수는 outerVar 변수를 참조할 수 있는데 이는 inner 함수가 선언된 위치가 outer 함수 내부이기 때문이다.
렉시컬 스코핑은 함수가 호출되는 위치가 아닌 정의된(선언된) 위치에 따라 스코프가 결정된다. 이때문에 inner 함수는 자신이 정의된 스코프(즉 outer 함수의 스코프) 내의 변수들에 접근할 수 있다.
클로저란는 주변 상태(어휘적 환경)에 대한 참조와 함께 묶인(포함된) 함수의 조합이다.
정의가 어렵게 되어 있는데 내가 쉽게 이해한 바로는
function outerFunc() {
// 외부 함수의 변수
var x = 10;
// 내부 함수에서 외부 함수의 변수에 접근할 수 있습니다.
var innerFunc = function () {
console.log(x);
};
return innerFunc;
}
var inner = outerFunc();
inner(); // 10
위 코드에서 outerFunc는 내부 함수 innerFunc를 return하고 생명 주기가 끝난 상태이다. (실행 후 콜스택에서 제거되었기 때문)
하지만 inner 함수를 호출하면 내부 함수 innerFunc가 실행되고 innerFunc는 선언된 당시 환경을 기억하고 있기 때문에 변수 x의 값이 10이 출력된다.
이와 같이 생명 주기가 끝난 외부 함수의 변수에 접근할 수 있는 함수를 클로저라고 한다.
- 상태 유지
클로저는 함수가 호출될 때마다 해당 함수의 렉시컬 환경을 기억하기 때문에, 함수 호출 간에 상태를 유지할 수 있다.
function debounce(callback, delay) {
let timer = null;
return function () {
clearTimeout(timer);
timer = setTimeout(() => {
callback.apply(this, arguments);
}, delay);
};
}
- 데이터 은닉과 캡슐화
클로저는 외부에서 직접 접글할 수 없는 비공개 변수를 생성하는 데 유용하다. 이를 통해 데이터 캡슐화가 가능해지고 전역 네임스페이스를 오염시키지 않으면서 데이터를 보호할 수 있다.
function makeCounter() {
let count = 0;
return function() {
count++;
return count;
};
}
const counter = makeCounter();
console.log(counter()); // 1
console.log(counter()); // 2
console.log(counter()); // 3