자바스크립트의 정적 유효범위(어휘적 스코프)를 이해하고, 이를 응용한 클로저에 대하여 알아봅니다
정적 유효범위란?
자바스크립트에서는 코드가 적힌 순간 변수의 유효범위가 정해집니다. 이것을 정적 유효범위, 또는 멋져보이는 말로 렉시컬(어휘적) 스코프라고 부릅니다.
function main(){
var name = 'chrisjune';
}
var로 선언한 name
변수의 유효범위는 함수를 절대로 벗어날 수 없습니다. 즉 함수안에서 선언한 변수는 함수 밖에서 접근할 수 없습니다.
var name = 'chrisjune';
function print(){
console.log(name);
}function main(){
name = 'choi';
print();
}main();
위 예의 실행 결과는 choi
입니다. main()
메서드가 실행되고, 전역변수인 name
의 값을 choi
로 변경하였기 때문에 print()
메서드에서 전역변수인 name
을 출력하므로 choi
를 출력합니다.
var name = 'chrisjune';
function print(){
console.log(name);
}function main(){
var name = 'choi';
print();
}main();
위의 실행 결과는 chrisjune
입니다. main()
메서드의 name
변수는 전역변수인 name
변수가 아니고 main()
함수안에서 선언되어 함수 밖으로 벗어날 수 없기 때문입니다.
이러한 유효범위는 변수 뿐만아니라 함수에도 동일하게 적용됩니다.
function print(){
console.log('global');
}function main(){
print();
}
main();
위의 예의 경우, main()
메서드에서는 전역함수인 print()
를 실행하므로, global
을 출력합니다.
function print(){
console.log('global');
}function main(){
function print(){
console.log('local');
}
print();
}
main();
위의 예에서는, main()
메서드에서는 지역함수 print()
를 실행하므로, local
을 출력합니다.
자바스크립트 언어는 동적언어지만, 유효범위는 코드가 작성되는 순간 정해지는 정적인 특성을 가집니다. 다시한번 정리하면, 함수안에서 정의된 변수는 함수 밖으로 빠져나갈 수 없다는 것이 핵심입니다.
클로저란?
MDN에서는 함수와 함수가 선언된 언어적(정적) 환경의 조합
이라고 정의하고 있습니다. 정의에서 이해가 바로되지 않기 때문에, 예를 통하여 알아보도록 하겠습니다.😄
for (var i=0; i<100; i++){
setTimeout(function(){
console.log(i);
}, i*100);
}
setTimeout
메소드는 첫번째 인자로 비동기콜백함수, 두번째인자로 실행할 시간(ms)으로 받는 함수입니다. 0.1초마다 0에서 99까지 출력을 하게하려고 작성을 하였습니다. 하지만 결과는 다음과 같습니다.
100이라는 결과가 100번 출력되었습니다. 그 이유는 반복문이 아래와 같이 실행되기 때문입니다.
setTimeout(function(){
console.log(i);
}, 0)setTimeout(function(){
console.log(i);
}, 100)setTimeout(function(){
console.log(i);
}, 200)...setTimeout(function(){
console.log(i);
}, 9900)
setTimeout
비동기 함수가 0.1초 마다 생성되고, 변수 i 를 출력합니다. 실제로 이 비동기 함수가 실행될때는 i가 100이되어 반복문이 종료되었기 때문에, 처음부터 100으로 출력이 됩니다.
좀더 자세하게 설명하자면, 자바스크립트는 단일 스레드언어이기 때문에 콜스택에 작업을 push
하고 처리할때마다 하나씩 pop
합니다. setTimeout
과 같은 비동기작업은, 콜스택이 아닌 이벤트큐에 저장해놓습니다. 그 후 이벤트루프가 콜스택이 비어있는 순간에 이벤트 큐에 쌓여있던 작업을 push
하여 콜백함수를 실행시켜줍니다. 위의 예에서는 콜백함수를 실행할때 i
는 이미 100이 되어있기 때문에 0이아니라 100이 출력됩니다.
해결방법.
for (var i = 0; i < 100; i++) {
function call(j) {
setTimeout(function () {
console.log(j);
}, j * 100);
}
call(i);
}
변수의 정적 유효범위를 활용하여 비동기함수에서 처리할 변수의 값을 함수로 감싸줍니다. 그러면 i 값이 100이 되어도 비동기 함수안에서 사용하는 j값은 고정되어 변하지 않습니다. 마치 함수로 변수를 고정(폐쇄)한다고 하여 이를 클로져(closure)라고 부릅니다.
위의 반복문 결과는 풀어서 아래와 같이 실행됩니다
function call(j) {
setTimeout(function () {
console.log(j);
}, j * 100);
}
call(0);function call(j) {
setTimeout(function () {
console.log(j);
}, j * 100);
}
call(1);...function call(j) {
setTimeout(function () {
console.log(j);
}, j * 100);
}
call(99);
매개변수 또한 지역변수이기 때문에 지역변수가 call함수안에서 고정되어있어 변하지 않습니다.
실행결과는 아래와 같습니다.
많은 경우 반복문과 비동기 함수가 만날 때 클로져 이슈가 자주 발생합니다. 위의 코드는 아래와 같이 즉시실행하는 형식을 더 많이 사용합니다.
for (var i = 0; i < 100; i++) {
(function call(j) {
setTimeout(function () {
console.log(j);
}, j * 100);
})(i);
}
참고자료