24. 클로저
- 함수를 일급 객체로 취급하는 함수형 프로그래밍 언어의 중요한 특성
- cf. 하스켈(Haskell), 리스프(Lisp), 스칼라(Scala) 등
- (MDN) 함수와 그 함수가 선언된 렉시컬 환경과의 조합
- 외부 함수 내부에서 중첩 함수를 정의하고 호출하면 외부 함수의 변수에 접근 가능
24.1. 렉시컬 스코프
- 렉시컬 스코프(정적 스코프): 자바스크립트는 함수를 정의한 위치에 따라 상위 스코프를 결정함
- 외부 렉시컬 환경에 대한 참조 = 상위 렉시컬 환경 = 상위 스코프
- 즉, 상위 스코프에 대한 참조는 함수 정의가 평가되는 시점에 정의된 환경(위치)에 의해 결정됨
24.2. 함수 객체의 내부 슬롯 [[Enviroment]]
- 렉시컬 스코프를 위해 내부 슬롯 [[Enviroment]]에 상위 스코프의 참조를 저장함
- 상위 스코프는 현재 실행 중인 실행 컨텍스트의 렉시컬 환경 참조임
24.3. 클로저와 렉시컬 환경
- 클로저(closure): 외부 함수보다 중첩 함수가 오래 유지될 때 이미 생명 주기가 종료된 외부 함수의 변수를 참조할 수 있음
- 외부 함수 생성할 때 전역 렉시컬 환경을 내부 슬롯에 상위 스코프로 저장함
- 중첩 함수가 평가되어 실행될 때 자신의 내부 슬롯에 외부 함수의 렉시컬 환경을 상위 스코프로 저장함
- (상위 스코프 = 외부 함수 내부 슬롯 = 내부 함수 내부 슬롯)
- 외부 함수가 내부 함수를 반환하면서 실행이 종료되면서 생명 주기도 종료됨
- 외부 함수의 실행 컨텍스트는 제거되지만, 내부 함수에서 참조되고 있기 때문에 렉시컬 환경(내부 슬롯)은 소멸하지 않음 -> 가비지 컬렉션 대상 X
- 외부 함수가 반환한 내부 함수를 호출하면 내부 함수의 실행 컨텍스트가 생성되고, 내부 함수 내브 슬롯에 저장된 렉시컬 환경(상위 스코프)을 유지함
- 상위 스코프의 식별자를 참조하지 않는 함수는 클로저가 아님
- 일반적으로 클로저는 중첩 함수가 상위 스코프의 식별자를 참조하고, 외부 함수보다 더 오래 유지되는 경우를 말함
24.4. 클로저의 활용
- 클로저는 상태를 안전하게 은닉 및 유지하고 특정 함수에게만 변경하도록 허용함
const counter = (function() {
let num = 0;
return {
increase() {
return ++num;
},
decrease() {
return num > 0 ? --num: 0;
}
};
}());
console.log(counter.increase());
console.log(counter.increase());
console.log(counter.decrease());
console.log(counter.decrease());
- 매개변수를 함수로 전달받아 반환하는 고차 함수로도 활용 가능
24.5. 캡슐화와 정보 은닉
- 캡슐화(encapsulation): 객체의 프로퍼티와 메서드를 하나로 묶는 것
- 정보 은닉(information hiding): 특정 객체의 프로퍼티나 메서드를 감추기 위해 캡슐화함
- 정보 은닉을 통해 정보를 보호하고, 객체 간 상호 의존성(결합도)을 낮춤
- 자바스크립트는 접근 제한자를 제공하지 않음
- 클로저를 통해 정보 은닉 효과를 만들 수 있음
- 단, 여러 개의 인스턴스를 생성할 경우, 하나의 동일한 상위 스코프를 공유하기 때문에 일부 변수의 상태가 유지되지 않는 단점이 발생
- private 필드를 정의할 수 있는 새로운 사양 예상됨
24.6. 자주 발생하는 실수
- var 키워드 사용: 함수 레벨 스코프이므로 전역 변수가 되어 식별자 값을 유지하기 어려움
- ES6 let 키워드 사용
- for문 등 코드 블록을 반복 실행할 때마다 항상 새로운 렉시컬 환경을 생성하고 값을 초기화함
- 고차 함수 사용
- 변수와 반복문 사용을 억제하여 오류를 줄이고 가독성 높임
- ex)
Array.forEach, Array.from 등
[출처] 모던 자바스크립트, Deep Dive