ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Effective JavaScript [13] - 즉시 실행함수 스코프
    Javascript 2016. 12. 7. 21:28



    본 게시물은 Effective Javascript의 내용을 재구성하여 작성되었음을 알립니다. 저작권 문제 발생시 게시물이 비공개 될 수 있습니다. 


    - 바인딩과 할당의 차이점을 이해하라.

    - 클로저는 외부 변수의 값이 아닌 참조를 저장한다.
    - 지역 스코프를 만들기 위해 즉시 실행 함수 표현식을 사용하라.

    - IIFE에서 블록으로 감쌌을 때 변화하는 상황에 주의하라. 




    (버그가 있는!) 프로그램은 어떤 계산을 할까?


    function wrapElements(a) {

        var result = [ ], i, n;

        for ( i = 0, n = a.length; i < n; i++ ) {

            result[i] = function() { return a[i]; };

        }

        return result;

    }


    var wrapped = wrapElements([10, 20, 30, 40, 50]);

    var f = wrapped[0];

    f();    // ?


    프로그래머는 10이라는 값을 계산할 의도로 코드를 작성했겠지만, 실제로는 undefined 값이 만들어진다. 이 코드를 제대로 동작하도록 하기 위해 바인딩과 할당의 차이점을 이해해야 한다. 


    런타임시 스코프에 진입하면 해당 스코프에 있는 변수들을 바인딩하기 위해 메모리에 '슬롯'을 할당한다. wrapElements 함수는 세 지역 변수 result, i, n을 바인딩한다. 따라서 이 함수가 호출되면 wrapElements 함수는 이 세 변수들을 위한 슬롯을 할당한다. 반복문을 순회할 때마다 반복문의 본문을 감싸는 함수를 위한 클로저를 할당한다. 


    이 프로그램의 버그는 감싸진 함수가 생성되는 시점에 그 함수가 i의 값을 저장하고 있다고 기대하기 때문에 발생한다. 하지만 사실은 i로의 참조를 포함할 뿐이다. i의 값이 매번 함수가 생성되고 난 뒤 변하기 때문에, 내부의 함수는 결국 i의 마지막 값을 바라보게 된다. 이게 바로 클로저의 키 포인트다. 클로저는 외부 변수의 값이 아니라 참조를 저장한다. 


    따라서 wrapElements에 의해 생성된 모든 클로저들은 반복문 이전에 i 를 위해 생성된 하나의 공유 슬롯을 참조한다. 반복문을 순회할 때마다 i의 값은 배열의 마지막에 도달할 때까지 증가하고, 클로저 i를 실제로 호출할 때는 배열의 다섯 번째 인덱스를 찾게 되어 undefined를 리턴한다. 


    var 선언을 for 반복문의 머리 부분에 두더라도 wrapElements 는 완전히 동일하게 동작한다.


    function wrapElements(a) {

        var result = [ ], i, n;
        for ( var i = 0, n = a.length; i < n; i++ ) {
            result[i] = function() { return a[i]; };
        }
        return result;
    }

    var wrapped = wrapElements([10, 20, 30, 40, 50]);
    var f = wrapped[0];

    f();    // ?



    이 버전은 var 선언문이 반복문의 안쪽에서 나타나기 때문에 조금 더 햇갈릴 수 있다. 하지만, 변수 선언은 반복문의 맨 우시부분으로 호이스팅된다. 따라서 마찬가지로 변수 i를 취한 하나의 슬롯만 할당된다. 아래와 같이 감싸진 함수를 만들어 강제로 지역 스코프를 만들고 즉시 실행하는 방법으로 이 문제를 해결할 수 있다.


    function wrapElements(a) {

        var result = [ ];

        for ( var i = 0, n = a.length; i < n; i++ )

            ( function () {

                var j = i;

                result[i] = function() { return a[j]; } ;

            }) ();

        }

        return result;

    }


    이 방법은, 즉시 실행 함수 표현식 또는 IIFE(immediately invoked function expression) 이라고 부르며, 자바스크립트의 블록 스코프 지원을 위한 필수적인 차선책이다. 대안으로 사용할 수 있는 변형으로는 다음과 같이 지역 변수를 IIFE의 파라미터로 바인딩하고 그 값을 인자로 전달하는 방법이 있다.


    function wrapElements(a) {

        var result = [ ];
        for ( var i = 0, n = a.length; i < n; i++ )
            ( function (j) {
                result[i] = function() { return a[j]; } ;
            }) (i);
        }
        return result;

    }


    하지만, 지역 스코프 생성을 위해 IIFE를 사용할 때는 함수 안에 블록으로 감싸는것이 블록에 어떤 이상한 변화를 만들기 때문에 조심해야 한다. 첫째로 블록 안에서는 break, continue 명령어를 사용할 수 없다. 왜냐하면 함수 밖에서 break나 continue 명령어를 사용할수 없기때문. 두번째로 블록에서 this나 특별한 arguments 변수를 참조하면, IIFE는 이를 다르게 해석한다. 



    - 바인딩과 할당의 차이점을 이해하라.

    - 클로저는 외부 변수의 값이 아닌 참조를 저장한다.

    - 지역 스코프를 만들기 위해 즉시 실행 함수 표현식을 사용하라.

    - IIFE에서 블록으로 감쌌을 때 변화하는 상황에 주의하라. 




Designed by Tistory.