들어가기 앞서
자바스크립트에는 실행 컨텍스트 라는 개념이 있으며, 이 실행 컨텍스트는 생성되었다가 사라질 수 있습니다.
함수 실행을 마치고나면 call stack에 있던 해당 함수에 대한 실행 컨텍스트는 사라질 것입니다.
const secureBooking = function() {
let passengerCount = 0;
return function() {
passengerCount++;
console.log(`${passengerCount} passengers`)
}
}
// 함수를 return 받았기 때문에 booker도 함수가 될것이다
const booker = secureBooking();
위의 코드에서, "secureBooking" 이라는 함수를 생성하고 return 값으로 함수를 반환하도록 했습니다.
그리고 booker 에 해당 return 값인 함수를 저장했습니다.
booker를 실행 할 때마다 passengerCount 값은 계속 증가하는지 보겠습니다.
booker();
booker();
booker();

정의대로라면 secureBooking 함수를 실행하고나면 실행 컨텍스트가 call stack에서 사라지고,
변수 환경 또한 사라져서 passengerCount 변수에 접근할 수 없어야 합니다.
그러나 제대로 출력이 되는 것을 볼 수 있었는데, 이것이 클로저가 하는 역할입니다.
클로저(closure)
클로저가 있기 때문에 함수가 정의된 시점의 모든 변수를 기억할 수 있습니다.
booker();
이 booker 함수가 실행되기 위해,
1) 먼저 call stack에 변수 환경이 비어있는 채로 실행 컨텍스트가 생성됩니다.
전역 스코프에서 실행되었기 때문에 여전히 passengerCount 변수에 접근할 수 없습니다.
2) 그러나 booker 함수는 클로저 라는 기능 덕분에
"해당 함수(secureBooking) 가 정의된 시점의 모든 변수 환경에 접근할 수 있습니다."
이는 해당 실행 컨텍스트가 사라져도 적용됩니다.
즉 클로저는
정확히 함수가 생성되었을 시점의 함수에 붙어있는 변수환경
이라고 할 수 있습니다.
클로저 덕분에 함수가 변수에 대한 연결을 계속해서 유지할 수 있는 것입니다.

함수가 사람이고, 배낭이 클로저이며, 변수환경이 물건들이라면,
사람이 어디를 가든 배낭을 가져가기 때문에 그 안에 들어있는 변수환경 또한 계속해서 가지고 다니는 것입니다.
그리고 클로저는 기본적으로 스코프 체인(scope chain) 보다 우선순위가 높습니다.
// 전역 스코프에 선언
let passengerCount = 123;
const secureBooking = function() {
let passengerCount = 0;
return function() {
passengerCount++;
console.log(`${passengerCount} passengers`)
}
}
const booker = secureBooking();
booker();
booker();
booker();
따라서 전역 스코프에 passengerCount 변수가 이미 있더라도
클로저를 먼저 살펴보기 때문에 아까와 같은 결과값이 출력됩니다.
그리고 클로저는 함수의 내부 속성일 뿐이기 때문에, 클로저로부터 변수를 가져와서 사용할 순 없습니다.
그저 클로저 기능이 기본적으로 있기 때문에 우리에게 보여지게 되는 것입니다.
그리고 클로저를 살펴보고싶다면, console.log 대신 console.dir 를 사용하면 됩니다.
console.dir(booker);

예시
1)
let f;
const g = function() {
const a = 23;
f = function() {
console.log(a * 2);
}
}
g();
f();
g 함수를 실행하면, f 변수에 새로 함수가 저장되는데,
이때 앞서 말한대로 g 함수가 실행이 끝나고 실행 컨텍스트가 삭제 되더라도, 변수 환경은 계속해서 연동되어 있기
때문에 정상적으로 출력이 될 것입니다.

그렇다면 h 함수를 선언하고 f에 재할당 한다면 어떻게 될까요?
const g = function() {
const a = 23;
f = function() {
console.log(a * 2);
}
}
const h = function() {
const b = 777;
f = function() {
console.log(b * 2);
}
}
// g 함수에 대한 실행 컨텍스트는 제거되었지만
g();
// a 변수는 앞서 말한대로 배낭 안에 있습니다
f();
// 재할당
// 이전 클로저는 사라짐
h();
f();

이전 클로저는 삭제되며 새로운 클로저가 생성되는 것을 볼 수 있습니다.
이렇듯 클로저는 재할당이 가능하다는 특징이 있습니다
2) 타이머
타이머를 사용하여 클로저가 존재하는지 확인해보겠습니다
const boardPassengers = function(n, wait) {
const perGroup = n / 3;
setTimeout(() => {
console.log(`${n}명과 함께 출발했음`);
console.log(`3 그룹, ${perGroup}명의 승객`)
}, 1000)
console.log(`${wait}초 후 시작`)
}
// 스코프 체인이 우선순위가 높다면 아래의 변수를 사용할 것이다
const perGroup = 1000;
boardPassengers(180, 3);

역시 타이머를 적용해도 클로저는 남아있어서 변수 환경이 그대로 연결되어있는 것을 확인할 수 있으며,
perGroup 변수를 전역스코프에 선언해도
클로저의 우선순위가 스코프 체인보다 높기 때문에 전역변수 대신 함수 내에 있는 변수가 사용된 것을 볼 수 있습니다
'Javascript' 카테고리의 다른 글
| javascript - call, apply, bind (0) | 2021.08.22 |
|---|---|
| javascript - first-class function vs higher-order function (0) | 2021.08.20 |
| javascript - string 여러가지 활용 (0) | 2021.08.20 |
| javascript - Map 에 대해 (0) | 2021.08.13 |
| javascript - optional chaining (?.) (0) | 2021.08.13 |