Closure를 이해하기 위해서는 Lexical Scope를 알아야 한다
변수의 유효범위(사용할 수 있는 범위)를 Lexical Scope라고 한다
아래 예시를 보면 sum 함수 안에서 연산을 실행하고 결과를 출력하는 함수를 호출하는데 이 함수는 sum의 지역변수인 result 변수를 사용할 수 있다
const sum = (a, b) => {
let result = a + b;
const displayResult = () => {
console.log(result);
}
displayResult();
}
sum(1, 2);
sum(2, 2);
sum 함수를 통해 출력하는 결과들은 각자 다르다는 것을 확인 할 수 있다
결과 값 :
3
4
displayResult 함수가 sum 함수의 지역변수인 result에 접근할 수 있다
-> 함수(displayResult)의 선언된 위치가 sum 함수와 중첩되어 있어서 외부(sum) Lexical Scope에 접근할 수 있다는 뜻이다
! Lexical Scope는 Static Scope이라고도 한다 !
함수의 호출이 아니라 함수의 선언에 따라 결정된다
함수를 어디서 선언하였는지에 따라 상위 스코프를 결정한다
JavaScript가 Dynamic Scope를 따르고 있다면 (함수의 실행으로 상위 스코프가 결정된다면 ) 아래의 코드에서 sum(5+7)의 결과가 12, displayResult()의 결과가 1이여야 한다
let result = 1;
const sum = (a, b) => {
let result = a + b;
displayResult();
}
function displayResult(){
console.log(result);
}
sum(5 + 7);
displayResult();
함수의 선언에 따라 상위 스코프가 결정되었기 때문에 결과값은 아래와 같다
1
1
MDN - Closure 에서는
함수와 함수가 선언된 Lexical(Static) 환경의 조합이라고 정의한다
정의를 보고 나서도 이해가 잘 되지 않아서 검색을 통해 알아보아 정리하였다
아래의 sum 함수에서 지역변수 result의 변수 값을 콘솔에 출력하는 함수를 반환하고 있다고 하자
const sum = (a, b) => {
let result = a + b;
return () => {
console.log(result);
}
}
let three = sum(1, 2);
let four = sum(2, 2);
감각적으로 three와 four 함수는 각자 다른 연산 값을 출력할 것이라고 예상할 수 있었고 결과 값도 아래와 같았다
3
4
이 때 반환된 함수에서 참조된 변수가 함수 실행이 끝났다고 해서 사라지지 않고 여전히 제대로 된 값을 반환하고 있는 걸 알 수 있다
반환된 함수(함수 내에서 함수를 정의하고 사용)를 클로저(Closure) 라고 한다
Closure가 함수와 함수가 선언된 Lexical(Static) 환경을 기억하고 있기 때문에 지역 변수를 무사히 참조할 수 있는 것이다
모든 클로저에는 세가지 스코프(범위)가 있다고 한다
클로저에서 찾을 수 없는 변수를 찾을 때 상위 외부함수를 통해 변수를 찾게 되는데 이런 환경을 클로저 스코프 체인이라고 한다
다음과 같이 함수를 중첩으로 선언하고 외부 지역변수들과 전역변수까지 사용한다고 보자
let e = 10;
const sum = (a) => {
return (b) => {
return (c) => {
return (d) => {
// 지역 범위 (local scope)
return a + b + c + d + e;
}
}
}
}
console.log(sum(1)(2)(3)(4));
let s = sum(1);
let s1 = s(2);
let s2 = s1(3);
let s3 = s2(4);
console.log(s3);
결과 값은 아래와 같이 무사히 계산을 완료하는 것을 볼 수 있다.
20
20
따라서 모든 클로저들은 클로저 스코프 체인을 통해 세가지 범위의 변수들을 참조할 수 있고 모든 클로저가 선언된 외부 함수의 스코프에 접근한다라고 말할 수 있다
0-9 까지의 정수를 출력하는 코드를 만들고 싶어 아래와 같이 작성해봐도 출력은 원하는 대로 되지 않는다.
let i;
for (i = 0; i < 10; i++) {
setTimeout(() => {
console.log(i);
}, 100);
}
setTimeout()에서 실행되는 함수는 모두 0.1초 뒤에 호출된다
이 때 0.1초 뒤에는 반복문이 전부 돌아서 i가 10인 상태이므로 출력은 10이 될 것이다.
10
10
10
10
10
10
10
10
10
10
이를 정상적으로 출력시키려면 Closure를 이용하면 된다.
for (let i = 0; i < 10; i++) {
setTimeout(() => {
console.log(i);
}, 100);
}
혹은
let i;
for (i = 0; i < 10; i++) {
((x) => {
setTimeout(() => {
console.log(x);
}, 100);
})(i);
}
출력값
0
1
2
3
4
5
6
7
8
9
아래의 코드에서는 three 클로저가 계속 result 변수를 참조하고 있게 된다.
const sum = (a, b) => {
const result = a + b;
return () => {
console.log(result);
};
}
let three = sum(1, 2);
클로저를 통해 내부 변수를 참조하면 차지하는 메모리를 Garbage Collector(가비지 컬렉터)가 회수하지 않아서 클로저 사용이 끝나면 참조를 제거하는 것이 좋다고 한다
three();