클로저는 함수와 함수가 선언된 어휘적 환경의 조합이다. 클로저를 이해하려면 자바스크립트가 어떻게 변수의 유효범위를 지정하는지(Lexical scoping)를 먼저 이해해야 한다.
feat.MDN
무슨말인지 잘 모르겠습니다. 번역기를 돌려보면
어떠한 것을 '닫다, 마무리하다'는 뜻인것같습니다.
번역해도 뭔말인지 모르겠습니다.
일단 코드를 보겠습니다.
function makeCounter(predicate) {
let num = 0;
return function () {
num = predicate(num);
return num;
};
}
function increase(n) {
return ++n;
}
const inc = makeCounter(increase);//객체화
console.log(inc());//1
console.log(inc());//2
console.log(inc());//3
일반적인 상식으로는 inc함수를 호출할때마다 makeCounter안에 있는 지역 변수인 num이 '초기화'가 되야하는데 inc함수를 호출할때마다 1씩 ++되고있습니다.
왜 이런 현상이 발생하냐면
모든 함수는 함수가 생성된 곳의 렉시컬 환경을 기억합니다. 함수는 Environment라 불리는 숨김 프로퍼티를 갖는데, 여기에 함수가 만들어진 곳의 렉시컬 환경에 대한 참조가 저장됩니다.
feat. Javascript.Info
호출 장소와 상관없이 함수가 자신이 태어난 곳을 기억할 수 있는 건 바로 이 Environment 프로퍼티 덕분입니다. Environment는 함수가 생성될 때 딱 한 번 값이 세팅되고 영원히 변하지 않습니다.
inc()를 호출하면 각 호출마다 새로운 렉시컬 환경이 생성됩니다. 그리고 이 렉시컬 환경은 inc.Environment에 저장된 렉시컬 환경을 외부 렉시컬 환경으로서 참조합니다. 그래서 inc함수가 밖에서 호출되도 makeCounter의 내부변수가 인식했던 외부변수 num의 Environment의 참조능력? 때문에 값을 변화시킬 수 있는것입니다.
하지만 클로저는 자신이 생성될 때의 환경을 기억해야 하므로 메모리 차원에서 손해를 볼 수 있습니다.
하지만 클로저는 강력한 기능으로 이를 적극적으로 사용해야 합니다.
클로저 함수의 또 다른 예시를 보겠습니다.
const btn = document.querySelector('button');
function toggle(){
let num = 0;
return function(){
++num;
console.log(num)
}
}
btn.addEventListener('click', toggle())//1,2,3...
토글이란 함수를 만들고 내부함수로 변수num이 ++되는 기능을 리턴하고있습니다. 그리고 이벤트핸들러에 할당하였습니다.
버튼을 클릭하면 숫자가 올라가는 코드로 자주 보는 모양입니다. 이 코드에는 장점이 있습니다.
위에서 설명했듯 toggle안의 내부함수가 리턴이 되면 내부함수가 자신이 선언되었을때를 기억하여 num변수의 값을 계속 변화 시키고 상태를 계속 유지할 수 있습니다.
지금은 이벤트 핸들러에 toggle함수를 할당했기때문에 클로저가 기억하는 렉시컬 환경의 변수 num은 소멸하지않습니다.
보통 위같은 코드를 작성할때에는 변화하는 변수(위에서는 num변수가 되겠네요)를 보통 글로벌 스코프에 작성합니다.
하지만 만약 나중에 누군가가 num 변수 네임을 나중에 뒤에서 모르게 작성을 해버리면? 갑자기 발생하는 오류에 파일을 뒤지고 야근하는겁니다.😴
그러한 멘붕과 야근을 억제하기위해서 최대한 중복되는 네임을 피해야합니다.
하지만 클로저를 사용하면 외부환경에서의 접근을 막을 수 있고 변수 네이밍 중복을 막을 수 있습니다.
이것은 모든 코드가 공통으로 추구해야하는 내용이지만 클로저 함수는 자신만의 환경을 가지면서 외부환경으로 부터 안전하기 때문에 다른 함수보다 더 단단하고 유지보수하기가 수월합니다.