함수와 함수가 선언된 어휘적(lexical)환경의 조합을 말합니다. 이 환경은 클로저가 생산된 시점의 유효범위 내에 있는 모든 지역 변수로 구성됩니다. -MDN
자바스크립트는 어휘적 환경, 즉 렉시컬 스코프(선언의 순서)에 의거해 변수를 조회합니다. 일반적으로 이미 실행이 종료된 함수의 변수나 함수를 참조할 수 있는 링크를 가진 내부함수를 뜻합니다.
자바스크립트의 고유개념이 아니라 함수를 일급 객체로 취급하는 함수형 프로그래밍 언어에서 사용되는 특성입니다.
클로저를 이해하기 위해서는 변수와 호이스팅에 대해 한번더 짚어봐야 합니다.
변수 선언 키워드 var
과 let
은 거의 같습니다. 차이점은 var
은 재선언이 가능하고 let
은 불가능하다는 것입니다.
var
는 선언하기 전에 사용해도 에러가 생기지 않습니다. 변수 선언이 최상위로 호이스팅되고 선언과 초기화가 동시에 진행되기 때문입니다.
let
과 const
도 호이스팅(스코프 내부 어디서든 변수 선언은 최상위에 선언된 것 처럼 행동하는 것)은 됩니다. 하지만 TDZ(Temporal Dead Zone) 영역에 호이스팅 되어 사용할 수 없습니다. 이것은 스코프단위로 적용되어 외부스코프에서 선언했더라도 내부 스코프에서 선언하지 않고 쓰인다면 에러가 납니다.
🎀 TDZ (Temporal Dead Zone)
let
키워드로 선언된 변수는 스코프의 시작에서 변수 선언까지 일시적 사각지대에 빠집니다. 따라서 선언 이전에 참조하면 참조에러('ReferenceError`)가 발생합니다.
let
은 선언단계와 초기화 단계가 나뉩니다. 실제 사용될 코드에서 초기화 단계가 진행됩니다. let
과 var
은 이후에 할당단계를 거칩니다(따라서 let
과 var
은 값을 재할당하고 변경할 수 있습니다). const
는 선언과 초기화, 할당이 동시에 진행됩니다. 이러한 과정은 스코프 단위로 이루어집니다.
쉽게 말하자면 함수 내에서 다른 함수(내부 함수)가 리턴되면, 이 함수를 클로저 함수라 부르고, 외부 함수에 있는 변수에 접근이 가능합니다.
함수를 리턴하는 함수입니다. 리턴하는 함수에 의해 스코프가 구분됩니다. 클로저는 스코프를 이용해 변수의 접근 범위를 닫는(closure: 폐쇄) 핵심을 가집니다. 따라서 변수가 선언된 위치가 중요합니다.
외부함수는 내부함수에 선언된 변수에 접근할 수 없습니다. 바깥 스코프에서는 안 쪽 스코프로의 접근이 불가능하기 때문입니다. 반대로 내부함수는 외부함수에 선언된 변수에 접근할 수 있습니다. 안쪽 스코프는 바깥 스코프에서 선언된 변수에 접근이 가능하기 때문입니다.
내부함수는 외부함수의 지역변수에 접근할 수 있는데, 외부함수의 실행이 끝나 외부함수가 소멸된 이후에도 외부함수의 지역변수를 사용하는 내부함수가 소멸될 때까지 외부 함수 내 변수가 메모리 상에 저장됩니다. 따라서 그 이후에도 내부함수가 외부함수의 변수에 접근할 수 있습니다.(어휘적 환경을 메모리에 저장하므로 가능합니다.)
장점 : 클로저는 현재 상태를 기억하고 있다가 상태가 변경되면 최신상태로 유지합니다. 또 변수를 함수내에 숨김으로써 다른이가 접근하여 의도치 않게 값이 변경될 여지를 줄일 수 있습니다.
단점 : 하지만 반대로 함수실행 종료 후 가비지 컬렉션의 대상이 될 객체가 계속해서 메모리 상에 남아있게 되면서 퍼포먼스 저하가 발생할 수 있습니다. 또 클로저를 과용하거나 오용하면 코드가 읽거나 고치기 어려워지고 버그가 발생하기 쉬워지는 부작용도 있습니다.
활용 예시 ) 클로저를 통해 커링(curring, 함수 하나가 n 개의 인자를 받는 대신 n개의 함수를 만들어 각 인자를 받게 하는 방법), 클로저 모듈(변수를 외부 함수 스코프 안 쪽에 감추어 함수 밖에서 노출되는 것을 막는 방법) 등의 패턴을 구현할 수 있습니다.
var
도 블록 레벨 스코프를 가질 수 있게 고려한 것입니다. (function () {
"함수를 괄호로 둘러싸기";
}) (); //첫번째 방법
(function () {
"전체를 괄호로 둘러싸기";
} () ); //두번째 방법
!function () {
"표현식 앞에 부정연산자 붙이기";
} (); //세번째 방법
+function () {
"표현식 앞에 단항 덧셈 연산자 붙이기";
} (); //네번째 방법
let
키워드를 사용하면서 이렇게 코드를 작성할 필요가 없어졌습니다.자바스크립트에서 중요한 패턴 중 하나로, 일반적으로 사용되는 디자인 패턴입니다. 코드 단위를 명확하게 분리하고 구성하기 좋습니다.
클로저를 이용해 내부 함수를 객체에 담아 여러개의 내부 함수를 리턴하도록 만들 수도 있습니다.
클래스를 모방해 관련 있는 변수와 함수를 모아 즉시실행함수로 감싸 하나의 모듈을 만들어 클로저를 기반으로 동작하는 것입니다.
다만 외부스코에서는 내부 스코프의 변수에 접근할 수 없어 함수 내부 변수를 직접 수정할 수는 없습니다. 대신 리턴하는 객체가 제공하는 메서드를 통해 간접적으로 조작할 수 있습니다. 이것을 정보의 접근 제한(캡슐화)라고 합니다.(자바 스크립트는 접근 제한자가 없기 때문에 클로저를 활용해 이런 특징을 흉내내는 것입니다.)
캡슐화를 통해 전역변수로 따로 만들 필요가 없어지면서 side effect를 최소화합니다. 의도되지 않은 변경을 줄이면, 오류로부터 보다 안전하게 값을 보호할 수 있습니다. 즉 클로저로 불필요한 전역 변수 사용을 줄이고 스코프로 값을 안전하게 다루는 것입니다.
함수의 재사용성을 극대화 하여, 함수 하나를 완전히 독립적인 부품 형태로 분리하는 것을 모듈화라고 합니다. 클로저는 데이터와 메서드를 묶어서 모듈화합니다. 하지만 느슨하게 결합한 것으로 언제든 구현을 수정할 수 있는 장점을 가집니다.
느슨한 결합
코드 실행 순서에 따라 절차적으로 코드를 작성하는 것이 아니라, 코드가 상징하는 실제 모습과 닮게 코드를 모아 결합하는 것을 의미합니다.
❓ Execution context
❓ lextical environment
❓ 클로저에 대해 좀 더 이해 요망