클로저는 함수와 함수가 선언된 어휘적 환경의 조합으로 클로저를 이해하려면 자바스크립트가 어떻게 변수의 유효 범위를 지정하는지(lexical scoping)을 먼저 이해해야 한다.
function init() {
var name = "Mozilla"; // name은 init에 의해 생성된 지역 변수이다.
function displayName() { // displayName() 은 내부 함수이며, 클로저다.
alert(name); // 부모 함수에서 선언된 변수를 사용한다.
}
displayName();
}
init();
init은 지역 변수 name과 함수 displayName을 생성. displayName 내부엔 지역 변수가 없다. 그런데 함수 내부에서 외부 함수의 변수에 접근할 수 있기 때문에 displayName이 부모 함수 init 안에서 선언된 변수 name에 접근을 할 수 있다. displayName이 자신만의 name 변수를 가지고 있었다면, **name
대신 this.name
을 사용했을 것이다.**
이는 어휘적 범위 지정(lexial scoping)의 한 예이다. lexical은 어휘적 범위 지정(lexial scoping) 과정에서 변수가 어디에서 사용 가능한지 알기 위해 그 변수가 소스 코드 내 어디에서 선언되었는지 고려한다는 것을 의미한다. 중첩된 함수는 외부 범위에서 선언한 변수에도 접근할 수 있다
ES6 이전 var만 사용 했던 Javascript에선 함수 스코프와 전역스코프 두가지만 존재했다.
var
로 선언한 변수는 함수 내부에서 선언되었을때만 블록 스코프가 되며 if,for문 등 다른 중괄호로 표시된 블록 안에서는 스코프를 생성하지 않아 혼란을 일으킬 수 있는 여지가 있었다.
if (Math.random() > 0.5) {
var x = 1;
} else {
var x = 2;
}
console.log(x); // 콘솔 잘 찍힘.
Javascript의 경우 var로 선언된 변수는 함수에서만 블록 스코프가 되어 조건문 안에 있는 x
는 글로벌 스코프가 되기에 콘솔이 잘 찍힌다.
ES6에선 호이스팅 뿐만 아니라 이러한 문제도 해결하기 위해 let과 const를 도입하며 시간상 사각지대 등을 같이 도입하였다.
if (Math.random() > 0.5) {
const x = 1;
} else {
const x = 2;
}
console.log(x); // ReferenceError: x is not defined
// const와 let은 조건문,반복문 등에서도 지역스코프가 된다.
비슷한 다음 예제
function makeFunc() {
var name = "Mozilla";
function displayName() {
alert(name);
}
return displayName;
}
var myFunc = makeFunc();
//myFunc변수에 displayName을 리턴함
//유효범위의 어휘적 환경을 유지
myFunc();
//리턴된 displayName 함수를 실행(name 변수에 접근)
이 코드는 바로 전 코드와 같을까? 동일한 결과가 실행된다. 하지만 차이가 있다면 displayName
함수가 실행되기 전에 외부함수인 makeFunc
로 부터 리턴되어 myFunc
변수에 저장된다는 것이다.
솔직히 처음 봤을때는 무슨차이가 있나 싶었다. makeFunc
함수의 실행이 끝나면 name
변수에 더이상 접근할 수 없을 걸로 생각했었다.
하지만 자바스크립트는 다르다고한다. 그 이유로는 자바스크립트는 함수를 리턴하고, 리턴하는 함수가 클로저를 형성하기 때문에. 클로저란 함수와 함수가 선언된 어휘적 환경의 조합이다 이 환경은 클로저가 생성된 시점의 유효 범위 내에 있는 모든 지역 변수로 구성된다. 첫번째 예시의 경우, myFunc
은 makeFunc
이 실행 될 때 생성된 displayName
함수의 인스턴스에 대한 참조다. 이런 이유로 displayName의 인스턴스는 변수 name이 있는 어휘적 환경에 대한 참조를 유지. 이런 이유로 myFunc
가 호출될 때 변수 name
은 사용할 수 있는 상태로 남게 되고 "Mozilla"가 alert에 전달된다.
이 다음 예제를 한번 보자
function makeAdder(x) {
var y = 1;
return function(z) {
y = 100;
return x + y + z;
};
}
var add5 = makeAdder(5);
var add10 = makeAdder(10);
//클로저에 x와 y의 환경이 저장됨
console.log(add5(2)); // 107 (x:5 + y:100 + z:2)
console.log(add10(2)); // 112 (x:10 + y:100 + z:2)
//함수 실행 시 클로저에 저장된 x, y값에 접근하여 값을 계산
이 예제에서 단일 인자 x
를 받아서 새 함수를 반환하는 함수 makeAdder(x)
를 정의했다. 반환되는 함수는 단일인자 z를 받아서 x와 y와 z의 합을 변환하다.
본질적으로 makeAdder
는 함수를 만들어내는 공장이다. 이는 makeAdder
함수가 특정한 값을 인자로 가질 수 있는 함수들을 리턴한다는 것을 의미. 위 코드에서 add5
, add10
두 개의 새로운 함수들을 만들기 위해 makeAdder
함수를 사용. 하나는 매개변수 x에 5를 더하고 다른 하나는 매개변수 x에 10을 더한다.
add5
와 add10
은 둘 다 클로저이다. 이 둘은 같은 함수 본문 정의를 공유하지만 서로 어휘적 환경을 저장한다. 함수 실행시에 add5
의 맥락적 환경에서 클로저 내부의 x
는 5이지만 add10
의 맥락적 환경에서 x는 10이다. 또한 리턴되는 함수에서 초기값이 1로 할당된 y에 접근하여 y 값을 100으로 변경한 것을 볼 수 있다. (물론 x값도 동일하게 변경 가능하다.) 이는 클로저가 리턴된 후에도 외부함수의 변수들에 접근 가능하다는 것을 보여주는 포인트이며 클로저에 단순히 값 형태로 전달되는것이 아니라는 것을 의미한다.
솔직히 오늘 클로저 처음 본거 아니였다... 근데 첨에 봤을땐 이게 뭔지 싶었다. 여러 블로그를 돌아다녔지만 결국 이해시켜준건 mdn. 충성충성