날것 - 클로저의 활용
클로저의 활용
전역 변수로 카운터 만들기의 문제
전역 변수는 누구나 접근할 수 있음으로 이건 당연히 문제가 발생할 확률이 높다
let num = 0
const increase = function () {
return ++num;
}
console.log(increase()) // 1
console.log(increase()) // 2
console.log(increase()) // 3
그렇다고 함수 내부에 값을 선언해놓으면 그 값이 유지되지 않는다.
const increase = function () {
let num = 0
return ++num;
}
console.log(increase()) // 1
console.log(increase()) // 1
console.log(increase()) // 1
여기까진 뽀삐도 설명할 수 있다.
그렇다면 어떤 방법이 있을까?
정답은 클로저에 있다
const increase = (function () {
let num = 0
return function() {
return ++num;
};
}());
console.log(increase()) // 1
console.log(increase()) // 2
console.log(increase()) // 3
이럼 클로저때문에 실행 함수는 호출된 이후 소멸하지만, 즉시 실행 함수가 반환한 클로저는 increase 변수에 할당되어 num 값에 접근할 수 있다
즉 클로저를 사용해서 private 상태를 만들어버린것 ㅋㅋ
이걸 생성자 함수를 사용해 좀 더 가독성 좋게 표현할 수 있음
const Counter = (function() {
let num = 0
function Counter() {
// this.num = 0; 프로퍼티는 public하므로 은닉되지 않는다. 외부에서도 접근 가능함
}
Counter.prototype.increase = function() {
return ++num;
};
Counter.prototype.decrease = function() {
return num > 0 ? --num : 0;
};
return Counter;
}())
const couter = new Counter();
console.log(counter.increase()) // 1
console.log(counter.increase()) // 2
console.log(counter.decrease()) // 1
console.log(counter.decrease()) // 0
약간의 꼼수를 확인할 수 있는게 프로퍼티는 외부에서 접근할 수 있지만 실행 함수 내에서 선언된 num 변수는 접근 못하니깐 이렇게 설정한 모습
함수형 프로그래밍 스타일 — 고차 함수로 상태 유지
{makeCounter처럼 보조 함수를 인자로 받고, 상태를 클로저에 가둔 함수를 반환}
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);
console.log(increaser()); // 1
console.log(increaser()); // 2
const decreaser = makeCounter(decreaser);
console.log(decreaser()); // -1
console.log(decreaser()); // -2
레전드레전드 ㅋㅋ
함수를 인자로 전달받는 고차함수 + 클로저를 사용해서 counter의 값을 변경할 수 있음
하나 특이한건 makeCounter가 다시 한번 실행 컨텍스트에 올라가버리면 새로운 makeCounter의 새로운 렉시컬 환경이 생기고 decreaser는 해당 렉시컬 환경을 바라보게 됨으로 increaser가 바라보던 렉시컬 환경과는 별개의 환경이 생긴다
그렇다면 이 방식으로 같은 렉시컬 환경과 묶어버릴 수 있음
const counter = (function (aux) {
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(decreaser)); // 1
console.log(counter(decreaser)); // 0
재밌네
재뮛서
캡슐화와 정보 은닉
자바스크립트는 접근 제한자가 없다(레전드 ㅋㅋ)
그래서 클로저로 흉내낸다 - 생성자 함수 + 즉시 실행 함수 조합
function Person(name, age) {
this.name = name; // public
let _age = age; // private
this.sayHi = function() {
console.log(`${this.name} , ${_age}`)
}
}
const me = new Person('Lee', 20);
me.sayHi(); // Lee , 20
console.log(me.name) // Lee
console.log(me._age) // undefined
이건 인스턴스 메서드라서 Person 객체가 생성될떄마다 중복 생성됨
이를 prototype으로 예방할 수 있음
const Person = (function() {
let _age = 0;
function Person(name, age) {
this.name = name; // public
_age = age; // private
}
Person.prototype.sayHi = function() {
console.log(`${this.name} , ${_age}`)
}
}
const me = new Person('Lee', 20);
me.sayHi(); // Lee , 20
console.log(me.name) // Lee
console.log(me._age) // undefined
근데 이것도 문제가 있음...
const me = new Person('Lee', 20);
me.sayHi(); // Lee , 20
const you = new Person('Kim', 30);
you.sayHi(); // Kim , 30
me.sayHi(); // Lee, 30
이는 Person.prototype.sayHi 메서드가 단 한번 생성되는 클로저이기 떄문에 발생하는 현상임
얜 잘못 만들어진게 맞다
참고 자료
- 모던 자바스크립트 Deep Dive 24장
- 실행 컨텍스트 — 23장, 렉시컬 환경과 외부 참조가 클로저의 실체
- 렉시컬 스코프 — 상위 스코프가 정의 위치로 정해지는 이유
- let/const와 블록 레벨 스코프 — for 문 실수의 해법
- 일급 객체 — 함수를 값처럼 다룰 수 있어야 클로저가 성립
- Closure — 먼저 정리해둔 짧은 요약