A closure is the combination of a function bundled together (enclosed) with references to its surrounding state (the lexical environment)
클로저
는 함수와 함수가 선언된 어휘적 환경(Lexical environment)의 조합
이다.
여기서 언급되는 Lexical environment는 실행 컨텍스트에서 배운 것과 관련이 깊기 때문에 실행 컨텍스트를 이해하고 있어야 아래 코드의 동작방식을 좀더 쉽게 이해할 수 있을 것이다.
아래 코드를 봐보자.
function makeFunc() {
let name = 'Mozilla';
function displayName() {
console.log(name);
}
return displayName;
}
let myFunc = makeFunc();
myFunc();
함수 makeFunc
가 실행된 이후 내부함수 displayName가 반환
되고나면, call stack
에서 함수 makeFunc의 실행 컨텍스트는 제거
되었으므로 함수 makeFunc의 변수 name 또한 더이상 유효하지 않게 되어 변수 name에 접근할 수 있는 방법이 없을 거라 생각한다.
하지만 코드의 결과로 함수 makeFunc의 변수 name의 값인 Mozilla
가 출력되는 것을 볼 수 있다.
이처럼 외부 함수의 실행 컨텍스트가 제거 되어도 내부함수를 호출하여 외부함수의 지역변수에 접근하는 경우
가 있는데
이때, 접근을 가능하게 해주는 것이 이 함수가 클로저(Closure)
이기 때문이다.
클로저에 의해 접근할 수 있는 외부함수의 지역변수를
자유변수(Free variable)
라고도 한다.
`클로저`는 `함수와 함수가 선언된 어휘적 환경(Lexical environment)의 조합`이다.
위 정의에서 말하는 “함수”란 반환된 내부함수를 의미하고 “그 함수가 선언될 때의 렉시컬 환경(Lexical environment)”란 내부 함수가 선언됐을 때의 스코프를 의미한다. 즉, 클로저는 반환된 내부함수가 자신이 선언됐을 때의 환경(Lexical environment)인 스코프를 기억하여 자신이 선언됐을 때의 환경(스코프) 밖에서 호출되어도 그 환경(스코프)에 접근할 수 있는 함수를 말한다. 이를 조금 더 간단히 말하면 클로저는
자신이 생성될 때의 환경(Lexical environment)을 기억하는 함수
다라고 말할 수 있겠다.
버튼이 클릭될 때마다 횟수가 누적되어 화면에 표시되는 카운터로 예시를 들어보자.
<!DOCTYPE html>
<html lang="ko">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<p id='count'>0</p>
<button id='increase'>+</button>
<button id='reset'>reset</button>
<script>
const increaseBtn = document.querySelector('#increase');
const count = document.querySelector('#count');
let counter = 0;
function increase() {
return ++counter;
}
const increaseCount = () => {
count.innerHTML = increase();
}
increaseBtn.addEventListener('click', increaseCount);
</script>
</body>
</html>
위에 코드를 사용해도 문제없이 카운트가 누적되는 것을 확인할 수 있다.
하지만 아래 사진과 같이 counter는 전역 변수이기 때문에 언제든지 누구나 접근할 수 있고 변경
할 수 있다. 이를 방지하고자 클로저 개념을 이용해보자.
const count = document.querySelector('#count');
const increaseBtn = document.querySelector('#increase');
const resetBtn = document.querySelector('#reset');
let myCounter = counter();
function counter() {
let n = 0;
return {
count: function() { return ++n; },
reset: function() { return n = 0; }
};
}
const increaseCount = () => {
count.innerHTML = myCounter.count();
}
const resetCount = () => {
count.innerHTML = myCounter.reset();
}
increaseBtn.addEventListener('click', increaseCount);
resetBtn.addEventListener('click', resetCount);
위에 코드도 동일하게 작동한다.
하지만 클로저를 통해 n값도 유지할 수 있고 아래 사진처럼 외부에서 접근할 수 없게하여 보다 안정적인 프로그래밍이 가능하다.
그렇기 때문에 클로저를 잘 활용하는 것이 좋다.