언제가는 한번 정리가 필요했는데... 왜 이렇게 정리하는게 힘들까....
Closure란
함수와 렉시컬(Lexical) 환경의 조합.
함수가 생성될 당시의 외부 변수를 기억하여 생성 이후에도 계속 접근 가능한 함수.
Global하게 어디서든 호출할 수 있는 add함수를 만들자.
function add() {
let counter = 0;
counter += 1;
return counter;
}
add(); //1
add(); //1
add(); //1
함수 호출할때 마다 counter변수가 초기화 되어 결과가 3이 되지 않는다.
Nested Functions를 만들어 함수 밖의 counter변수에 접근할 수 있게 _add함수를 변경하자.
function _add() {
let counter = 0;
return function () {
return ++counter;
};
}
let add = _add();
add(); // 1
add(); // 2
add(); // 3
실행하면 원하는 데로 작동한다.
이것은 Nested Functions의 밖의 환경(Lexical)이 보존되기 때문에 가능하다.
함수는 자신의 바깥쪽의 변수를 접근할 수 있는 특징이 있기 때문에, 안쪽함수는 counter변수를 접근할 수 있지만 _add함수 바깥쪽에서는 _add함수 안쪽에 선언된 counter변수를 접근할 수 없다.
즉 _add함수안쪽의 counter변수가 private화 되어 _add함수 밖의 global환경에서 임의로 counter변수를 변경할 수 없다.
위 로직에 추가로 새로운 변수로 _add함수를 할당하여 실행해보자.
let add2 = _add();
// 새로운 Lexical환경이다.
add2(); // 1
add2(); // 2
add2(); // 3
// 기 작동된 Lexical환경에도 영향이 없다.
add(); // 4
counter변수가 각각의 Lexical환경에서 은닉화되었다.
이걸 더 단순화 시키고 anonymous self-invoking function함수로 만들면 아래와 같다.
let add = (function () {
let counter = 0;
return function () {
return ++counter;
};
})();
add(); // 1
add(); // 2
add(); // 3
위 소스에처럼 function 정의 전체를 ()로 감싸고 이것을 다시()로 호출하는 self-invoke가 되어 선언과 동시에 단 한번 호출이 된다.
앞의 괄호는 함수정의 괄호, 뒤의 괄호는 호출을 위한 괄호. 즉 (함수정의)(호출)가 되어 정의와 동시에 호출이 되는 것이다.
self-invoking 참고 : https://blog.daonelab.com/post/20/1969/
아무튼 이걸 또 Arrow functions으로 만들면 아래와 같다. 결과도 같다.
let add = (() => {
let counter = 0;
return function() {
return ++counter;
};
})();
add(); // 1
add(); // 2
add(); // 3