클로저란?
- 함수와 함수가 선언된 어휘적(lexical) 환경의 조합으로 이 환경은 클로저가 생성된 시점의 유효 범위 내에 있는 모든 지역 변수로 구성된다.
- 자바스크립트는 함수가 호출되는 환경과 별개로 기존에 선언되어 있던 환경, 즉 어휘적 환경을 기준으로 변수를 조회하려고 한다.
- 스코프는 함수 호출 시점이 아니라 함수를 어디에 선언하였는지에 따라 결정된다. 이를 렉시컬 스코핑(Lexical scoping)라 한다.
- 외부 함수의 변수에 접근할 수 있는 내부 함수를 클로저 함수라고 한다.
- 클로저는 자바스크립트 고유의 개념이 아니라 함수를 일급 객체로 취급하는 함수형 프로그래밍 언어이며 ECMAScript 명세에 클로저의 정의가 등장하지 않는다.
클로저 함수의 특징과 형태
함수를 리턴하는 함수
const add = (x, y) => x + y;
const adder = x => y => x + y;
adder(5)(7);
typeof adder(5);
(5)
adder(5);
const adder = function(x) {
return function (y) {
return x + y;
}
}
- 변수 x를 외부에서 접근하지 못하도록 함수를 만들어 함수의 지역변수로 만든다.
- 내부 함수와 외부 함수의 변수(x)의 조합
리턴하는 함수에 의해 스코프(변수의 접근 범위)가 구분된다.

- 클로저의 핵심은 스코프를 이용해서 변수의 접근 범위를 닫는(closure)데에 있다. 따라서 함수를 리턴하는 것만큼이나 변수가 선언된 곳이 중요하다.
- 변수 x와 y가 선언된 곳이 각기 다르며 x가 선언된 함수는 외부 함수고 y가 선언된 함수는 내부 함수다. 따라서 이 클로저 함수는 스코프가 분리되어 있다.
- Q. 외부 함수는 y에 접근이 가능할까?
X, 바깥 스코프에서 안쪽 스코프로의 접근은 불가능하다.
- Q. 내부 함수는 x에 접근이 가능할까?
O, 안쪽 스코프는 바깥 스코프에서 선언된 변수에 접근이 가능하다.
클로저의 활용
데이터를 보존하는 함수
// 외부 함수(adder)의 실행이 끝나더라도 외부 함수 내 변수 x를 사용할 수 있다.
const adder = function (x) {
return function (y) {
return x + y;
}
}
// 함수 실행이 끝나도 5라는 값은 사용 가능하다.
const add5 = adder(5);
- 일반적인 함수는 함수 실행이 끝나고 나면 함수 내부의 변수를 사용할 수 없지만 클로저는 외부 함수의 실행이 끝나더라도 외부 함수 내 변수가 메모리 상에 저장된다. (어휘적 환경을 메모리에 저장하기 때문)
const adder = function (x) {
return function (y) {
return x + y;
}
}
// 함수 실행이 끝나도 5라는 값은 사용 가능하다.
const add5 = adder(5);
add5(7); // 12
add5(10); // 15
- 변수 add5에는 클로저를 통해 리턴한 함수가 담겨 있는데 adder함수에서 인자로 넘긴 5라는 값을 외부 함수의 실행이 끝났음에도 변수 x에 계속 담은 채로 남아있다.
보다 실용적인 예제 - HTML 문자열 생성기
// 클로저를 이용해 HTML 문자열을 만드는 예제
const tagMaker = tag => content => `<${tag}>${content}</${tag}>`
const divMaker = tagMaker('div');
divMaker('hello'); // '<div>hello</div>'
divMaker('javascript'); // '<div>javascript</div>'
const anchorMaker = tagMaker('a');
anchorMaker('naver'); // '<a>naver</a>'
anchorMaker('google'); // '<a>google</a>'
- divMaker 함수는 'div'라는 문자열을 tag라는 변수에 담아두고 있으며 anchorMaker 함수는 'a'라는 문자열을 tag에 담아두고 있다.
- 클로저는 이처럼 특정 데이터를 스코프 안에 가두어 둔 채로 계속 사용할 수 있게 해준다.
정보의 접근 제한
클로저 모듈 패턴
const makeCounter = () => {
let value = 0;
return {
increase: () => {
value = value + 1
},
decrease: () => {
value = value - 1
},
getValue: () => value
}
}
const counter = makeCounter();
counter; // {increase: ƒ, decrease: ƒ, getValue: ƒ}
- 클로저를 이용해 내부 함수를 단 하나만 리턴하는 것에 그치지 않고 객체에 담아 여러 개의 내부 함수를 리턴하도록 만들 수 있다.
- makeCounter함수는 increase, decrease, getValue메서드를 포함한 객체 하나를 리턴하므로 counter는 객체이다.
캡슐화
Q. makeCounter함수를 바꾸지 않고 변수 value에 새로운 값을 할당할 수 있는 방법이 있을까?
- X, 외부 스코프에서 내부 스코프의 변수에 접근할 수 없기 때문에 어떤 경우에도 value는 직접 수정이 불가능하다. 하지만 리턴하는 객체가 제공하는 메서드를 통해 value 값을 간접적으로 조작할 수 있다.
- 이것을 캡슐화 라고 하는데 스코프로 value값을 감싸지 않았더라면 value값은 전역 변수여야만 했을 것이다. 하지만 makeCounter라는 함수가 value값을 보존하고 있기 때문에 전역 변수로 따로 만들 필요가 없다.
- 전역 변수는 다른 함수나 로직에 의해 의도되지 않은 변경을 초래하는 side effect를 발생 시키는데 클로저를 통해 불필요한 전역 변수 사용을 줄이고 스코프를 이용해 값을 보다 안전하게 다룰 수 있다.
모듈화
// 재활용이 가능한 makeCounter 함수
const counter1 = makeCounter();
counter1.decrease();
counter1.decrease();
counter1.increase();
counter1.getValue(); // -1
const counter2 = makeCounter();
counter2.increase();
counter2.increase();
counter2.decrease();
counter2.getValue(); // 1
// getValue메서드는 외부 함수에 선언된 value 값을 리턴
- makeCounter에 의해 리턴된 객체는 makeCounter를 실행할 때에 선언되는 value값을 각자 독립적으로 가지게 되므로 counter1에서의 value와 counter2에서의 value는 서로에게 영향을 끼치지 않고 각각의 값을 보존할 수 있다.
- 이와 같이 함수 재사용성을 극대화하여 함수 하나를 완전히 독립적인 부품 형태로 분리하는 것을 모듈화라고 한다.
- 클로저를 통해 데이터와 메서드를 같이 묶어서 다룰 수 있고 클로저는 모듈화에 유리하다.