[자바스크립트] 클로저(Closure)란?
클로저의 정의
클로저(Closure)는 함수와 해당 함수가 선언된 렉시컬 스코프(정적 범위)의 조합이다. 외부 함수가 return
문으로 종료된 이후에도, 내부 함수가 여전히 외부 함수의 변수 범위에 접근할 수 있다.
자바스크립트에서는 함수가 생성될 때, 해당 함수가 외부 변수를 참조하면 클로저가 형성된다.
클로저 설명에 앞서, 렉시컬 스코프란?
렉시컬 스코프란, 코드가 작성된 위치를 기준으로 하여 범위가 결정되는 방식이다. 즉, 어디서 호출되었는지가 아니라, 어디서 정의되었는지가 중요하다. 자바스크립트를 포함한 대부분의 현대 프로그래밍 언어는 렉시컬 스코프를 기반으로 하고 있다.
아래 예시 코드에서 init()
은 지역 변수 name
과 함수 displayName()
을 생성하는데, displayName()
은 init()
함수 본문 내에서만 사용할 수 있다. displayName()
은 부모 함수 init()
에서 선언된 변수 name
에 접근하는 것이 가능하다.
이와 같이 중첩된 함수는 외부 범위에서 선언한 변수에도 접근 가능하며, 이것이 바로 렉시컬 스코프의 특징이다.
function init() {
var name = "doi"; // name은 init에 의해 생성된 지역 변수
function displayName() {
// displayName() 은 내부 함수이며, 클로저
console.log(name); // 외부 함수 init()에서 선언된 변수를 사용
}
displayName();
}
init();
예제 코드와 함께 보는 클로저
위의 렉시컬 스코프 예제 코드에서 displayName()
을 init()
함수 바깥으로 return
하여 외부에서 호출 가능한 상태로 만들면, displayName()
은 여전히 init()
내부 변수인 name
을 기억하고 있어 클로저가 된다.
하지만 만약 displayName()
을 init()
의 외부로 반환해 호출하면, init()
은 이미 종료된 상태임에도 불구하고 여전히 name
에 접근할 수 있게 된다. 이처럼 함수가 자신이 선언된 환경(스코프)을 기억하고 있을 때, 이를 클로저라고 한다.
위의 렉시컬 스코프 예제 코드와 아래의 예제 코드는 동일한 실행 결과를 가진다.
function makeFunc() {
const name = "doi";
function displayName() {
console.log(name);
}
return displayName;
}
const myFunc = makeFunc();
myFunc();
아래의 예시 코드를 보자. makeAdder()
는 x
를 받아 새 함수를 반환하고, 반환되는 함수는 다시 y
를 받아 x
와 y
의 합을 반환한다. 이때 반환되는 함수는 x
의 값을 기억하고 있는 클로저가 된다.
이처럼 클로저는 상태를 유지하는 함수를 만들 때 유용하다.
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
왜 전역 변수를 사용하지 않고 클로저를 사용하는가?
전역 변수는 어디서든 접근할 수 있기 때문에 코드의 예측 가능성을 낮추고, 유지 보수를 어렵게 만들 수 있다. 또한 프로그램이 종료될 때까지 메모리에 남아 있기 때문에, 메모리 해제가 어려워 장기적으로는 더 이상 필요하지 않은 메모리를 계속 점유하는 ‘메모리 누수’가 발생할 수 있다.
반면 클로저는 필요한 범위 내에서만 데이터에 접근하게 하며, 상태를 안전하게 유지할 수 있어
모듈화, 정보 은닉, 메모리 관리 등의 측면에서 전역 변수보다 훨씬 더 바람직하다.
구체적으로 어디에 사용할 수 있는가?
클로저는 특정 데이터(렉시컬 스코프), 그리고 그 데이터를 사용하거나 조작하는 함수를 하나로 묶어 준다. 이는 객체지향 프로그램에서 속성과 메소드를 다루는 방식과 매우 유사하다.
하나의 메소드만 있는 단순한 객체처럼 사용할 수 있는 곳이라면, 클로저가 매우 유용할 수 있다. 특히 프론트엔드 자바스크립트에서 자주 쓰이는데, 웹에서는 대부분의 작업이 이벤트 기반이기 때문이다. 예시로, 사용자가 버튼을 클릭하거나 키를 누를 때 실행되는 함수를 콜백 함수라고 하는데, 이러한 콜백 함수 안에 상태 또는 데이터를 기억시켜야 할 때 클로저가 유용하게 쓰인다.
아래의 예시 코드는 웹 페이지의 글자 크기를 조절하기 위해, <body>
태그의 font-size
를 조절하는 버튼 3개를 생성하는 코드이다. 각 버튼에 콜백 함수를 등록하고, 해당 콜백 함수 안에서 클로저를 사용하여 사용자 클릭에 따라 font-size
의 값을 조절하게 된다.
function makeSizer(size) {
return function () {
document.body.style.fontSize = `${size}px`;
};
}
const size12 = makeSizer(12);
const size14 = makeSizer(14);
const size16 = makeSizer(16);
document.getElementById("size-12").onclick = size12;
document.getElementById("size-14").onclick = size14;
document.getElementById("size-16").onclick = size16;
<button id="size-12">12</button>
<button id="size-14">14</button>
<button id="size-16">16</button>