전역 변수/지역 변수

C/C++ 프로그램에 익숙한 분들은 변수를 크게 전역 변수/지역 변수로 구분하는데 익숙하실 겁니다. 하지만 클로저를 지원하는 JavaScript에서는 함수 안에 함수를 중첩해서 많이 선언하기 때문에 지역 변수의 스펙트럼이 다양해집니다.

예를 들어, JavaScript에서는 전역 네임스페이스를 오염시키지 않기 위해 최상단에 익명 함수를 하나 정의하고 곧바로 호출하는 용법을 많이 사용합니다.

(function () {
    var x = 1;

    function foo() {
        return x;
    }

    x++;

    function bar() {
        return x;
    }

    console.log(foo());
    // 2
    console.log(bar());
    // 2
})();

여기서 변수 x는 엄밀한 의미에서 익명 함수의 지역 변수이지만, foo()bar() 함수 입장에서 봤을 때는 두 함수가 서로 공유하는 전역 변수가 됩니다. foo() 함수에서 x가 1일때 값을 캡춰했지만, x++이 실행되었기 때문에 foo() 함수는 2를 리턴합니다.

이번에는 foo() 함수를 다음과 같이 확장해 봅시다. 여기서 변수 yfoo() 함수의 지역 변수이지만, foo1(), foo2() 함수의 전역 변수가 됩니다.

    function foo() {
        var y = 2;

        function foo1() {
            return y;
        }

        y++;

        function foo2() {
            return y;
        }

        console.log(foo1());
        // 3
        console.log(foo2());
        // 3

        return x;
    }

즉, 클로저가 있는 언어에서는 전역 변수와 지역 변수의 구분이 모호해지는 것을 알 수 있습니다. 앞서 왜 변수가 나쁜가?라는 글에서 제기한 문제도 여기에 있습니다. 클로저가 캡춰한 변수 i는 루프 내의 모든 익명 함수가 공유하는 전역 변수가 되기 때문입니다.

function makeAdders(n) {
    var adders = [];
    for (var i = 0; i < n; i++) {
        adders.push(function (x) { return i + x; });
    }
    return adders;
}
 
var adders = makeAdders(10);
var add5 = adders[5];
var r = add5(2);
console.log(r);
// We expect 7, but 12 is printed.

참고로 프로그래밍 언어가 클로저가 캡춰하는 변수를 복사할 것인지, 공유할 것인지는 정답이 없습니다. 어느 쪽을 선택하든 임의의 선택이 되는 거죠. 실제로 프로그래밍 언어마다 방식이 다릅니다. 문제는 변수라는 개념이 클로저와 결합하여 프로그래밍 언어 자체를 복잡하게 만드는 요소가 되고, 개발자 입장에서는 프로그래밍 언어마다 다른 임의의 규칙을 기억하고 써야 하기 때문에 프로그래밍의 복잡도를 더 하는 요인이 됩니다.

Advertisements