클로저에 대한 내용을 다뤘더니 예전에 받았던 Try This 과제가 하나 떠올랐다. 부수 효과가 발생할 수 있는 함수를 순수 함수로 만들어보는 과제였다. 문제 코드는 이렇다.
const weeks = ['일', '월', '화', '수', '목', '금', '토'];
let widx = -1;
const getNextWeek = () => {
widx += 1;
if (widx >= weeks.length) widx = 0;
return `${weeks[widx]}요일`;
};
let cnt = 0;
const intl = setInterval(() => {
widx += 2; // side-effect!
console.log('call', cnt, getNextWeek());
if ((cnt += 1) === 8) clearInterval(intl);
}, 1000);
이 코드는 일요일부터 토요일까지 1초마다 출력하는 코드다. 순수 함수란 입력 값이 같으면 결과 또한 같아야 하며 부수 효과(side-effect)가 없어야 한다. 위 코드에서 widx가 전역에 선언되었으므로 어디에서든지 접근하여 값을 변경할 수 있다. 현재 부수 효과가 발생하고 있다. 이를 막아 getNextWeek 함수를 순수 함수로 만드는 과제였다.
클로저가 무엇을 할 수 있었던가? 바로 은닉성 제어다. widx 변수를 함수 내에 넣고 클로저를 이용해 외부에서 접근할 수 없도록 만들면 될 것 같다! 처음 이 과제를 받았을 때는 시간이 조금 걸렸던 것 같은데 지금 다시 풀어보니 조금 빨라졌다.😆
const weeks = ['일', '월', '화', '수', '목', '금', '토'];
const getNextWeek = (() => {
let widx = -1;
return () => `${weeks[(widx += 1, widx % 7)]}요일`;
})();
let cnt = 0;
const intl = setInterval(() => {
// widx += 2; // 접근 불가!
console.log('call', cnt, getNextWeek());
if ((cnt += 1) === 8) clearInterval(intl);
}, 1000);
즉시 실행 함수로 만들었기 때문에 getNextWeek는
() => `${weeks[(widx += 1, widx % 7)]}요일`;
이 함수 객체를 가리키게 된다. 따라서 getNextWeek() 이렇게 호출하면 ‘?요일’ 문자열이 반환된다. 이 안에서 사용한 쉼표 연산자는 각각의 피 연산자를 왼쪽에서 오른쪽 순서로 평가하고, 마지막 연산자의 값을 반환한다.
즉 widx += 1를 실행한 후, +1된 widx를 7로 나눈 나머지 값을 반환한다.
widx 변수가 getNextWeek 함수 내에서 선언되었기 때문에 함수 외부에서 접근이 불가능하다. 그리고 getNextWeek 함수가 반환하는 함수가 widx를 참조하고 있으므로 이는 클로저다!! 이렇듯 클로저를 이용해 순수 함수를 만들 수 있다는 것을 배웠다!✌️