MDN에서 클로저의 정의를 보면 다음과 같이 설명하고 있다.
"클로저는 함수와 함수가 선언된 어휘적 환경의 조합이다."
쉽게 말해서 클로저란 함수가 선언되었을 때, 선언된 시점의 환경을 기억한다는 것이다. 여기서 환경은 외부 변수에의 접근을 의미한다. 예시를 보면 쉽게 이해할 수 있다.
const multiple = (value) => {
let times = value;
return function (newValue) {
return times * newValue;
};
};
const tenTimes = multiple(10);
console.log(tenTimes(15)); //150
const fiveTimes = multiple(5);
console.log(fiveTimes(5)); //25
multiple 함수는 value를 받아 새로운 함수를 반환하는 고차함수다. 새로운 함수는 newValue를 받아, 이전에 받아온 value값을 저장한 times라는 변수와 곱한 값을 리턴하고 있다.
코드를 보면, value에 10을 넣어 반환된 temTimes라는 함수가 있다. 이미 multiple 함수는 종료되었음에도 tenTimes 함수가 생성되었을 때 'times = 10' 라는 환경을 tenTimes가 기억하고 있고, 그때문에 temTimes(15)를 실행해보면 150을 반환하고 있음을 볼 수 있다.
마찬가지로 multiple(5)를 통해 fiveTimes를 만들어보면, fiveTimes가 'times = 5'라는 환경을 기억하고 있기 때문에 fiveTimes(5)는 25를 반환하고 있다.
이처럼 함수가 선언되었을 당시의 환경을 기억하는 것을 클로저라고 부른다.
클로저와 유사한 개념으로 커링이라는 개념이 있다. 사실 바로 위에 클로저를 설명하기 위해 예시로 들었던 코드가 바로 커링이라고 볼 수 있다. javascript.info라는 사이트에서 커링에 대한 설명을 보면 아래와 같이 설명한다.
커링은 f(a, b, c)처럼 단일 호출로 처리하는 함수를 f(a)(b)(c)와 같이 각각의 인수가 호출 가능한 프로세스로 호출된 후 병합되도록 변환하는 것입니다.
가장 간단한 예시로 3개 숫자의 곱을 들 수 있다.
const multiple = (a,b,c) =>{
return a*b*c
}
console.log(multiple(5,6,7)); //210
function curryingMultiple(a){
return function(b){
return function(c){
return a*b*c
}
}
}
console.log(curryingMultiple(5)(6)(7)); //210
이렇게 한번에 n개의 인수를 받아 실행하는 함수를 각 인수를 받을 때마다 새로운 함수를 반환하는 형태로 쪼개는 것이다. 굳이 한 단계에서 끝낼 수 있는 코드를 이렇게 여러 단계로 나눈 것은 바로 재사용성 때문이다.
예를 들어서 3개 숫자의 곱을 계산해야하는데, 첫번째, 두번째 숫자가 5와 6으로 고정된 상태라고 해보자.
console.log(multiple(5,6,1))
console.log(multiple(5,6,2))
.
.
.
console.log(multiple(5,6,9))
----------------------------------------
const fixedFiveSix = curryingMultiple(5)(6)
console.log(fixedFiveSix(1))
console.log(fixedFiveSix(2))
.
.
.
console.log(fixedFiveSix(9))
위 두 케이스를 보면 커링을 사용했을 때 사용성이나 가독성 측면에서 훨씬 개선되었다는 사실을 알 수 있다. 커링은 이처럼 내부 구조가 비슷한 함수를 상황에 따라 재사용하기 편한 상태로 구성할 수 있다는 장점이 있다.
여담으로 arrow function을 중첩사용하면 커링을 보다 간단하게 표현할 수 있다.
const curryingMultiple = (a) => (b) => (c) => a * b * c;
console.log(curryingMultiple(5)(6)(7)); //210