클로저는 함수가 선언될 때의 스코프를 기억하여, 함수가 생성된 이후에도 그 스코프에 접근할 수 있는 기능을 말한다. 비유하자면, 함수가 자신이 생성된 환경을 '기억'하는 것이라고 할 수 있다. 클로저는 자바스크립트의 함수가 일급 객체라는 특성과 렉시컬 스코프의 조합으로 만들어진다.
// 클로저 예시 코드
function outerFunction(outerVariable) {
return function innerFunction(innerVariable) {
console.log('Outer Variable: ' + outerVariable);
console.log('Inner Variable: ' + innerVariable);
};
}
const newFunction = outerFunction('outside');
newFunction('inside');
여기서 innerFunction은 outerFunction의 내부에 정의되어 있다. innerFunction은 자신이 생성된 스코프, 즉 outerFunction의 스코프를 기억하고, outerFunction의 호출이 완료된 이후에도 그 스코프에 접근할 수 있다. 그리고 이에 따라 innerFunction은 outerVariable에도 접근할 수 있다. 이것이 클로저가 동작하는 방식이다.
클로저는 언제 활용하는가?
클로저는 변수와 함수의 접근 범위를 제어하고 특정 데이터와 상태를 유지하기 위해 자주 활용된다. 크게 세 가지 대표적인 사용 사례로 나누어 설명할 수 있다.
첫째, 데이터 은닉에 활용된다. 클로저는 외부에서 접근할 수 없는 비공개 변수와 함수를 만들 수 있다. 이를 통해 데이터를 은닉하여 외부 접근을 막고, 데이터 무결성을 유지할 수 있다. 예를 들어, 특정 함수 내부에서만 접근 가능한 변수를 생성하고, 이를 조작할 수 있는 함수만 외부로 노출하여 안전하게 데이터를 관리할 수 있게된다.
둘째, 비동기 작업에 활용된다. 클로저는 비동기 작업에서 이전의 실행 컨텍스트를 유지해야 할 때 유용하다. 콜백 함수가 비동기적으로 실행될 때 클로저를 사용하면 함수 실행 시점의 변수를 참조할 수 있다.
function createLogger(name) {
return function() {
console.log(`Logger: ${name}`);
};
}
const logger = createLogger('MyApp');
setTimeout(logger, 1000); // 1초 후에 'Logger: MyApp' 출력
위의 예시에서 클로저가 name 변수('MyApp')를 저장하여 1초 후에도 해당 값이 유지되어 출력된다.
셋째, 모듈 패턴을 구현하는 데 활용된다. 모듈 패턴은 특정 기능을 캡슐화하고, 외부에 공개하고자 하는 부분만 선택적으로 노출하여 코드의 응집력을 높이고, 유지보수성을 향상시키는 패턴이다. 클로저를 활용하면 필요한 함수와 데이터만 외부로 노출함으로써 모듈 패턴을 쉽게 구현할 수 있다.
추가적으로 공부해보기
렉시컬 스코프(Lexical Scope)
렉시컬 스코프는 함수를 어디서 호출하는지가 아니라 어디에 선언하였는지에 따라 상위 스코프가 결정되는 것을 말합니다2. 즉, 함수의 상위 스코프는 함수가 정의된 위치에 의해 결정됩니다. 이는 함수가 호출되는 위치와는 무관합니다. 예를 들어
var x = 1;
function first() {
var x = 10;
second();
}
function second() {
console.log(x);
}
first(); // 1
second(); // 1
이 예제에서 second 함수는 전역 스코프에서 선언되었기 때문에, x의 값으로 전역 변수인 1을 참조합니다. 클로저는 함수가 선언된 환경의 스코프를 기억하여, 함수가 스코프 밖에서 실행될 때에도 기억한 스코프에 접근할 수 있게 합니다. 이는 함수의 실행 컨텍스트가 종료된 후에도 해당 환경의 변수에 접근할 수 있음을 의미합니다.
비공개 변수와 함수 생성
클로저를 이용하면 비공개 변수와 함수를 만들 수 있습니다. 이는 모듈 패턴을 통해 구현할 수 있습니다.
const counter = (function() {
let privateCounter = 0;
function changeBy(val) {
privateCounter += val;
}
return {
increment: function() {
changeBy(1);
},
decrement: function() {
changeBy(-1);
},
value: function() {
return privateCounter;
}
};
})();
이 예제에서 privateCounter와 changeBy 함수는 외부에서 직접 접근할 수 없는 비공개 요소입니다.
비동기 작업에서의 클로저 활용
클로저는 비동기 작업에서 특히 유용합니다. 예를 들어, 이미지 다운로드와 같은 비동기 작업에서 클로저를 활용할 수 있습니다.
function downloadImage(url, callback) {
// 이미지 다운로드 로직
setTimeout(() => {
const imageName = url.split('/').pop();
callback(imageName);
}, 3000);
}
function processImage(name) {
console.log(`Processing image: ${name}`);
}
downloadImage('https://example.com/image.jpg', processImage);
이 예제에서 downloadImage 함수는 클로저를 생성하여 url과 callback 함수를 캡처합니다. 비동기 작업이 완료된 후에도 이 정보에 접근할 수 있습니다. 클로저는 JavaScript의 강력한 기능으로, 데이터 은닉, 상태 유지, 모듈화 등 다양한 상황에서 활용될 수 있습니다. 이를 통해 더 안전하고 효율적인 코드를 작성할 수 있습니다.