자바스크립트를 좀 더 깊게 파다 보면 '클로저(Closure)'라는 개념을 만나게 되죠. 뭔가 좀 어려운 용어 같기도 하고... 이게 도대체 뭘까요? 🤔
클로저는 자바스크립트의 아주 흥미로운 특징인데요, 함수 안에서 만든 또 다른 함수(내부 함수)가 바깥 함수의 변수나 매개변수에 접근할 수 있게 해주는 거예요. 더 신기한 건, 내부 함수는 자기가 태어난 환경(외부 함수)의 변수들을 마치 사진 찍듯이 기억하고 있다가, 외부 함수 실행이 끝나고 사라진 뒤에도 계속 그 변수들을 사용할 수 있다는 점이에요! ✨
클로저는 기본적으로 함수와 그 함수가 태어난 환경(렉시컬 환경)의 조합이라고 생각하면 이해하기 쉬워요. 자바스크립트는 함수가 어디서 선언되었는지를 기준으로 사용할 수 있는 변수를 결정하는데 (이걸 렉시컬 스코핑이라고 해요), 클로저는 바로 이 특징 덕분에 가능하답니다.
함수가 다른 함수 안에서 정의되고, 그 내부 함수가 밖으로 반환될 때 마법 같은 일이 일어나요. 내부 함수는 마치 '기억 가방'🎒을 챙겨서 나가는데요, 이 가방 안에는 자기가 태어난 환경(외부 함수)의 변수 정보들이 고스란히 담겨 있어요.
이렇게 함수와 그 함수의 '기억 가방'(스코프 정보)을 합쳐서 부르는 게 바로 클로저예요. 내부 함수가 외부 함수의 지역 변수를 참조할 때 클로저가 만들어지고, 외부 함수가 실행을 마친 후에도 내부 함수는 이 '기억 가방' 덕분에 외부 함수의 변수에 계속 접근할 수 있는 거죠.
말로만 들으면 좀 헷갈릴 수 있으니, 클로저를 활용한 간단한 카운터 예제를 한번 볼게요. 이게 클로저의 강력함을 보여주는 대표적인 예시거든요!
// 카운터를 만들어주는 함수
function createCounter(initValue) {
let value = initValue; // 외부 함수의 지역 변수 (이걸 기억할 거예요!)
// 내부 함수들 정의
function plusOne() {
value += 1; // 외부 함수의 value에 접근!
}
function minusOne() {
value -= 1; // 외부 함수의 value에 접근!
}
function changeValue(_value) {
value = _value; // 외부 함수의 value에 접근!
}
function getValue() {
return value; // 외부 함수의 value에 접근!
}
// 내부 함수들을 객체에 담아서 밖으로 내보내기 (반환)
// 이 함수들이 바로 클로저가 되는 거예요!
return {
plusOne,
minusOne,
changeValue,
getValue
};
}
// createCounter 함수를 실행해서 카운터 객체를 얻어요.
// 이때 createCounter 함수 자체는 실행이 끝났지만...
const { plusOne, minusOne, changeValue, getValue } = createCounter(0); // 초기값 0
console.log(getValue()); // 0 (초기값 잘 나오죠?)
plusOne(); // 내부 함수 plusOne 실행! createCounter의 value를 1 증가시켜요.
console.log(getValue()); // 1 (오! 값이 유지되고 바뀌었네요?)
changeValue(23); // 내부 함수 changeValue 실행! value를 23으로 바꿔요.
console.log(getValue()); // 23 (잘 바뀌었죠?)
createCounter
함수는 실행이 끝나서 사라졌는데도, 반환된 plusOne
, minusOne
, changeValue
, getValue
함수들은 여전히 createCounter
안에 있던 value
변수를 기억하고 접근할 수 있어요. 이게 바로 클로저의 힘이죠! 마치 value
변수가 이 함수들만의 비밀 변수가 된 것 같아요. 🤫
클로저의 이런 특징 덕분에 정말 다양한 곳에서 유용하게 활용될 수 있어요.
데이터 은닉 (Data Hiding)
위 카운터 예제처럼, 외부에서 직접 접근할 수 없는 '비공개 변수'를 만들 수 있어요. 객체 지향 프로그래밍의 캡슐화랑 비슷하죠? value
변수는 오직 반환된 함수들을 통해서만 제어할 수 있어요.
상태 유지 (State Maintenance)
함수가 실행될 때마다 초기화되지 않고, 이전 상태를 기억해야 할 때 클로저를 유용하게 쓸 수 있어요. 전역 변수를 쓰지 않고도 상태를 안전하게 관리할 수 있죠.
코드 모듈화 (Code Modularization)
관련된 함수와 데이터를 묶어서 독립적인 모듈처럼 만들 수 있어요. 재사용성도 높아지고요!
"혹시 화살표 함수( =>
)를 쓰면 클로저가 다르게 동작하나요?" 라고 생각할 수도 있는데요, 아니에요! 화살표 함수에서도 클로저는 똑같이 적용된답니다. 화살표 함수도 자기가 태어난 환경(렉시컬 스코프)을 기억하는 렉시컬 스코핑 규칙을 따르거든요. (물론 this
키워드 동작 방식은 좀 다르지만, 클로저 자체는 같아요!)
// 화살표 함수로 곱셈 함수 만들기
let multiplier = factor => { // 외부 함수 (화살표 함수)
// 내부 함수 (화살표 함수) 반환! 이 함수가 클로저가 돼요.
return number => {
// 외부 함수의 factor 변수를 기억하고 접근해요!
return number * factor;
};
};
let double = multiplier(2); // factor가 2인 클로저 함수 생성!
let triple = multiplier(3); // factor가 3인 클로저 함수 생성!
console.log(double(5)); // 10 (5 * 2)
console.log(triple(5)); // 15 (5 * 3)
multiplier
함수가 반환하는 내부 화살표 함수는 multiplier
가 실행될 때 전달받은 factor
값을 정확히 기억하고 있죠? 클로저는 함수의 형태(function
이든 =>
든)와 상관없이 잘 동작해요!
클로저는 자바스크립트의 정말 중요하고 강력한 개념 중 하나예요. 처음엔 조금 어렵게 느껴질 수 있지만, 함수가 자신이 태어난 환경을 '기억'한다는 핵심 아이디어를 이해하면 코드를 보는 눈이 확 달라질 거예요!
혹시 면접 같은 곳에서 클로저에 대해 물어본다면, 이렇게 한번 설명해보세요. "클로저는 함수 안의 다른 함수가 바깥 함수의 변수들을 기억해서 계속 사용할 수 있게 해주는 기능이에요!" 라고요. 😉
클로저를 잘 활용하면 데이터를 안전하게 숨기고, 상태를 깔끔하게 관리하고, 코드를 더 효율적이고 모듈적으로 만들 수 있답니다. 꼭 한번 직접 코드로 만들어보면서 클로저와 친해져 보세요! 👍