
클로저는 반환된 내부함수가 자신이 선언됐을 때의 렉시컬 환경 (Lexical enviroment) 을 기억하여, 함수 외부에서 호출되더라도 그 환경 (스코프)에 접근할 수 있는 함수이다
즉, 클로저는 자신이 생성될 때의 렉시컬 환경을 기억하는 함수이다
function outerFunc() {
var x = 10
var innerFunc = function() {
console.log(x)
}
return innerFunc
}
var inner = outerFunc()
inner() // 10
// outerFunc는 실행이 끝났지만, 내부 함수인 innerFunc가 x를 참조하고 있어
// x는 GC(가비지 컬렉션) 에 의해 수거되지 않고 살아있으며, 이후에도 계속 접근할 수 있다

클로저가 가장 유용하게 사용되는 상황은 현재 상태를 기억하고 변경된 최신 상태를 유지하는 것이다
<!DOCTYPE html>
<html>
<body>
<button class="toggle">toggle</button>
<div class="box" style="width: 100px; height: 100px background: red;"></div>
<script>
var box = document.querySelector('.box');
var toggleBtn = document.querySelector('.toggle')
var toggle = (function () {
var isShow = false
return function () {
box.style.display = isShow ? 'block' : 'none'
isShow = !isShow
}
})()
toggleBtn.onclick = toggle
</script>
</body>
</html>
이처럼 클로저는 .box 요소의 표시 상태를 나타내는 isShow라는 변수를 안전하게 기억하고 제어할 수 있게 해준다
<!DOCTYPE html>
<html>
<body>
<p>클로저를 사용한 Counting</p>
<button id="inclease">+</button>
<p id="count">0</p>
<script>
var incleaseBtn = document.getElementById('inclease')
var count = document.getElementById('count')
// 1번
var counter = 0
function increase() {
return ++counter
}
// 2번
function increase() {
var counter = 0
return ++counter
}
// 3번
var increase = (function () {
var counter = 0
return function () {
return ++counter
}
}())
incleaseBtn.onclick = function () {
count.innerHTML = increase()
}
</script>
</body>
</html>
1번 코드는 동작은 하지만 좋지 않은 방식이다.
전역변수 counter는 누구나 접근하고 변경할 수 있으므로, 의도치 않은 상태 변경의 위험이 존재한다
2번 코드는 counter를 함수 내부의 지역변수로 선언하여 전역변수의 위험을 피했지만, increase()가 호출될 때마다 counter가 0으로 초기화되기 때문에 상태를 기억하지 못한다.
때문에 버튼을 눌러도 매번 1만 출력된다
3번 코드는 즉시실행함수를 사용해서 클로저를 생성한 예로, 스크립트가 실행되면 즉시실행함수가 실행되어 내부 변수 counter와 그를 사용하는 함수를 반환하고, 이 반환된 함수는 increase 변수에 할당된다
이 함수는 자신이 생성되었을 당시의 렉시컬 환경을 클로저로 기억하며, 이 안의 counter 변수는 외부에서 직접 접근할 수 없는 private 변수이기 때문에 의도되지 않은 변경에 대한 걱정이 없는 안정적인 프로그래밍이 가능하다
변수의 값은 누군가에 의해 언제든지 변경될 수 있어 오류 발생의 근본적인 원인이 될 수 있다. 상태 변경이나 가변 데이터를 피하고 불변성을 지향하는 함수형 프로그래밍에서 부수 효과를 최대한 억제하여 오류를 피하고 프로그램의 안정성을 높이기 위해 클로저는 적극적으로 사용된다
function Counter() {
var counter = 0
this.increase = function () {
return ++counter
}
this.decrease = function () {
return --counter
}
}
const counter = new Counter()
console.log(counter.increase()) // 1
console.log(counter.decrease()) // 0
생성자 함수 Counter는 increase, decrease 메소드를 갖는 인스턴스를 생성한다
이 메서드들은 자신이 생성될 당시의 렉시컬 환경인 Counter 함수의 스코프를 기억하는 클로저이다
두 메서드는 동일한 렉시컬 환경을 공유하므로, 하나의 counter 변수를 함께 참조하게 된다
Counter 함수 내부의 counter 는 this.counter 와 같은 프로퍼티가 아니라 지역변수이므로, 외부에서 직접 접근할 수 없다
하지만 increase, decrease 메서드는 클로저이기 때문에 자신이 기억하고 있는 렉시컬 환경을 통해 counter 변수에 접근할 수 있다
이처럼 클로저를 활용하면 JS에서도 private 키워드처럼 외부에서는 접근할 수 없는 내부 상태를 유지하는 방식, 즉 정보 은익을 흉내낼 수 있다.