반응형

JavaScript (Scope)


JS는 기본적으로 함수 레벨 스코프를 지원해왔고, 얼마 전까지만 해도 블록 레벨 스코프는 지원하지 않았습니다. 하지만 가장 최신 명세인 ES6(ECMAScript 6)부터 블록 레벨 스코프를 지원하기 시작했습니다.


-> 함수 레벨 스코프

     자바스크립트에서 var로 선언된 변수나, 함수 선언식으로 만들어진 함수는 함수 레벨 스코프를 가집니

     다. 즉, 함수 내부 전체에서 유효하다는 것이죠


1
2
3
4
5
6
7
   function foo() {
           if(true) {
               var color='red';
           }
           console.log(color);        // red
   }
   foo();
cs

   여기서 color가 블록 레벨 스코프라면 if문이 끝날때 사라지고 console.log에서 참조할때 에러가 날것입니다. 하지만 var는 함수 레벨 스코프를 가지고 있기 때문에 foo함수 내부 어디에서도 에러 없이 참조가능한 것이죠.


-> 블록 레벨 스코프

    let 과 const 키워드는 블록 레벨 스코프를 가집니다. 


1
2
3
4
5
6
7
8
   function foo() {
           if(true) {
               let color = 'red';
               console.log(color);        // red
           }
           console.log(color);        // ReferenceError
   }
   foo();
cs


   let color를 if 블록 내부에서 선언하였기 때문에 if문이 끝남과 동시에 잘못된 참조로 에러가 발생하는 것입니다.


그렇다면 let, const, var는 언제 써야 하는 것인가?

요즘은 대부분 var는 사용하지 않습니다. 물론 상황에 맞게 사용하는 것이 맞지만 var는 let과 const로 모두 대체가 가능하고

var 자체가 함수 레벨의 스코프를 가지기 때문에 블록 레벨 스코프보다 더 많은 에러와 혼란은 가져올 수 있습니다.


JavaScript (Closure)


사실 자바스크립트를 공부하면서 많이 어려움을 느낀 부분입니다. 클로저의 정의는 무엇인지도 모르겠고 영어사전에는 닫는것, 종료의 행위 라고 나옵니다.

먼저 정리하면 생성될 당시를 기억하는 것이라고 생각하시면 됩니다. 이는 스코프 체인을 통해 접근할 수 있는 변수 등의 스코프가 해제되었음에도 불구하고 접근할 수 있는 것을 말하는데 아래에서 자세히 살펴보도록 하겠습니다.

자바스크립트에서 클로저는 내부함수가 외부함수의 context에 접근할 수 있는 것을 말합니다. 다음 예시들을 통해 조금 더 자세히 살펴봅시다.


1
2
3
4
5
6
7
8
    function foo() {
        var color = 'red';
        function bar() {
            console.log(color);
        }
        bar();
    }
    foo();
cs

bar 함수는 우리가 부르는 클로저인가요? 일단 bar는 foo안에 속하기 때문에 foo 스코프를 외부 스코프 참조로 가집니다. 그리고 bar는 foo의 color를 참조할 것이죠. 그럼 이 bar 함수는 클로저 일까요? 아닙니다! bar는 foo안에서 정의되고 실행되었을 뿐, foo밖으로 나오지 않았기 때문에 클로저가 아니죠!


그럼 다음 예시를 한번 더 살펴보도록 하죠.


1
2
3
4
5
6
7
8
9
10
    var color = 'red';
    function foo() {
        var color = 'white';
        function bar() {
            console.log(color);
        }
        return bar;
    }
    var a = foo();
    a();
cs


먼저 말씀드리면 이코드는 클로저를 나타내고 있습니다. 

bar는 color를 찾아 출력하는 함수로 정의되었습니다. 또한 bar는 외부함수를 참조로 white를 가리키게 되겠죠. 하지만 foo() 수행이 끝나면 GC가 인스턴스를 회수하지 않나요? 라는 궁금증이 생길 수 있지만 bar()는 a가 여전히 참조하고 있기 때문에 bar에서 참조하는 color는 'white'가 되며 결과값으로 white가 출력됩니다. 정리하면 JavaScript에서 스코프는 소스코드가 작성된 그 문맥에서 결정이 됩니다. 때문에 위 함수에서도 bar()는 계속 참조되고 있기 때문에 GC가 회수하지 않고요.

그래서 white가 출력되는 것입니다.


다른 예시를 통해 다시 한번 살펴보죠.


1
2
3
4
5
6
7
8
9
    function count() {
        var i;
        for(i=1; i<10; i+=1) {
            setTimeout(function timer() {
                console.log(i);
            }, i*100);
        }
    }
    count();
cs


이 코드는 1~9까지를 0.1초마다 출력하는 것이 목표였다고 가정합시다. 하지만 결과값으로는 10이 9번 출력되었습니다. timer는 클로저로 언제 어디서 호출되던지 항상 상위 스코프인 count의 i를 참조하겠죠. 그리고 timer는 0.1초 후 호출됩니다. 그런데 첫 0.1초가 지날동안 이미 i가 10이 되어버린 것입니다. timer는 0.1초 주기로 호출될 때마다 항상 count에서 i를 참조하고 결국 10이 되어버린 i만 출력하는 것입니다.

이 문제를 해결하기 위해서는 어떻게 해야 할까요?


1) 새로운 스코프를 추가하여 반복마다 그곳에 각각 따로 값을 저장하는 방법

2) 블록 스코프를 이용하는 방법


1
2
3
4
5
6
7
8
9
10
11
12
    //1번 방법입니다.
    function count() {
        var i;
        for(i=1; i<10; i+=1) {
            (function(cntNum) {
                setTimeout(function timer() {
                    console.log(cntNum);
                }, i*100);
            })(i);
        }
    }
    count();
cs


1
2
3
4
5
6
7
8
9
//    2번 방법입니다.
    function count() {
        for(let i=1; i<10; i+=1) {
            setTimeout(function timer() {
                console.log(i);
            }, i*100);
        }
    }
    count();
cs


지금까지 자바스크립트의 Scope와 Clouser에 대해 알아보았습니다. 클로저라는 개념자체가 굉장히 머리아프고 까다롭게 느껴지지만 이 글이 도움이 되었으면 합니다. 혹시라도 더 깊게 알고 싶으신 분들은 자바스크립트 렉시컬 스코프에 대해 학습하시면 도움이 되실 것입니다.

반응형

'JavaScript' 카테고리의 다른 글

자바스크립트 화살표 함수  (0) 2019.02.26
[JavaScript] JS의 장점?? JS의 좋은 문법  (0) 2018.09.20

+ Recent posts