클로저(Closure)는 무엇이며, 어떻게/왜 사용하나요 ?
사전 지식
클로저를 이해 하기 위해서 Scope, Scope Chain, Context 에 대한 이해가 선행되어야 합니다.
스코프(Scope)
프로그래밍에서 변수에 접근 할 수 있는 유효 범위
- var 는 선언된 곳과 가장 가까운 함수를 유효 범위로 가집니다 (함수 스코프)
- let, const 는 선언된 곳과 가장 가까운 블록을 유효 범위로 가집니다 (블록 스코프)
스코프 체인(Scope Chain)
식별자를 찾을 때 자신이 속한 스코프에서 찾고 없으면 상위 스코프에서 다시 찾는 현상 입니다.
렉시컬 스코프(Lexical Scope) - 정적스코프
함수를 어디서 선언 하였는지에 따라서 상위 스코프를 결정하는 것으로 JavaScript는 렉시컬 스코프를 따릅니다.
예제
// global scope
var number = 10;
function foo () {
var number = 1;
bar();
}
// local scope
function bar () {
console.log(number);
}
foo(); // ?
bar(); // ?
위의 예제에서 bar 함수내에 선언 된 number가 없어서 찾을 수 없으므로 스코프 체인으로 상위 스코프에서 number 를 찾게 됩니다. bar 함수는 전역에 선언 되었으므로 글로벌 스코프의 전역 변수를 상위 스코프로 가지게 되어 전역 변수의 number를 찾아서 10이 출력됩니다.
다이나믹 스코프(Dynamic Scope) - 동적스코프
함수의 호출에 따라 상위 스코프가 정해지는 것으로 Perl, Bash Shell 에서 다이나믹 스코프를 따릅니다.
예제
#!/bin/sh
NUMBER=10
foo(){
NUMBER=1
bar
}
bar(){
echo $NUMBER
}
foo # ??
bar # ??
위의 예제는 다이나믹 스코프를 따르는 쉘 스크립트로 둘다 1이 출력됩니다. bar 를 먼저 호출 하고 foo 를 호출 하면 10, 1 이 출력됩니다.
클로저(Closure)
무엇인가?
내부 함수를 리턴하는 외부 함수가 종료된 후에 내부 함수를 실행 해도 렉시컬 스코프 환경을 유지하여 실행 이전에 기억하고 있던 변수에 접근 할 수 있는 특성
// 외부함수
function outer () {
const x = 10;
// 내부함수
return function (y) {
return x * y;
}
}
const carryOuter = outer();
console.log(carryOuter(10));
위의 예제에서 outer 의 내부 익명함수의 렉시컬 스코프는 y와 스코프 체인으로 outer 를 상위 스코프로 갖게 되어 x 변수에 접근 할 수 있습니다. outer 는 내부 함수를 carryOuter에게 리턴 하고 Call Stack 에서 제거 되었지만, carryOuter() 를 실행하면, 내부 익명함수는 렉시컬 스코프 환경을 유지하여 outer 함수에 선언된 x 에 접근 할 수 있게 됩니다. 이러한 특성을 클로저라고 합니다.
어떻게/왜 사용하는가?
- 모듈 패턴으로 데이터를 보호하고 캡슐화 합니다.
- 함수형 프로그래밍에서 curring 패턴으로 사용합니다.
- 생성자가 한 개의 객체만을 생성하는 싱글톤 패턴에서 사용합니다.
단점은?
스코프가 종료 된 후에도 스코프 밖에서 언젠든지 호출 될 수 있도록 메모리에 계속해서 저장하고 있기 때문에 메모리 사용량이 늘어납니다.
클로저 해제는 null 을 대입합니다.