function init() {
var name = "Mozilla"; // name is a local variable created by init
function displayName() {
// displayName() is the inner function, that forms the closure
console.log(name); // use variable declared in the parent function
}
displayName();
}
init();
init()을 호출하면 지역 변수(local variable)인 name과 함수 displayName()을 호출한다. displayName()은 init 내부에 정의되어, 마찬가지로 init에 정의된 변수인 name을 사용하는 함수다. displayName이 name을 사용할 수 있는 이유는 함수가 자신을 감싸고 있는 주위 환경, 다시 말해 바깥에 있는 변수에 접근할 수 있기 때문이다 — 그리고 우리는 실행 컨텍스트 관점으로는 이것이 LexicalEnvironment의 outerEnvironmentReference 덕분에 가능한 것이라는 것을 알고 있다.
이는 lexical scoping의 예시라고 볼수도 있다. JavaScript에서, 함수가 접근할 수 있는 변수의 범위, 즉 스코프는 함수 실행시(dynamic scoping)가 아니라, 함수 선언 시에 결정되는데, 이러한 방식을 lexical scoping / static scoping 이라고 한다. 즉 위 코드에서 displayName은 함수 init의 내부에서 선언되었기 때문에, init의 function scope안에 속하게 되고, 따라서 동일 스코프에 속한 name에 접근할 수 있는 것이다. (In this particular example, the scope is called a function scope, because the variable is accessible and only accessible within the function body where it's declared.)
전통적으로는(before ES6), JavaScript에는 오직 두가지의 스코프만이 존재했다: function scope와 global scope다. var를 통해 선언된 변수들은 함수 안에서 선언되었냐 아니면 밖에서 선언되었느냐에 따라 function-scope이거나 global-scope로 결정된다. 이때 curly braces가 스코프를 형성하는 것이 아니기 때문에, 이 부분이 까다롭게 느껴질 수 있다.
if (Math.random() > 0.5) {
var x = 1;
} else {
var x = 2;
}
console.log(x);
C, Java와 같이 블록이 스코프를 형성하는 다른 언어를 배운 사람들은 위 코드는 에러를 발생시켜야 한다고 생각할 것이다. 하지만, 블록은 var에 대해서는 스코프를 형성하지 않기 때문에, 여기서의 var은 global scope다. var는 정말로 그냥 function안에 있으면 function scope, 아니면 무조건 global scope인 것이다. 이런 부분은 예를들면 클로저와 함께 사용할때 버그를 야기할 수 있기 때문에 조심해야 한다. (예시: Creating closures in loops: A common mistake)
하지만 JavaScript는 ES6에서 let과 const 선언을 도입하여, block-scoped variables를 생성할 수 있도록 하였다.
if (Math.random() > 0.5) {
const x = 1;
} else {
const x = 2;
}
console.log(x); // ReferenceError: x is not defined
ES6에서 마침내 블록은 스코프로 취급될 수 있게 되었다(물론 let과 const를 사용하는 경우). 여기에 더해, ES6는 modules를 도입했는데, module 또한 module scope를 생성한다. 클로저는 이 모든 네가지 스코프(function, global, block, module) 속에서 변수를 붙잡아둘 수 있는데, 이는 앞으로 다뤄볼 것이다.