클로저(Closure)는 JavaScript에서 매우 중요한 개념 중 하나로, 함수가 선언될 때의 환경(렉시컬 환경)을 기억하고, 그 함수가 나중에 어디서 호출되더라도 해당 환경에 접근할 수 있게 하는 기능입니다. 클로저를 통해 함수는 자신이 생성될 때의 스코프에 있는 변수들에 계속해서 접근할 수 있습니다. 이는 JavaScript의 함수가 "First Class Citizens"으로 간주되기 때문에 가능한 것입니다.
클로저(Closure)는 프로그래밍 언어에서 중요한 개념 중 하나입니다. 특히 함수형 프로그래밍에서 자주 등장하는 개념이죠. 간단히 말해, 클로저는 함수가 생성될 때 그 함수가 생성된 환경을 "기억"하는 함수입니다. 다시 말해서, 클로저는 어떤 함수 내부에서 사용된 외부 변수들에 대한 접근을 가능하게 해주는 기능을 말합니다.
function makeAdder(x) {
return function(y) {
return x + y;
};
}
var add5 = makeAdder(5);
var add10 = makeAdder(10);
console.log(add5(2)); // 7
console.log(add10(2)); // 12
이 예시에서 makeAdder 함수는 함수를 반환합니다. 반환된 함수는 makeAdder 함수의 파라미터 x에 접근할 수 있는 클로저를 형성합니다. makeAdder(5)를 호출하면, x의 값으로 5가 설정된 새로운 함수가 생성됩니다. 이 함수는 어디서 호출되든 x의 값인 5에 접근할 수 있습니다. 이것이 클로저의 핵심 기능입니다.
쉽게 설명하면 클로저는 함수와 그 함수가 만들어질 때 주변 상황을 함께 담아두는 기능을 말해요. 함수가 어떤 외부 변수를 사용하고 있을 때, 그 함수와 변수가 서로 붙어 다니게 되는 거죠.
이렇게 생각해볼까요? 당신이 한 카페의 단골이고, 그 카페에서는 당신이 항상 아메리카노를 주문한다는 걸 기억해둔다고 해보세요. 다음 번에 카페에 갈 때, 직원은 "아메리카노를 드릴까요?"라고 물을 수 있습니다. 여기서 카페 직원이 클로저라고 생각해볼 수 있어요. 왜냐하면, 직원은 당신의 주문(외부 변수)을 기억하고 있으니까요.
프로그래밍에서 클로저의 예를 들어볼게요:
makeAdder)가 있습니다. 이 함수는 또 다른 함수를 만들어 반환하는데, 반환되는 함수는 makeAdder로부터 받은 특정 숫자(x)를 기억하고 있습니다.makeAdder 함수에 5라는 숫자를 주고 호출하면, 새 함수가 만들어지는데 이 함수는 "5를 더하는 일"을 항상 기억합니다.클로저는 특정 기능을 가진 "함수"
즉, 클로저는 함수와 그 함수가 참조하는 외부 변수들의 조합으로 이루어진 특별한 종류의 함수입니다. 이를 통해, 함수가 자신이 생성될 당시의 환경(변수, 상태 등)을 "기억"하고, 그 환경에 접근할 수 있는 기능을 가지게 됩니다.
간단하게 말해서, 클로저는 두 가지를 합니다:
내부 함수가 외부 함수의 변수를 참조할 때: 가장 일반적인 클로저의 생성 상황입니다. 외부 함수가 내부 함수를 반환하고, 이 내부 함수가 외부 함수의 변수를 참조할 경우, 이 내부 함수는 클로저가 됩니다. 이 클로저는 외부 함수의 변수들을 "기억"하고, 외부 함수의 실행이 끝난 후에도 이 변수들에 접근할 수 있습니다.
데이터 은닉과 캡슐화를 위해: 클로저를 사용하여 특정 데이터를 함수 외부로부터 보호하고, 오직 특정 함수를 통해서만 데이터에 접근하도록 할 수 있습니다. 이 경우, 클로저는 private 변수와 같은 역할을 하며, 객체 지향 프로그래밍의 캡슐화와 유사한 개념을 구현할 수 있습니다.
콜백 함수에서: 비동기 작업이나 이벤트 처리와 같이 나중에 호출되는 콜백 함수에서 외부 스코프의 변수를 사용할 때 클로저가 생성될 수 있습니다. 이를 통해, 콜백 함수는 비동기 작업의 결과나 이벤트 발생 시 필요한 추가 정보를 "기억"하고 사용할 수 있습니다.
고차 함수에서 반환될 때: 고차 함수(higher-order function)는 다른 함수를 반환하는 함수입니다. 고차 함수에 의해 반환된 함수가 외부 스코프의 변수를 참조하면, 이 반환된 함수는 클로저가 됩니다. 이를 통해 함수를 동적으로 생성하고, 생성된 함수가 실행 시점에 필요한 정보를 가질 수 있도록 합니다.
이러한 상황들은 클로저가 생성될 수 있는 대표적인 예시들입니다. 클로저를 이용하면, 함수가 자신이 생성될 때의 환경을 "기억"하게 만들어, 프로그래밍에서 매우 유용하게 활용할 수 있습니다.
이 예시는 간단한 카운터 기능을 구현한 것으로, 클로저를 통해 어떻게 내부 상태(변수)를 안전하게 숨기고 관리할 수 있는지 보여줍니다.
function createCounter() {
let count = 0; // `createCounter` 함수의 지역 변수
return function() {
count = count + 1; // 외부 함수의 변수 `count`를 참조
return count;
};
}
// 카운터 인스턴스 생성
const counter1 = createCounter();
const counter2 = createCounter();
console.log(counter1()); // 1
console.log(counter1()); // 2
console.log(counter2()); // 1
console.log(counter2()); // 2
createCounter 함수는 내부 함수(익명 함수)를 반환합니다. 이 내부 함수는 createCounter의 지역 변수 count에 접근합니다.createCounter 함수가 호출될 때마다 새로운 count 변수가 생성되고, 각각의 내부 함수(클로저)는 자신이 생성될 때의 count 변수에 대한 참조를 유지합니다. 이렇게 하여, 각 클로저는 독립적인 count 변수의 상태를 "기억"하게 됩니다.counter1과 counter2는 서로 독립적인 클로저 인스턴스입니다. 따라서 각각 별도의 count 변수를 가지고, 서로에게 영향을 주지 않고 독립적으로 카운트를 증가시킵니다.count 변수의 값이 증가하고, 그 값이 반환됩니다. 외부에서는 count 변수에 직접 접근할 수 없으며, 오직 이 클로저를 통해서만 count 값을 변경하고 조회할 수 있습니다.count 변수는 외부로부터 직접 접근할 수 없으며, 오직 클로저를 통해서만 조작할 수 있습니다. 이는 객체 지향 프로그래밍의 캡슐화와 비슷한 개념입니다.count 변수)를 유지하며, 여러 번 호출되더라도 해당 상태를 기억합니다.