ABOUT ME

-

Today
-
Yesterday
-
Total
-
  • Effective JavaScript [14] - 기명 함수 표현식의 스코프
    Javascript 2016. 12. 8. 20:01



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


    - Error 객체와 디버거에서 스택 추적을 개선하기 위해 기명 함수 표현식을 사용하라.
    - ES3과 버그가 있는 자바스크립트 실행 환경에서 함수 표현식이 스코프를 Object.prototype으로 오염시킨다는 점을 주의하라.
    - 버그가 있는 자바스크립트 실행 환경에서 기명 함수 표현식의 호이스팅과 중복 할당을 주의하라.
    - 기명 함수 표현식의 사용을 자제하고, 배포하기 전에 제거하라.
    - ES5를 제대로 구현한 실행 환경에 배포한다면, 걱정할 필요가 없다. 



    자바스크립트 함수는 문맥에 따라 의미가 달라진다.


    function double(x) { return x * 2 ; } 


    이 함수는 코드 위치에 따라 함수 선언문이 될수도, 기명 함수(named function)이 될수도 있다. 이 선언문은 함수를 선언하고 현재 스코프의 변수에 이를 바인딩 한다. 예를 들어, 프로그램의 최상위 레벨에서 이 선언문은 전역 변수 double 을 생성할 것이다.


    하지만 동일한 함수 코드가 다음과 같이 완전히 다른 의미를 가지는 표현식으로 사용될 수도 있다.


    var f = function double(x) { return x * 2; };


    ECMAScript 명세에 따르면, 이 예제는 함수를 double이 아니라 변수 f 에 바인딩한다. 물론, 함수 표현식에 이름을 지정할 필요는 없다. 익명 함수 표현식의 형태로 사용할 수도 있다.


    var f = function(x) { return x * 2; };


    기명 함수 표현식은 익명함수 표현식과 달리 그 이름을 함수 내의 지역변수로 바인딩한다는 차이점이 있다. 이 특징을 재귀 함수 표현식을 작성하는데 사용할 수 있다.


    var f = function find(tree, key) {

        if ( !tree) {

            return null;

        }

        if ( tree.key === key) {

            return tree.value;

        }

        return find(tree.left, key) || find(tree.right, key);   

    };


    find는 그 함수 자신의 내부에서만 스코프가 적용된다는 점에 주목해야한다. 함수 선언문과 다르게, 기명 함수 표현식은 내부에서 사용되는 이름을 외부에서 참조할 수 없다.


    find (myTree, "foo");    // 오류: find 가 정의되지 않음


    재귀를 위해 기명 함수 표현식을 사용하는 것은, 다음과 같이 그 함수의 외부 스코프의 이름을 사용할 수도 있기 때문에 유용하지 않을 수도 있다.


    var f = function(tree, key) {

        if (!tree) {

            return null;

        }

        if (tree.key === key) {

            return tree.value;

        }

        return f(tree.left, key) || f(tree.right, key);

    };


    혹은 다음과 같이 단순하게 선언문을 사용해도 된다.


    function find(tree, key) {

        if (!tree) {

            return null;
        }
        if (tree.key === key) {
            return tree.value;
        }

        return find(tree.left, key) || find(tree.right, key);    

    }


    var f = find;



    기명함수는 디버깅을 할 때 정말로 유용하다. 대부분의 최신 자바스크립트 실행 환경은 Error 객체를 위해 스택 추적(stack trace)을 만들고, 함수 표현식의 이름은 보통 스택 추적 내의 엔트리로 사용한다. stack을 검사하는 기능을 가진 디버거들은 보통 기명 함수 표현식을 비슷한 방식으로 사용한다. 


    안타깝게도, 기명 함수 표현식은 자바스크립트 엔진에서의 버그와 ECMAScript 명세의 과거 실수로 인해, 스코프와 호환성 이슈를 낳기로 악명 높다. ES3에 존재했던 명세 실수는 기명 함수 표현식의 스코프를 객체로 표현해야 한다는 것인데, 이로 인해 생성시 문제를 일으킬 소지가 많았다. 이 스코프 객체는 그 함수의 이름을 바인딩 하는 하나의 프로퍼티만을 가지지만, 당연히 Object,prototype의 프로퍼티들을 상속받았다. 이는 함수 표현식에 이름을 짓는 것만으로 Object.prototype의 모든 프로퍼티가 해당 스코프 안으로 들어온다는 뜻이다.


    var constructor = function() { return null; };

    var f = function f() {

        return constructor();

    };


    f(); // {}


    이 프로그램은 null을 반환할 것으로 보이지만 기명함수 표현식이 Object.prototype.constructor (즉, Object 생성자 함수)를 상속하기 때문에 실제로는 새로운 객첼르 만들어 낸다. 그리고 with와 비슷하게 스코프는 Object.prototype 의 동적인 변화에 영향을 받는다. 프로그램 한 부분에서 Object.prototype에 프로퍼티를 추가하거나 삭제하게 되면 함수 표현식 안의 모든 변수가 영향을 받게 된다.


    ES5에서는 이 실수를 수정했다. 몇몇 자바스크립트 실행환경은 잘못된 객체 스코프 방식을 고수하고 있다. 함수 표현식의 스코프를 객체로 오염시키는 문제를 피하는 최선의 방법은 Object.prototype에 새로운 프로퍼티를 추가하지 않고, 지역 변수에 표준 Object.prototype 프로퍼티의 어떠한 이름도 사용하지 않는 것이다. 


    다음은 유명한 자바스크립트 엔진에서 발견되는 버그로, 기명 함수 표현식을 선언문처럼 호이스팅한다.


    var f = function g() { return 17; };

    g();    // 17 (표준을 따르지 않는 실행 환경 


    이는 명백히 표준을 제대로 준수하지 않는 동작이다. 이런 동작을 피하기 위한 합리적인 차선책으로 다음과 같이 함수 표현식과 동일한 이름으로 지역 변수를 만들고 null을 할당하면 된다.


    var f = function g() { return 17; };

    var g = null;


    변수를 var로 재선언하면 함수 표현식을 오류로 호이스팅하는 실행환경에서 g가 바인딩 되는 것을 보장하고, 값을 null로 지정하면 복제된 함수가 가비지 컬렉션의 대상이 되게 한다.


    결국 기명함수 표현식을 사용하기에는 너무 문제가 많다고 결론짓는 것이 당연하다. 또한 기명 함수 표현식은 디버깅할 때만 사용하고, 배포하기 전에 전처리기를 통해 모든 함수 표현식을 디버깅할 땜나 사용하고, 배포하기 전에 전처리기를 통해 모든 함수 표현식을 익명으로 만드는 것이 좋다. 



    - Error 객체와 디버거에서 스택 추적을 개선하기 위해 기명 함수 표현식을 사용하라.
    - ES3과 버그가 있는 자바스크립트 실행 환경에서 함수 표현식이 스코프를 Object.prototype으로 오염시킨다는 점을 주의하라.
    - 버그가 있는 자바스크립트 실행 환경에서 기명 함수 표현식의 호이스팅과 중복 할당을 주의하라.
    - 기명 함수 표현식의 사용을 자제하고, 배포하기 전에 제거하라.
    - ES5를 제대로 구현한 실행 환경에 배포한다면, 걱정할 필요가 없다. 



Designed by Tistory.