클로저는 내부 함수로 부터 외부 함수의 접근 권한을 준다.
클로저는 함수 생성 시점에 언제나 생긴다.
function hello() {
return console.log('hello')
}
const testHello = hello() //
//testHello에는 함수의 실행 결과가 할당됨 따라서 할당 될 때 콘솔에 'hello'가 찍히지만
//그 다음에 호출 할 때는 undefined만 나옴 그리고 함수가 할당된게 아니라 할당된 결과가 할당된거라서
//testHello()할 수도 없음
const testHello2 = hello
const x = 1;
function outer(){
const x = 10;
const inner = function() {console.log(x)}
return inner;
}
const innerFunc = outer(); // 이 시점에 outer()는 실행되고 그 결과인 inner 가 innerFunc에 할당된다.
// const innerFunc = inner 따라서 outer함수는 실행컨텍스트에서 사라 졌지만 inner가 외부 함수를 참조 한다.
//외부 함수보다 중첩함수, 내부 함수가 더 오래 유지되는 경우이다.
// inner 함수에 의해서 참조하고 있기 때문에 가비지 컬렉터에 의해 사라지지 않는다.
## num 증가
const increase = (function() {
let num = 0;
return function(){
return ++ num;
}
}())
debugger
increase()
console.log(increase()) // 1
// increase() 하게 되면 increase 함수가 리턴하는 function(){return ++n }을 선물을 개봉하자마자 실행하는 것과 동일해진다. 상위 스코프의 num의 값을 1만큼 더한다.
console.log(increase()) // 2
console.log(increase()) // 3
## 생성자 함수
const Counter = (function(){
// (1)
let num = 0;
function Counter(){
};
Counter.prototype.increase = function () {
return ++num;
}
Counter.prototype.decrease = function(){
return num > 0 ? --num : 0;
}
return Counter;
}());
const counter = new Counter();
---
//ES5에서 클래스 만들 때 이렇게했음
function Counter(){}
const counter = new Counter(); << counter라는 인스턴스 생성됨
//new 연산자로 인해서
//counter라는 객체가 생성된다.
debugger
counter.increase()
counter.increase()
counter.decrease()
counter.decrease()
console.log(counter.increase()) ; //1
console.log(counter.increase()) ; //2
console.log(counter.decrease()) ; //1
console.log(counter.decrease()) ; //0
counter 는 인스턴스가 생성됨. 그리고 Counter.prototype.increase, decrease에서 num 을 참조 하고 있음
따라서 num을 공유함
위 예제의 num(1)은 생성자 함수 Counter가 생성할 인스턴스의 프로퍼티가 아니라 즉시 실행 함수 내에서 성언된 변수다.
만약 num 이 생성자 함수 Counter가 생성할 인스턴스의 프로퍼티라면(2) 인스턴스를 통해 외부에서 접근이 자유로운 public 프로퍼티가 된다.
하지만 즉시 실행 함수 내에서 선언된 num 변수는 인스턴스를 통해 접근할 수 없으며, 즉시 실행 함수 외부에서도 접근할 수 없는 은닉된 변수다.
근데 즉시 실행함수로 안만들어도 왜 문제 없지 ???
생성자 함수 Counter는 프로토타입을 통해 increase, decrease 메서드를 상속받는 인스턴스를 생성한다.
increase, decrease 메서드는 모두 자신의 함수 정의가 평가되어 함수 객체가 될 때 실행중인 실행 컨텍스트의 즉시 실행 함수 컨텍스트의 렉시컬 환경을 기억하는 클로저다.
따라서 프로토타입을 통해 상속되는 프로토타입 메서드일지라도 즉시 실행 함수의 자유 변수 num을 참조할 수있다.
다시 말해, num 변수의 값은 increase, decrease 메서드만이 변경할 수 있다.
변수 값은 누군가에 의해 언제든지 변경될 수 있어 오류 발생의 근본 원인이 될 수 있다.
외부 상태 변경이나 가변 데이터를 피하고 불변성을 지향하는 함수형 프로그래밍에서 부수효과를 최대한 억제하여 오류를 피하고 프로그램의 안정성을 높이기 위해 클로저는 적극적으로 사용된다.
## 함수형 프로그래밍에서 클로저를 활용하는 예제
// 함수를 인수로 전달받고 함수를 반환하는 고차 함수
// 이 함수는 카운트 상태를 유지하기 위한 자유 변수 counter를 기억하는 클로저를 반환한다.
function makeCounter(aux){
// 카운트 상태를 유지하기 위한 자유 변수
let counter = 0;
//클로저를 반환
return function B() {
// 인수로 전달받은 보조함수에 상태 변경을 위임한다.
counter = aux(counter);
return counter
};
}
// 보조함수
function increase(n){
return ++n
}
function decrease(n){
return --n
}
// 함수로 함수를 생성한다.
// makeCounter 함수는 보조 함수를 인수로 전달받아 함수를 반환한다.
const increaser = makeCounter(increase) ; // (1)
console.log(increaser()) // 1
console.log(increaser()) // 2
// increaser 함수와는 별개의 독립된 렉시컬 환경을 갖기 때문에 카운터 상태가 연동하지 않는다.
const decreaser = makeCounter(decrease);
console.log(decreaser()) // -1
console.log(decreaser()) // -2
increaser 함수는 makeCounter(increase)의 결과 값.
즉, function(){
counter = increase(counter);
} 가 할당되어 있고
decrease 함수는 makeCounter(decrease)의 결과 값.
즉, function(){
counter = decrease(counter);
} 가 할당되어 있다.
각 함수가 선언되는 시점에 렉시컬 환경이 다르므로 counter를 공유하지 않는다.
makeCounter 함수는 보조 함수를 인자로 전달받고 함수를 반환하는 고차함수다.
makeCounter 함수가 반환하는 함수는 자신이 생성됐을 때의 렉시컬 환경인 makeCounter 함수의 스코프에 속한 counter 변수를 기억하는 클로저다.
makeCounter 함수는 인자로 전달 받은 보조 함수를 합성하여 자신이 반환하는 함수의 동작을 변경할 수 있다.
이때 주의해야 할 것은 makeCounter 함수를 호출해 함수를 반환할 때 반환된 함수는 자신만의 독립된 렉시컬 환경을 갖는다.
이는 함수를 호출하면 그때마다 새로운 makeCounter 함수 실행 컨텍스트의 렉시컬 환경이 생성되기 때문이다.
(1)에서 makeCounter 함수를 호출하면 makeCounter 함수의 실행 컨텍스트가 생성된다. 그리고 makeCounter 함수는 함수 객체를 생성하여 반환한 후 소멸된다.
makeCounter 함수가 반환한 함수
(makeCounter(increase) // return counter = increase(counter) ... ++counter)
는 makeCounter 함수의 렉시컬 환경을 상위 스코프로서 기억하는 클로저이며, 전역 변수인 increaser에 할당된다.
이때 makeCounter 함수의 실행 컨텍스트는 소멸되지만 makeCounter 함수 실행 컨텍스트의 렉시컬 환경은 makeCounter 함수가 반환한 함수의 [[Environment]]
내부 슬롯에 의해 참조되고 있기 때문에 소멸되지 않는다.
(2)에서 makeCounter 함수를 호출하면 새로운 makeCounter 함수의 실행 컨텍스트가 생성된다.
그리고 makeCounter 함수는 함수 객체를 생성하여 반환한 후 소멸된다.
makeCounter 함수가 반환한 함수는 makeCounter 함수의 렉시컬 환경을 상위 스코프로서 기억하는 클로저이며, 전역 변수인 decreaser에 할당된다.
이때 makeCounter 함수의 실행 컨텍스트는 소멸되지만 makeCounter 함수 실행 컨텍스트의 렉시컬 환경은 makeCounter 함수가 반환한 함수의 [[Envirionment]]
내부 슬롯에 의해 참조되고 있기 때문에 소멸되지 않는다.
// 함수를 반환하는 고차함수
// 이 함수는 카운트 상태를 유지하기 위한 자유 변수 counter를 기억하는 클로저를 반환한다.
const counter = (function (){
// 카운트 상태를 유지하기 위한 자유 변수
let counter = 0;
// 함수를 인수로 전달받는 클로저를 반환
return function (aux){
//인수로 전달받은 보조 함수에 상태 변경을 위임한다.
counter = aux(counter)
return counter;
}
}());
function increase(n){
return ++n;
}
function decrease(n){
return --n;
}
debugger
console.log(counter(increase)) // 1
console.log(counter(increase)) // 2
// 자유 변수를 공유한다.
console.log(counter(decrease)) // 1
console.log(counter(decrease)) // 0
counter(increase)를 하게되면 즉시 실행함수로 인해
function (aux){ counter = increase(counter) // <-- aux(counter) 였던 것 return counter; }
가 실행되어 상위 스코프의 counter를 참조하게 된다.
counter 함수선언시점은 같고 인수로 들어오는 함수가 달라지므로 같은 함수 내의 상위 스코프의 counter를 공유한다.
위 예제에서 전역 변수 increaser 와 decreaser에 할당된 함수는 각각 자신만의 독립된 렉시컬 환경을 갖기 때문에 카운트를 유지하기 위한 자유변수 counter를 공유하지 않아 카운터의 증감이 연동되지 않는다.
따라서 독립된 카운터가 아니라 연동하여 증감이 가능한 카운터를 만들려면 렉시컬 환경을 공유하는 클로저를 만들어야 한다.
이를 위해서는 makeCounter 함수를 두 번 호출하지 말아야 한다.