2022. 2. 1. 21:25ㆍ책/자바스크립트 딥다이브
4. 클로저 활용
상태(state)를 안전하게 변경하고 유지하기 위해 주로 사용된다. 다시 말해, 상태가 의도치 않게 변경되지 않도록 상태를 안전하게 은닉(information hiding)하고 특정 함수에게만 상태 변경을 허용하기 위해 사용한다.
아래 예제는 어려울 수 있으니 차근차근 읽어보길 바란다.
이해하고 나면 은닉이 무엇인지, 특정 함수만 상태 변경을 허용한다는게 뭔지 대충 감이 올 듯
가. 예제1 ( +1씩 더해서 출력해주는 함수 )
ㅇ의도
- 화면에 버튼을 누르면 +1씩 더해서 출력시켜준다.
- 단, 화면에 출력되는 숫자(변수)는 코드내 다른 함수로 조작할 수 없다
ㅇ코드
const $p = document.querySelector('p');
const $button = document.querySelector('button');
const increase = function () { // 즉시실행함수. 실행됨과 동시에 중첩함수 자체를 리턴한다
let num = 0; // 리턴 직후 실행 컨텍스트 스택에서 제거되므로 외부에서 접근이 불가능하다
// 하지만 중첩함수는 이 함수의 렉시컬 스코프를 참고하고있는 중이다
return function() {
++num; // 현재 중첩함수의 상위 스코프 = 즉시실행함수의 렉시컬 스코프
// 스택엔 없지만 렉시컬 스코프의 num엔 접근가능하므로 증가시킬 수 있다
return $p.textContent = num; // 증가된 num 값을 화면상에 표출한다
};
}();
$button.onclick = increase;
console.log(num) // undefined 즉, num은 은닉된 private 변수
나. 예제2 ( +1/-1 기능이 달린 객체 )
const $p = document.querySelector('p');
const $button_plus = document.querySelectorAll('button')[0];
const $button_minus = document.querySelectorAll('button')[1];
const counter = (function() {
let num = 0;
// 예제1에선 함수를 반환했지만 예제 2에선 객체를 반환
// 이 객체엔 increase, decrease 메서드가 할당되어있다
return {
// 즉시 실행 함수의 실행 단계에서 평가되어 객체가 됨
// 두 메서드의 상위 스코프는 메서드가 평가되는 시점에 실행중인 실행 컨텍스트
// 즉, 즉시 실행 함수 실행 컨텍스트의 렉시컬 환경
increase() {
++num;
return $p.textContent = num;
},
decrease() {
num > 0 ? --num : 0;
return $p.textContent = num;
}
}
}());
// 각 메서드는 어디서 호출되던 즉시 실행 함수의 렉시컬 환경과 연결되어있고
// 따라서 렉시컬 환경의 환경 레코드상에 저장된 식별자 num에 접근 및 조작이 가능하다
$button_plus.onclick = counter.increase;
$button_minus.onclick = counter.decrease;
다. 예제3
// 보조 함수를 인수로 전달받는 함수
// 뭐 어찌됐건 클로저를 반환함
function makeCounter(aux) {
let counter = 0;
return function() {
counter = aux(counter);
return counter;
}
}
function increase(n) {
return ++n;
}
function decrease(n) {
return --n;
}
const increaser = makeCounter(increase);
const decreaser = makeCounter(decrease);
// 이제 아래 코드를 순차적으로 실행하면 0 +1 -1 = 0 나오겠지?
console.log(increaser()); // 1
console.log(decreaser()); // -1
// 뭐야 +1 해주고 -1 했으니까 0이 나와야되는거 아냐?
// increaser와 decreaser는 서로 독립적인 렉시컬 환경을 갖고있다
// 즉, counter 값이 서로 연동이 안됨
// 왜 그런지는 아래 과정에서 자세히 설명하겠음
ㅇconst increaser = ... 과정
1) makeCounter 함수를 호출 → makeCounter 함수의 실행 컨텍스트 생성
2) makeCounter 함수는 함수 객체를 생성해 반환 → makeCounter 함수 실행 컨텍스트는 스택에서 제거(pop)
3) 반환된 함수는 makeConter 함수의 렉시컬 환경을 상위 스코프로 기억하는 클로저 상태로, 전역 변수인 increaser에 할당
ㅇconst decreaser = ... 과정
1) 똑같이 makeCounter 함수를 호출 → makeCounter 함수의 실행 컨텍스트 생성
1-1. 단, 이 때 생성된 실행 컨텍스트는 increaser 과정에서 생긴 makeCounter 함수의 실행 컨텍스트와 하등 상관이 없다
2) makeCounter 함수는 함수 객체를 생성해 반환 → makeCounter 함수 실행 컨텍스트는 스택에서 제거(pop)
3) 반환된 함수는 makeConter 함수의 렉시컬 환경을 상위 스코프로 기억하는 클로저 상태로, 전역 변수인 decreaser에 할당
3-1. makeCounter 함수를 쓴건 같지만 애초에 다른 실행 컨텍스트였으며, 참조하고 있는 렉시컬 환경 또한 별개의 것
즉, increaser와 decreaser에 할당된 함수는 각각 자신만의 독립된 렉시컬 환경을 갖고있기 때문에 변수 counter를 공유하지 않아 서로 연동되지 않은 것이다.
만약 증감이 연동되는 카운터를 만드려면 렉시컬 환경을 공유하는 클로저를 만들어야 되는데, 그 말은 곧 makeCounter 함수를 두 번 호출하지 말아야 한다는 뜻이다.
다. 예제3 (수정)
const counter = ( function() {
let counter = 0;
return function(aux) {
counter = aux(counter);
return counter;
}
}() );
function increase(n) {
return ++n;
}
function decrease(n) {
return --n;
}
console.log(counter(increase)); // 1
console.log(counter(increase)); // 2
console.log(counter(decrease)); // 1
console.log(counter(decrease)); // 0
'책 > 자바스크립트 딥다이브' 카테고리의 다른 글
[25장] 클래스(2) (0) | 2022.02.04 |
---|---|
[25장] 클래스 (0) | 2022.02.03 |
[24장] 클로저 (0) | 2022.02.01 |
[23장] 실행 컨텍스트(2) (0) | 2022.02.01 |
[23장] 실행 컨텍스트 (0) | 2022.02.01 |