클로저란?
클로저는 자신이 선언될 당시의 환경을 기억하는 함수다.
클로저는 함수와 그 함수가 선언된 렉시컬 환경과의 조합이다.
해당 함수의 생명 주기가 종료되더라도 함수의 반환된 값이 변수에 의해 참조되고 있다면 생명 주기가 종료되더라도 (실행 컨텍스트 스택에서 제거되어 pop 되더라도) 렉시컬 환경에 남아 참조가 가능하다.
클로저를 사용하면 뭐가 좋은 거지?
클로저는 상태를 안전하게 변경하고 유지하기 위해 사용한다.
다시 말해, 상태가 의도치 않게 변경되지 않도록 상태를 은닉하고 특정 함수에게만 상태 변경을 허용하기 위해 사용한다.
클로저를 어떻게 생성하나요?
1. 내부(중첩) 함수가 익명 함수로 되어 외부 함수의 반환값으로 사용될 때
2. 내부(중첩) 함수가 외부 함수의 스코프에서 실행될 때
3. 내부 함수에서 사용되는 변수가 외부 함수의 변수 스코프에 포함되어 있을 때
var name = `Global`;
function outer() {
var name = `closure`;
return function inner() {
console.log(name);
};
}
var callFunc = outer();
callFunc();
위 코드에서 callFunc를 클로저라고 한다. callFunc 호출에 의해 name이라는 값이 console에 찍히는데, 찍히는 값은 Global이 아니라 closure라는 값이다. 즉 outer 함수의 context에 속해있는 변수를 참조하는 것이다. 여기서 outer 함수의 지역변수로 존재하는 name 변수를 free variable(자유변수)라고 한다.
이처럼 외부 함수 호출이 종료되더라도 외부 함수의 지역 변수 및 변수 스코프 객체의 체인 관계를 유지할 수 있는 구조를 클로저라고 한다.
다양한 케이스
case 1 상위 스코프의 식별자를 참조하지 않는 경우
<!DOCTYPE html>
<html lang="en">
<body>
<script>
function foo(){
const x = 1;
const y = 2;
function bar(){
const z = 3;
debugger;
// 상위 스코프의 함수(foo)의 어떠한 식별자도 참조하지 않았다.
console.log(z);
}
return bar;
}
const bar = foo();
bar();
</script>
</body>
</html>
위 예제의 중첩 함수 bar는 외부 함수 foo 보다 더 오래 유지되지만 상위 스코프의 어떤 식별자(x, y)도 참조하지 않는다.
이처럼 상위 스코프의 어떤 식별자도 참조하지 않는 경우 대부분의 모던 브라우저는 최적화를 통해 사진과 같이 상위 스코프를 기억하지 않는다.
참조하지도 않는 식별자를 기억하는 것은 메모리 낭비이기 때문이다. 따라서 bar 함수는 클로저라고 할 수 없다.
참조하는 식별자를 실행 컨텍스트가 종료되어도 렉시컬 환경을 통해 참조하고, 값을 변경할 수 있는 것이 클로저이다.
case2 상위 스코프의 식별자를 참조하지만, 중첩 함수가 반환되지 않는 경우
<!DOCTYPE html>
<html lang="en">
<body>
<script>
function foo() {
const x = 1;
// 일반적으로 클로저라고 하지 않는다.
// bar 함수는 클로저였지만 곧바로 소멸한다.
function bar() {
debugger;
// 상위 스코프의 식별자를 참조
console.log(x);
}
bar();
}
foo();
</script>
</body>
</html>
위 예제의 중첩 함수 bar는 상위 스코프의 식별자를 참조하고 있으므로 클로저다.
하지만 외부 함수 foo의 외부로 중첩 함수가 반환되지 않는다.
즉, 외부 함수 foo보다 중첩 함수 bar의 생명 주기가 짧다. 이런 경우 중첩 함수 bar는 클로저였지만 외부 함수보다 일찍 소멸되기 때문에 생명 주기가 종료된 외부 함수의 식별자를 참조할 수 있다는 클로저의 본질에 부합하지 않는다.
따라서 중첩 함수 bar는 일반적으로 클로저라고 하지 않는다.
case3. 상위 스코프의 식별자를 참조하고, 중첩 함수가 반환되는 경우 (올바르게 사용된 클로저)
<!DOCTYPE html>
<html lang="en">
<body>
<script>
function foo() {
const x = 1;
const y = 2;
// 클로저
// 중첩 함수 bar는 외부 함수보다 더 오래 유지되며 상위 스코프의 식별자를 참조
function bar() {
debugger;
console.log(x);
}
return bar;
}
const bar = foo();
bar();
</script>
</body>
</html>
위 예제의 중첩 함수 bar는 상위 스코프의 식별자를 참고하고 있으므로 클로저다.
그리고 외부 함수의 외부로 반환되어 외부 함수보다 더 오래 살아남는다.
이처럼 외부 함수보다 중첩 함수가 더 오래 유지되는 경우 중첩 함수는 이미 생명 주기가 종료한 외부 함수의 변수를 참조할 수 있다. 이러한 중첩 함수를 클로저라고 부른다.
즉, 클로저는
1. 중첩 함수가 상위 스코프의 식별자를 참조하고 있고
2. 중첩 함수가 외부 함수보다 더 오래 유지되는 경우
에 한정하는 것이 일반적이다.
'🍞 Front-End > JavaScript' 카테고리의 다른 글
[JavaScript] 브라우저 렌더링 과정 (0) | 2024.10.17 |
---|---|
[JavaScript] Event Bubbling, Capturing (2) | 2024.09.04 |
[JavaScript] 스코프(Scope)에 대해서 (3) | 2024.08.28 |
[JavaScript] JS 클로저란? (closure) (2) | 2022.10.05 |
[JavaScript] JavaScript 데이터 실습 - OMDb API (0) | 2022.10.03 |