함수가 생성될 때의 스코프를 활용하는 방법이고, 함수에서 함수를 리턴한다고 했을 때 상위 함수의 외부에서 내부 함수를 호출하더라도 상위 함수의 지역에 접근할 수 있는 함수를 클로저라고 한다.
외부함수가 실행 컨텍스트로 콜 스택에 올라가고 실행되고 Life Cycle이 종료되더라고 내부 함수에 의해서 참조되고 있다면 외부 함수 실행 컨텍스트 안에 활성 객체(내부 변수, 함수 등등이 저장되는 객체)는 유지되어서 내부 함수에서 스코프 체이닝으로 접근할 수 있다.
함수와 함수의 Lexical scope를 이용해 조합하는 것, 내부 함수
1function init(){
2 let name = "kam"
3 function showName(){
4 console.log(name)
5 }
6 showName()
7}
8init()
showName은 init에서만 사용할 수 있다.
1function makeFunc() {
2 let name = "kam"
3 function showName(){
4 console.log(name)
5 }
6 return showName
7}
8
9const logKam = makeFunc()
10logKam()
- makeFunc함수 실행으로 컨텍스트가 콜스택에 쌓인다.
- makeFunc를 스캔하고 실행하면서 “kam”이 콜스택 메모리에 저장되고 name에 주소가 저장된다.
- showName이 스캔되면서 힙, 콜스택에 할당되고 showName에 주소가 저장된다.
- logKam에 showName이 가르키는 주소를 저장한다.
- logKam을 실행시키면 저장된 주소를 통해 메모리에 접근하여 저장된 showName 내부 소스가 실행된다.
- showName 내부의 name을 찾기 위해 컨텍스트에 outenviromentReference를 따라 name을 찾는다.
- name = “kam”을 찾아 log를 찍는다.
자바스크립트는 함수를 리턴하면 리턴하는 함수가 클로저를 형성한다.
클로저가 생성된 시점의 유효한 범위 내에 있는 모든 지역 변수로 구성된다.
1function makeAdder(x){
2 let y = 1
3 return function(z){
4 return x + y+ z
5 }
6}
7
8const add5 = makeAdder(5)
9
10console.log(add5(4)) // 10
makeAdder는 함수를 만들어내는 공장으로 볼 수 있다.
개인적인 생각: 2번 꺽어서 동작하게 만드므로 다양한 경우를 커버할 수 있다.
1function makeSizer(size){
2 return function(){
3 document.body.style.fontSize = size + 'px'
4 }
5}
6
7const size12 = makeSizer(12)
8
9document.getElementById('size-12').onclick = size12
클릭시 실행될 함수를 전달해야할 때 사용할 수 있다.
클로저를 이용해서 private함수를 만들 수 있다.
모듈 패턴
1const makeCounter = function(){
2 let privateCount = 0;
3
4 function changeBy(value){
5 privateCount += value
6 }
7
8 return {
9 increment: function(){
10 changeBy(1)
11 },
12 decrement: function(){
13 changeBy(-1)
14 },
15 value: function(){
16 return privateCount
17 }
18 }
19}()
20
21const counter1 = makeCounter()
22counter1.value() // 0
23counter1.increment()
24counter1.value() // 1
25counter1.decrement()
26counter1.value() // 0
count에 리턴 값으로 숫자에 대한 메소드들이 담긴 객체에 참조가 들어간다.
객체는 생성되었던 환경의 스코프를 저장하고 있고, 변수나 함수가 사용될 때 스코프를 찾아 올라간다.
클로저 스코프 체인
겹치는 함수 범위에서는 공통으로 클로저가 접근한다.
1function showHelp(help) {
2 document.getElementById('help').innerHTML = help;
3}
4
5function makeHelpCallback(help) {
6 return function() {
7 showHelp(help);
8 };
9}
10
11function setupHelp() {
12 var helpText = [
13 {'id': 'email', 'help': 'Your e-mail address'},
14 {'id': 'name', 'help': 'Your full name'},
15 {'id': 'age', 'help': 'Your age (you must be over 16)'}
16 ];
17 // 이 부분
18 for (var i = 0; i < helpText.length; i++) {
19 var item = helpText[i];
20 document.getElementById(item.id).onfocus = makeHelpCallback(item.help);
21 }
22}
23
24 setupHelp();
여기서 var item을 하면 사실상 주석한 부분에 item이 선언된 것과 같기 때문에
나중에 onfocus에서 콜백 클로저를 접근할 때 모든 클로저가 스코프를 타고 올라가서 보면 item = helpText[2]가 된다.
그래서 var 대신 let을 쓰면 블록 스코프로 적용되므로 원하는대로 사용할 수 있고, 아니면 helpText을 바로 사용하는 방법도 있다.
보조 함수 예제
1const makeCounter(predicate){
2 let count = 0
3 return function(){
4 count += predicate(count)
5 return count
6 }
7}
8
9function increase(n){
10 return n += 1
11}
12
13function decrease(n){
14 return n -= 1
15}
16
17const increaser = makeCounter(increase)
18increaser()
19const decreaser = makeCounter(decrease)
20decreaser()
필요성이 사라진 경우에는 식별자에 undefined, null을 할당하면 GC가 수거한다.
네트워트 요청 데이터 캐싱과 같이 자유변수의 참조값을 캐싱하고자 할 때 많이 쓰인다.