클로저(Closure)란?
정의 1
“A closure is the combination of a function and the lexical environment within which that function was declared”
클로저는 함수와 그 함수가 선언됐을 때의 렉시컬 환경(Lexical environment)과의 조합이다.
- 함수 makeCounter()를 호출하면 내부 함수 function() { return count++ }가 반환된다.
- makeCounter()의 실행 컨텍스트는 소멸한다.
- 그렇지만 우리는 counter() 를 콘솔로 찍어보면 makeCounter()의 지역변수인 count의 값을 알 수 있다!
정의 2
이처럼 자신을 포함하고 있는 외부함수보다 내부함수가 더 오래 유지되는 경우, 외부 함수 밖에서 내부함수가 호출되더라도 외부함수의 지역 변수에 접근할 수 있는데 이러한 내부함수를 클로저라고 부른다.
정의 3
클로저는 자신이 생성될 때의 렉시컬 환경을 기억하는 함수다.
클로저의 동작원리
- counter.[[Environment]]에는 makeCounter() 렉시컬 환경에 대한 참조가 저장
- counter()를 호출하면 각 호출마다 새로운 렉시컬 환경이 생성
- 그리고 이 렉시컬 환경들은 모두 counter.[[Environment]]에 저장된 렉시컬 환경을 외부 렉시컬 환경으로 참조
- counter() 함수 호출 시 count 변수가 필요하다!
- 내부 렉시컬 환경에서 변수 찾기 → count 없음
- 외부 렉시컬 환경에서 변수 찾기 → count 찾음
- count++ 가 실행되면서 count 값 증가 → count 변수가 저장된 렉시컬 환경에서 갱신
function makeCounter() {
let count = 0;
return function () {
return count++;
};
}
const counter = makeCounter();
console.log(counter()); // 0
console.log(counter()); // 1
console.log(counter()); // 2
- 즉, "counter()를 호출하면 각 호출마다 새로운 렉시컬 환경이 생성" 됨에도 불구하고,
- count 변수는 변수가 저장된 렉시컬 환경에서 갱신되기 때문에,
- 각 counter()들이 외부 렉시컬 환경으로 동일하게 바라보고 있는 counter.[[Environment]] 에서 갱신이 이루어져,
- 위 코드의 출력이 0 0 0 이 아닌 0 1 2 인 것이다.
이렇게 count와 같이 값은 외부함수의 렉시컬 환경에서 갱신되면서, 생명주기는 실행함수에 따르는 변수를 자유변수라고 한다.
언제 클로저가 생성될까?
자바스크립트에서는 모든 함수가 클로저가 된다 (new Function 예외)
- 자바스크립트의 모든 함수는 [[Environment]]를 이용해 자신이 어디서 만들어졌는지를 기억하기 때문.
하지만 관점에 따라 아래와 같은 조건이 추가된다.
외부 함수 보다 내부 함수의 생명 주기가 길어야 한다.
var a = 10;
var b = 20;
function f1() {
return a + b;
}
f1(); // 30
(브라우저의 경우) f1의 외부가 전역 객체이므로 a, b는 계속해서 유지되기 때문에 f1은 클로저가 아니다.
function f4() {
var a = 10;
var b = 20;
function f5() {
return a + b;
}
return f5();
}
f4(); // 30
- f4의 마지막 라인을 보면 f5를 실행하여 리턴한다.
- 결국 f5를 참조하고 있는 곳이 어디에도 없기 때문에 f5는 사라지고,
- f5가 사라지면 a, b도 사라질 수 있기 때문에 클로저는 f4가 실행되는 사이에만 생겼다가 사라진다.
내부 함수가 외부 함수의 식별자를 참조하고 있어야 한다.
function f2() {
var a = 10;
var b = 20;
function f3(c, d) {
return c + d;
}
return f3;
}
var f4 = f2();
f4(5, 7); // 12
f3()에서 외부 함수인 f2()의 a, b를 참조하고 있지 않으므로 f3()은 클로저가 아니다.
클로저를 어떻게 활용할 수 있을까?
상태 유지
현재 상태를 기억하고 이 상태가 변경된 후 최신 상태를 기억해야 하는 상황
ex) 리액트의 useState 훅
전역 변수의 사용 억제 / 정보의 은닉
자유변수는 외부 접근이 불가하므로 의도되지 않은 변경을 막을 수 있다.
참고자료
'TIL > JavaScript' 카테고리의 다른 글
[240201] 실행 컨텍스트 - (2) (1) | 2024.02.01 |
---|---|
[240130] this (0) | 2024.01.30 |
[240130] 렉시컬 환경, 렉시컬 스코프 (1) | 2024.01.30 |
[240130] 스코프, 스코프 체인, 프로토타입, 프로토타입 체인 (0) | 2024.01.30 |
[240130] 자바스크립트의 객체 생성 방법(객체 리터럴 vs new Object() vs 생성자 함수 vs 클래스) (1) | 2024.01.30 |