자 저번편에서 클로저 공부하려고 구글링 딱 시작하니까 이렇게 나왔다.
그럼 포이마웹 클로저부터 시작해보자.(시작만 몇번째냐.....)
function outerFunc() {
var x = 10;
var innerFunc = function () { console.log(x); };
innerFunc();
}
outerFunc(); // 10
위에 코드에서 당연히 결과값은 10입니다. innerfunc은 자신을 포함하고 있는 outerfunc 변수 x에 당연히 접근할 수 있다.
근데 바로 x에 접근이 성공한 걸까?? 아니다.
말이 너무 어렵다. 클로저를 이해할려면 1편에 있는 개념들을 최소한 찾아보고 오자. 내 블로그만으로는 이해가 안간다.. 정말 좋은 개발자분들의 블로그를 참고해서 공부하고 오자.
자 함수를 살짝 바꿔보자.
function outerFunc() {
var x = 10;
var innerFunc = function () { console.log(x); };
return innerFunc;
}
/**
* 함수 outerFunc를 호출하면 내부 함수 innerFunc가 반환된다.
* 그리고 함수 outerFunc의 실행 컨텍스트는 소멸한다.
*/
var inner = outerFunc();
inner(); // 10
outerfunc은 innerfunc을 반환하고 죽었다. 즉 실행컨텍스트 -> 콜스택에서 함수는 제거 되었다. 그렇다면 당연히 변수 x또한 없어졌고 접근할수 있는 방법이 없는게 맞아보인다.
근데?? 이상하게도 동작한다. 정말 이상하다. 진짜 이상하다. 마법이다.
이걸 클로저라고 부른다고 한다.
“A closure is the combination of a function and the lexical environment within which that function was declared.”
클로저는 함수와 그 함수가 선언됐을 때의 렉시컬 환경(Lexical environment)과의 조합이다.
즉, 클로저는 자신이 선언됐을 때의 렉시컬 스코프를 기억한다. 그래서 자신의 스코프 밖에서 호출되어도 그 렉시컬 스코프를 기억하고 있기 때문에 그 환경에 접근할 수 있느 함수를 말한다.
첨봤을때 어려웠지만 이제 좀 이해가 간다.
조금 더 추가해서 말해보자면 내부함수가 유효한 상태에서 외부함수가 종료되어도 실행컨텍스트 객체 정보를 가지고 있고 스코프 체인을 통해서 참조할 수 있다는것이다.
코드로 한 번 봐보자. 이해한다면 진짜 신기하다. 왜 클로저를 쓰는지 진짜 이해가 간다. 봐보자. 걍 보자
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;
toggle
토글 버튼을 누르면 없어졌다가 나타났다가 하는 그런 코드이다. 진짜 기초적이고 많이 쓰이는 코드이다. 여태까지 let 으로 전역변수를 만들어서 불리언값을 줘서 같은 기능을 만들었었다. 쓰면서 let은 어디서나 접근 가능하고 변경할 수 있기 때문에 오류의 원인이 된다고 알고 있었다. 쓰면서도 let을 쓰는게 맞을까? 이런 기초적인 것도 let을 남발하는데 실제 코드에서도 이걸 쓰는게 맞을까 생각했었는데 클로저를 쓰면 매우 유용하다.
버튼을 누를때마다 숫자를 1씩 증가하는 카운트 버튼을 만들어보자.
var incleaseBtn = document.getElementById('inclease');
var count = document.getElementById('count');
// 카운트 상태를 유지하기 위한 전역 변수
var counter = 0;
function increase() {
return ++counter;
}
incleaseBtn.onclick = function () {
count.innerHTML = increase();
};
클로저를 알고 나니까 부끄럽다. 맨날 이렇게 만들었다. 이 코드에서는 문제가 무엇일까?
전역 변수 counter의 값이 반드시 0 또는 숫자여야만 동작할 것이다.
근데 전역변수는 어디서나, 누구나 접근이 가능한 변수이고 의도치 않게 변경될 수 있다. 이는 오류로 이어진다. 그렇기 때문에 counter변수는 increase 함수가 관리하는것이 바람직한 방향이다.
function increase() {
// 카운트 상태를 유지하기 위한 지역 변수
var counter = 0;
return ++counter;
}
incleaseBtn.onclick = function () {
count.innerHTML = increase();
};
0에서 1은 변경이 되지만 함수가 호출될 때마다 counter가 0으로 초기화 되기 때문에 언제나 1이 표시된다.
var increase = (function () {
// 카운트 상태를 유지하기 위한 자유 변수
var counter = 0;
// 클로저를 반환
return function () {
return ++counter;
};
}());
incleaseBtn.onclick = function () {
count.innerHTML = increase();
};
즉시실행함수가 호출되고 변수 increase 카운터를 올려주는 함수가 할당된다. 이 함수는 렉시컬 스코프, 즉 자신이 만들어졌을때의 환경을 기억하는 클로저다. 그렇기 때문에 counter변수를 기억하고 있고 접근이 가능하다. 즉, 내부함수가 counter를 참조하는 한 죽지 않고 살아서 계속 유지된다.
- 변수의 값은 누군가에 의해 언제든지 변경될 수 있어 오류 발생의 근본적 원인이 될 수 있다. 상태 변경이나 가변(mutable) 데이터를 피하고 불변성(Immutability)을 지향하는 함수형 프로그래밍에서 부수 효과(Side effect)를 최대한 억제하여 오류를 피하고 프로그램의 안정성을 높이기 위해 클로저는 적극적으로 사용된다
그렇다. 클로저 어렵다. 이해하고 나면 재밌다. 공부하고 보면 써보고 싶다. 다음에는 let, var, const에 대해 더 공부해보면서 기록해야겠다. 하지만 잘 쓴다면 좀 간지나는 개발자가 될 것 같다.
갑자기 이런 생각이 들었다. 리액트를 하면서 usestate는 왜 const로 변수를 만들까?? state값을 계속 바꿔주고 여러가지 별거 다하는데 왜 let보다 const일까?? 궁금해서 검색해봤는데 클로저 때문이라고 한다.... 그냥 검색해봤는데 갑자기 클로저가 튀어나왔다. 클로저 키워드 보자마자 일단 도망쳤는데 다음에는 날잡고 공부해봐야겠다.
poimaWeb 감사합니다. 예제는 거의 다 가져왔지만 그래도 정말 열심히 공부했어요.