프로그래밍에서 scope란 변수의 유효범위를 나타내는 용어이다.
Javascript는 Lexical scope를 따르고 있다는데 그럼 Lexical scope는 무엇일까?
결론부터 말하면 Lexical scope는 함수를 어디서 선언하였는지에 따라 상위 스코프를 결정하는 것이다. 중요한 점은 함수의 호출이 아니라 함수의 선언에 따라 결정된다는 점이다.
var num = 1;
function a() {
var num = 10;
b();
}
function b() {
console.log(num);
}
a();
b();
위 코드의 결과 아래와 같다.
1
1
이러한 출력의 이유는 함수의 호출로 상위 스코프가 결정된 것이 아니라 함수의 선언에 따라 상위 스코프가 결정되었기 때문이다. 즉, 결과가 전역의 num 값 1을 2번 출력한 것으로 보아 함수 b의 상위 스코프가 전역을 가리키고 있음을 알 수 있다.
다른 예시를 봐보자
function init() {
var name = 'Mozilla'; // name은 init에 의해 생성된 지역 변수이다.
function displayName() {
// displayName() 은 내부 함수이며, 클로저다.
console.log(name); // 부모 함수에서 선언된 변수를 사용한다.
}
displayName();
}
init();
init()은 지역변수 name과 displayName() 함수를 생성한다. displayName() 함수는 init()안에 정의된 내부 함수이며 init() 내부에서만 사용이 가능하다. displayName() 함수는 함수 내부에서 외부 함수에 접근이 가능하므로 name에 접근할 수 있다.
위 코드를 실행하면 displayName() 함수 내의 console.log()문이 부모 함수에서 정의한 변수 name 값을 성공적으로 출력한다. 이 예시를 통해 함수가 중첩된 상황에서 파서가 어떻게 변수를 처리하는지 알 수 있다. 이것이 Lexical scope의 한 예이다.
클로저는 독립적인 변수를 가리키는 함수이다. 또는, 클로저 안에 정의된 함수는 만들어진 환경을 기억한다.
클로저 = 함수 + 함수를 둘러싼 환경(Lexical enviornment)
함수를 둘러싼 환경이라는 것은 렉시컬 스코프이다. 함수를 만들고 그 함수 내부의 코드가 탐색하는 스코프를 함수 생성 당시의 렉시컬 스크프로 고정하면 바로 클로저가 되는 것이다.
function foo() {
var color = 'blue';
function bar() {
console.log(color);
}
bar();
}
foo();
여기서 bar
는 클로저일까???
bar
는 foo
안에 속하기 때문에 foo
스코프를 외부 스코프 참조로 저장한다. 그리고 bar
는 외부함수에 접근이 가능하므로 foo
의 color를 참조한다.
bar
는 foo
안에서 정의되고 실행되었을 뿐, foo
밖으로 나오지 않았기 때문에 클로저라고 부르지 않는다.
이번 코드는 클로저를 나타낸다.
var color = 'red';
function foo() {
var color = 'blue';
function bar() {
console.log(color);
}
return bar;
}
var baz = foo();
baz();// blue
bar
는 color를 출력한다.bar
는 foo
스코프를 외부 스코프 참조로 저장한다.baz
라는 변수에 bar
를 초기화.baz()
를 호출하면 bar
는 color를 찾는다.bar
는 자신의 스코프에 color라는 값이 없으므로 스코프 체인을 통해 외부 스코프에서 color를 찾는다.bar
는 자신이 생성된 렉시컬 스코프(foo)에서 벗어나 전역에서 baz
라는 이름으로 호출 되었다. baz()
를 bar
로 초기화할 때 이미 bar
는 상위 스코프를 foo
로 결정했기 때문에 아무리 전역에서 bar
를 호출하더라도 foo
의 color를 출력한다.
이런 bar
, baz
를 클로저라고 부른다.
자바스크립트의 스코프는 렉시컬 스코프이다. 즉 이름의 범위는 소스코드가 작선된 그 문맥에서 바로 결정된다.
var i;
for (i = 0; i < 10; i++) {
setTimeout(function () {
console.log(i);
}, 100);
}
0~9까지 출력하는 코드같지만 결과는 10만 열번 출력된다.
setTimeout()의 콜백 함수는 모두 0.1초 뒤에 호출될 것이다. 0.1초 동안 이미 반복문은 모두 순회되어 i의 값은 10을 된 상태이고, 그 때 콜백 함수가 호출되면서 10이 되버린 i를 참조한다.
var i;
for (i = 0; i < 10; i++) {
(function(j) {
setTimeout(function() {
console.log(j);
}, 100);
})(i);
}
for (let i = 0; i < 10; i++) {
(function (j) {
setTimeout(function () {
console.log(j);
}, 100);
})(i);
}