이번 포스팅은 지난번 포스팅과 이어진다면 이어질 수도 있는 내용이다. 어느덧 내일배움캠프는 6주차에 접어들었고, 옷을 여미게 되는 날씨가 되었다. 밖에 안 나가지 않느냐고? 맞다... 아무튼... 시간의 흐름을 실감한다는 이야기다...
그렇다면 6주가 지나는 동안 나는 무엇을 했고 얼마나 성장했는가. 어떤 걸 할 수 있고 어떤게 아직 어려운가... 생각을 해보면 실제로 어려운 것과 꺼려지는 것이 나뉜다. 실제 어려운 것의 대부분은 아직은 공부를 해도 온전히 흡수할 수 없는, 투자 대비 효용이 떨어지는 것들이다. Babel
이라던가 Webpack
, bundling
, code-splitting
, .... 그렇다면 반대로 꺼려지는 것은 무엇일까?
JavaScript 와 지금까지 배운 React 내용중에서 위의 세가지에 해당되는 건 뭐가 있을까?
Closure, async/await,
useState(이 친구는 최근 이 목록에서 탈출했다. 조만간 다룰 예정)
역시 이들이다. 얘들은 머릿속에서 떠날 생각을 않는다. 그래서 하나씩 부숴보기로 했다. 일단 클로저부터.
클로저의 사전적 의미는 폐쇄, 닫힘으로, 이 패턴을 사용함으로써 얻을 수 있는 효과를 잘 보여준다. 클로저는 어떤 별도의 기능이라기보다 렉시컬 스코프와 함수를 조합해 만들 수 있는 패턴인데, 이 패턴은 데이터를 안전하게 은닉하거나 캡슐화 하는데에 많이 쓰인다고 한다. 클로저를 이해하고 사용하기 위해선 실행 컨텍스트와 콜 스택, 렉시컬 환경에 대한 이해가 선행되어야 하는데, 백 날 줄글만 봐선 알 수 없으니 코드를 작성해보자.
위의 코드는 클로저를 사용해보고자 작성한 코드의 일부인데, 이를 보고 알 수 있는 사실은 다음과 같다.
closureTest
는 innerFunction
을 반환하는 함수로, 스코프 안에 변수 count
를 갖는다.innerFunction
함수는 count
를 1만큼 증가시킨 후 반환한다. 그러나 count
에 대한 정보를 가지고 있지는 않다.countAccumulator
는 closureTest
함수가 실행된 후 반환한 innerFunction
을 그대로 담는다.이제 여기에 자바스크립트 엔진의 변수 환경, 렉시컬 환경, 실행 컨텍스트 그리고 콜 스택의 맥락을 얹어서 다시 보자.
innerFunction
이 클로저에 해당한다.innerFunction
을 countAccumulator
에 할당한 이후 closureTest
는 종료되고 이 함수의 실행 콘텍스트는 콜 스택에서 pop 되어 제거된다.closureTest
에서 선언한 변수 count
는 그대로 남아있다. innerFunction
함수에서 참조하고 있기 때문이다.여기서 innerFunction
은 count
를 어떻게 참조할 수 있는걸까? 앞서 말한 JS 엔진의 맥락에서innerFunction
을 다시 살펴보자.
count
를 1만큼 증가시켜야하는데, 함수 내부에 변수가 없음을 확인하고 Outer lexical environment 를 확인한다.closureTest
의 렉시컬 스코프에 접근하고 count
를 찾아내어 참조하는 것이다.countAccumulator
함수가 호출될때마다 반복된다.이제 전역에서
count
의 값을 알 방법은countAccumulator
를 호출한 결과값을 확인하는 방법밖에는 없다. 완전히 은닉된 변수가 된 것이다.
이제 위 코드가 포함되어있는 전체 코드로 구현한 간단한 웹페이지를 보자. 이 웹페이지의 JS로 작성된 부분 중 중요한 부분만 추려서 보면 다음과 같다.
const countBtn = document.querySelector("#counter");
const checkBtn = document.querySelector("#check");
const closureTest = () => {
let count = 0;
const innerFunction = (e) => {
return count++;
};
return innerFunction;
};
const countAccumulator = closureTest();
const showCount = () => {
const target = document.querySelector("#display");
const countValue = countAccumulator();
target.innerHTML = `You clicked the button ${countValue} times`;
};
countBtn.addEventListener("click", countAccumulator);
checkBtn.addEventListener("click", showCount);
counter
라는 id를 가진 버튼에 countAccumulator
를 이벤트리스너의 콜백 함수로 등록했다. 또한 count
의 값을 확인하기 위해 check
라는 아이디를 가진 버튼에 showCount
라는 콜백 함수를 이벤트리스너에 등록했다. 이 때 showCount
가 출력을 위해 count
값을 가져오는 부분을 보면
target.innerHTML = `You clicked the button ${countAccumulator()} times`;
위와 같은 코드로 target인 display
라는 아이디를 가진 HTML 요소의 innerHTML
에 count
의 값을 출력하는데, 이 때에도 countAccumulator
함수를 호출해야 한다. 물론 이 때에도 count
값의 증가는 일어나지만 횟수를 잘 보여주기 위해 부득이하게 count
변수에 increment
연산자를 postfix로 붙였기 때문에 버튼을 누른 횟수만큼 보이는 것이다.
gif 이미지로 보면 아래와 같다.
count 옆의 버튼을 누르기 전까진 값이 보이지 않는 것을 확인할 수 있다.