티스토리 뷰


* 지난번까지는 너무 속으로 깊이 들어간것 같은 기분이 들어서 다시 표면으로 좀 올라와서 실용적으로 자바스크립트 개발을 할 때 참고할 수 있을만한 내용을 다루고자 한다. 지난번에 예고 했듯이, '변수 선언'의 기본에 대해서 알아보기로 하자. 대략 2~3번에 나눠서 살펴볼 것인데, 우선적으로 1. 글로벌에 대한 내용, 2. 로컬 변수에 대한 내용과 멤버 변수의 접근 방식, 그리고 마지막으로 3. 성능을 고려한 적절한 변수 선언 방법까지 다루어볼 예정이다. 일단 이번에는 첫번째로 글로벌 변수에 대해서 살펴보자.


* 이전글

2012/12/10 - [속깊은 자바스크립트 강좌] 시작 (예고편)

2012/12/17 - [속깊은 자바스크립트 강좌] 자바스크립트의 Scope와 Closure 기초

2013/01/07 - [속깊은 자바스크립트 강좌] function declaration vs function expression 차이점

2013/01/10 - [속깊은 자바스크립트 강좌] 함수를 호출하는 방법과 this의 이해

2013/01/21 - [속깊은 자바스크립트 강좌] Closure의 이해 / 오버로딩 구현하기

2013/01/30 - [속깊은 자바스크립트 강좌] Closure 쉽게 이해하기/실용 예제 소스

2013/02/13 - [속깊은 자바스크립트 강좌] 쉬어가기: 웹 개발 방법론의 변화/자바스크립트의 재발견

2013/02/22 - [속깊은 자바스크립트 강좌] 객체지향의 기본: prototype

* 글로벌 변수의 일반적인 예
: 글로벌 변수는 자바스크립트에서 엄청 많이 이용되고 있다. 이용되고 있는 이유를 사실대로 말한다면 사용안할 이유를 못찾고 있기 때문이기도 하다. 하지만 어쩌면 더 큰 이유는 글로벌 변수가 아닌 로컬 변수로 전환하는 방법을 모르고 있기 때문이기도 하다(이에 대해서는 다다음편 쯤 살펴볼 것이다).
<script>
// 예1
var element = document.getElementById("myDiv");
var insertHtml= "Hello world";
element.innerHTML = insertHtml;
</script>
: 위와 같은 경우가 글로벌 변수의 가장 대표적인 활용 예이다. 조금 다른 예를 든다면 아래와 같은 경우도 있을 것이다.
<script>
//예2
var element = document.getElementById("myDiv");
var button = document.getElementById("myButton");
var insertHtml = "Hello world";
button.onclick = function() {
    element.innerHTML = insertHtml;
};
</script>
: 조금 더 고급스러운 예를 더 살펴보자.
<script>
var element = document.getElementById("myDiv");
var button = document.getElementById("myButton");
var xhr = new XMLHttpRequest();
button.onclick = function() {
    xhr.open("http://unikys.tistory.com/");
    xhr.onreadystatechange = function () {
        if (xhr.readyStatus === 4 && xhr.status === 200) {
            element.innerHTML = xhr.responseText;
        };
    };
    xhr.send();
};
</script>
: 일반적인 사이트에서도 자주 볼만한 자바스크립트의 모양들이다. 그러면 글로벌변수에 대해서 살펴보기 전에 먼저 왜 글로벌 변수의 사용을 왜 자제해야하는지 알아보자.


* 글로벌 변수를 왜 자제해야하나?
: 프로그래밍을 시작하게 되면 가장 많이 듣게 되는 말이 있다. 

"글로벌 변수를 사용하지 마라"

: 자바스크립트도 엄연히 프로그래밍 언어이기 때문에(물론 그렇게까지 생각 안하는 사람도 많지만..) 글로벌 변수가 존재하고, 그 사용 또한 자제하면 좋다. 그런데 위의 예1,2,3을 보면 도대체 무엇이 문제가 되고 왜 자제해야하는지 모르는 경우도 많을 것이고, 다른 C나 JAVA에서도 왜 글로벌을 자제하라고 하는지에 따른 이해도가 없다면 더더욱 그럴 것이다. 하지만 '웹'이라는 특수성 때문에 자바스크립트는 엄연히 글로벌 변수의 사용을 다른 언어들보다 더 자제하고 조심해야한다는 것을 알게 되면 놀랄 것이다. '웹'이 다른 어플리케이션과 다른 점이 있다면 아래와 같은 특징이 있고, 각각은 글로벌 변수를 사용할 때 주의하면 좋은 이유들과 연관이 되기도 한다.

1. 소스와 데이터의 공개성
2. 비동기 로직이 용이하게 구현 가능
3. 모바일/PC 등 좋은 성능에서 안 좋은 성능까지 골고루 퍼져있는 브라우징 환경

: 일단 첫번째로 "소스와 데이터의 공개성"은 웹에서는 '브라우져에서 돌아가는 모든 소스와 데이터는 클라이언트 측에서 돌아간다'는 점에서 해킹이나 보안에 취약할수도 있다. 물론 서버에 미치는 치명적인 손상을 브라우져에서 자바스크립트를 손봐서 입히기는 어렵기는 하지만, 서버의 중요한 데이터라던가 공개하고 싶지 않은 데이터를 자바스크립트로 관리할 때에는 글로벌 변수를 사용하면 안 될 것이다. 특히, 요즘 브라우져에서 '기본적으로' 제공해주고 있는 디버깅툴들로 인해 소스들은 기본이고 자바스크립트 변수들의 값 또한 아주 쉽게 접근이 가능하고 그 값을 가져가는 것이 가능하다. 그렇다면 데이터의 접근을 막는 가장 유용한 방법은 무엇인지 이전 글들을 쭉 되돌아보면 기억이 날지도 모른다.


: 바로 Closure를 사용하는 예에서 중간의 "Closure를 쓰는 실제 예"의 소스에서 #1에 있는 appendDiv 변수는 외부에서 수정도 불가능하고 접근도 불가능하다. 이미 실행이 된 상태에서 해당 변수에 대한 정보는 볼 수 없기 때문에 이러한 방법으로 보안을 신경쓰면 된다. 아래는 해당 소스를 가져온 것이다. #1의 변수는 일반 사람들이 쉽게 브라우져에서 접근하여 내용을 알아내기 힘들다.
(function () {
    var appendDiv = document.getElementById("appendDiv");   // #1
    document.getElementById("wrapper").addEventListener("click", append);
 
    function append(e) {
        var target = e.target || e.srcElement || event.srcElement;
        var callbackFunction = callback[target.getAttribute("data-cb")];
        appendDiv.appendChild(callbackFunction());
    };
    var callback = {
        "1":(function () {
            var div = document.createElement("div");    // #2
            div.innerHTML = "1번";
            return function () {
                return div.cloneNode(true);    // #3
            }
        }()),
        "2":(function () {
            var img = document.createElement("img");
            img.src = "http://www.google.co.kr/images/srpr/logo3w.png";
            return function () {
                return img.cloneNode(true);
            }
        }()),
        "delete":function () {
            appendDiv.innerHTML = "";
            return document.createTextNode("Cleared");
        }
    };
}());
: 다음으로는 "비동기 로직의 구현이 용이"하다는 점이다. 이러한 특징이 글로벌 변수에 미치는 영향은 크지는 않지만, 프로그래밍의 실수를 저지르면 정말 이유도 모르고 예상치 못하는 동작을 하게 될지도 모른다. 위의 예3번을 보면 이러한 상황에서 글로벌로 인하여 문제가 발생할 수 있는 여지를 만들어둔 것이다. 바로 'xhr'이라는 변수를 글로벌로 사용하게 된 것은 재사용성을 높이기 위함일수도 있지만, 이 xhr 변수를 만약에 그대로 다른 비동기 호출에서 사용한다면 사용자가 연속으로 클릭했을 때에는 원래 있었던 xhr의 상태들이 덮어씌워지고 이벤트 콜백 함수들도 덮어씌워져서 예상치 못하는 일들이 생길 것이다. 이러한 일은 AJAX가 활성화 되기 시작하면서 더 조심해야하는 실수로, 예상치 못하는 버그가 단순한 코딩 습관과 실수로 생기기도 한다.

: 그 다음은 "모바일/PC 등 고른 브라우징 환경"이라는 특징이 있다. ('그 다음'이라 하고 '마지막' 특징이라고 말 안한 것은 너무나 많은 특성들이 있는데, 일단 글로벌 변수를 자제해야하는 특징들을 우선적으로 뽑아봤기 때문이다.) 모바일 브라우져와 PC 브라우져의 가장 큰 차이점이라고 한다면 화면의 차이와 성능의 차이일것이다. 여기서 글로벌 변수의 사용은 성능의 차이로 인하여 문제가 될수도 있다. 그 이유는 글로벌 변수는 현재 페이지가 떠있는한 '언제나' 메모리에 떠있기 때문이다. 나름 자바스러운 처리 과정들을 채택한 덕분에 자동 garbage collection을 사용하고 있는 자바스크립트는 해당 변수에 대하여 참조하고 있는 scope나 closure 등이 있으면 해제를 하지 않는다. 하지만 글로벌은 언제나 어떠한 scope에서도 참조되고 있으므로 글로벌로 생성한 변수를 개발자가 수동으로 해제하기 전까지는 언제나 메모리에 남아있게 된다. 따라서 모바일을 고려할 때 모바일 브라우져의 메모리 사용을 최적화 해주기 위하여 '잠시 사용할 변수'는 글로벌 변수 목록에서 빼서 로컬로 돌려서 사용 안할 때에는 해제될 수 있도록 해주는 것이 좋고, 마찬가지로 AJAX의 요청 등으로 오게 될 '대용량 데이터(예: html, table 데이터, 예2의 element, button, insertHTML 등)'는 오래 가지고 있을수록 메모리에 영향을 미치므로 이러한 변수들 역시 로컬로 보관해서 처리하고나면 바로바로 메모리가 해제 될 수 있도록 해주는 것이 좋다.

: 그 외에도 변수를 로직과 연관된 곳에 놔둠으로써 유지보수를 조금 더 편하게 하는 것과 라이브러리를 이용하는 경우 나의 데이터를 보호하고 다른 라이브러리와의 충돌을 막기 위해서도 글로벌 변수의 사용을 자제하면 좋다.

: 사실 위의 특징들로 인한 여파는 미미할지도 모르지만, 이러한 미미한 차이들이 쌓여서 고급 자바스크립트 개발자와 누구나 다 하는 수준의 자바스크립트 개발자가 분류되는 것이므로, 머리에 염두를 두면 좋을 것이다. 


* 글로벌 변수의 정의
: 글로벌 변수는 선언하면 말그대로 어디서든지 접근할 수 있는 변수이다. 이러한 글로벌 변수를 선언하는 방법이 몇가지가 있다.
<script>
var myGlobal = "Hello, Global!";
</script>

: 가장 대표적인 예는 위와 같다. 그냥 <script>안에다가 바로 var로 변수를 선언하고 사용하면 myGlobal 변수는 바로 글로벌 변수가 되어버린다. 아마 가장 많은 웹 개발자들이 자바스크립트 변수를 사용하고 있는 방식일 것이다. 이러한 사용은 사실 복잡하지 않은 웹페이지에서는 상관이 없지만, 조금 복잡한 기능의 웹페이지 개발을 꿈꾼다면 이러한 사용은 조금 자제하는 것도 좋은 방법이다. 이전의 scope에도 잠깐 언급했던 for라던가 if 안에서 변수를 선언하는 것 또한 똑같이 글로벌 변수를 사용하게 되는 것이다.

<script>
for (var i = 0 ; i < 10 ; i++) {
    var isGlobal = true;
}
console.log(i);
console.log(isGlobal);
</script>

: scope 편에서 배웠던 것처럼 for의 안에서는 새로운 scope가 생성되지 않는다. 따라서, for의 안에서 선언한 i와 isGlobal은 전부다 글로벌 변수가 되어 외부에서 접근이 가능하게 된다. 만약 i와 같은 글로벌 변수를 사용하다가 다른 함수를 호출하고 그 안에서 다시 i를 글로벌 변수로서 사용하고 있다면 자바스크립트의 로직은 깨질 것이므로 조심해야한다. 아래에 글로벌 변수 사용의 잘못된 예를 한번 살펴보자. 이 소스의 목적은 1~10까지의 합을 더한 것을 10번 구하고 싶은 것을 간단하게(버그가 있게) 구현해본 것이다.

<script>
function addOneToTen() {
    sum = 0;    // #1
    for (i = 1 ; i < 11 ; i++ ) {    // #2
        sum = sum + i;
    }
    return sum;
}
sum = 0;    //#3
for (i = 0 ; i < 10 ; i++ ) {    //#4
    sum = addOneToTen() + sum;
}
alert(sum);  // === 550?
</script>

: 위의 예는 아주 '지독하게' 글로벌을 사용하고 있는 소스로 위에서 글로벌 사용시 로직이 어긋나는 것을 보여주고 있다. 1~10을 10번 더하고 싶은데 그 결과로는 #4에서 10번을 돌면서 55를 더하니까 550이 되어야할 것 같지만 출력되는 결과는 110가 된다. function 안에 scope가 생성되기 때문에 무엇이 잘못된것인지 단번에 파악하기 힘들지도 모른다. 하지만 중요한 것은 #1, #2의 변수들은 function이라는 새로운 scope 안에 있지만, var로 변수 선언을 하지 않았기 때문에 global 영역에 있는 변수들을 접근하여 조회/설정하게 된다. 쉽게 말하자면 #3과 #4에 정의되고 사용되고 있는 변수들을 function addOneToTen() 함수 안에서 다시 사용하고 있기 때문에, #2에서 글로벌 변수 i를 11까지 더해져서 #4에서 마찬가지로 사용하고 있는 i도 11이 되므로 루프를 한번 밖에 돌지 않는 것이다. 루프를 여러번 돈다고 해도, #3에서 사용하고 있는 글로벌 변수 sum을 #1에서 다시 초기화시키고 55까지 더하여 addOneToTen() 함수의 결과로 55와 sum이 #2에서 더하게 된 55를 더하는 값인 110이 나오게 되는 것이다.



* var를 쓰지 않았을 경우 일어나는 일

: 이렇게 function 안에서 var를 쓰지 않고 변수를 쓰면 흔히 'global 변수로 선언된다'라고 말하는 사람들이 많지만, 정확하게는 틀린 말이다. 위의 #1처럼 sum = 0;을 그냥 쓰게 되면 바로 글로벌 변수가 되기 때문에 이렇게 생각하기 쉬운데, 정확하게 말하자면 글로벌 영역으로 가는 것이 아니라 '현재보다 위의 scope에 sum이라는 변수가 있는지 체크'하는 것이다. 이러한 체크를 글로벌 영역까지 지속적으로 하게 되다가 만약 글로벌에서도 변수가 정의되지 않았으면 글로벌 영역에다가 변수를 하나 선언하게 된다. closure로 예를 들면 다음과 같다.

var getVariable = "global";
(function () {
    var getVariable = "immediate function";
    insideFunction();
    console.log("2. Immediate function: " + getVariable);

    function insideFunction () {
        console.log("1. Inside function: " + getVariable);
        getVariable = "will I be global?";
    };
}());
console.log("3. Global: " + getVariable);

: 출력이 1, 2, 3 순서대로 되는데, insideFunction에서는 getVariable을 var 없이 정의하고 사용하지만, global이 아닌 하나 위의 scope에 있는 getVariable 변수가 쓰여지는 것을 보면, "var 없이 변수를 사용하면 글로벌 변수다" 라는 말이 틀린 말이라는 것을 알 수 있다. 



* window 객체

: window 객체의 존재는 자바스크립트 개발자들에게 정말 꿀과 같은 신세계를 맛보게 해주는 객체이다. 이 객체가 어떤 객체인지는 ECMAScript에서 어떻게 정의하고 있는지 살펴보도록 하자. 아래의 스펙은 "Global Object"에 대해서 정의를 하고 있는 부분이다.


http://www.ecma-international.org/ecma-262/5.1/#sec-15.1


The unique global object is created before control enters any execution context.

Unless otherwise specified, the standard built-in properties of the global object have attributes {[[Writable]]: true, [[Enumerable]]: false, [[Configurable]]: true}.


The global object does not have a [[Construct]] internal property; it is not possible to use the global object as a constructor with the new operator.


The global object does not have a [[Call]] internal property; it is not possible to invoke the global object as a function.


The values of the [[Prototype]] and [[Class]] internal properties of the global object are implementation-dependent.


In addition to the properties defined in this specification the global object may have additional host defined properties. This may include a property whose value is the global object itself; for example, in the HTML document object model the window property of the global object is the global object itself.



: 요약을 하자면, global 객체는 실행 전단계에 생성이 되며, 이 객체에는 속성들을 쓸 수 있으며, constructor가 없기 때문에 new로 생성할수도 없다. 또한 함수로서 호출할 수 없으며, prototype도 없고, "글로벌 객체는 자기 자신을 정의하는 속성을 가지고 있으며, HTML DOM에서는 'window' 속성이 global 객체 그 자신이다". 즉, window 객체는 global 객체라는 것이다. 따라서, 아래처럼 window 객체는 global 객체이면서 global 객체는 window를 가지고 있으므로, 재귀적인 모양이 되는 것이다. 아래는 크롬의 디버그 툴에서 그것을 확인해본 것이다. window === window.window 이며, window === window.window.window 이기도 하다.



: window객체는 global 객체이기도 하므로, 글로벌 변수로 선언된 모든 변수들을 window 객체가 속성으로 가지게 된다.

<script>
var myGlobal = "am i in window?";
alert(myGlobal);
alert(window.myGlobal + " Yes you are!");
</script>

: 이러한 것을 보게 되면 '이게 뭐?' 라는 생각을 하게 될지도 모르지만, 객체의 속성 접근 방법을 스트링을 인덱스로 하는 배열 접근 방식도 가능하다는 것을 떠올리면 이러한 window 객체의 활용은 그야말로 무궁무진하게 되는 것이다. 

<script>
var myGlobal = "am i in window?";
var myVariableName = "myGlobal";
alert(myGlobal);
alert(window.myGlobal + " Yes you are!");
alert(window["myGlobal"] + " Yes you are!");
alert(window[myVariableName] + " Yes you are!");
</script>

: 여전히 '이게 뭐?' 라고 생각한다면, 흔히들 eval을 이용해서 많이 구현하던, 가변적으로 button들을 설정하기 등과 같은 것을 떠올려보자.

var button1 = document.getElementById("button1");
button1.dosomething = "버튼별로 무언가를 한다. 근데 자꾸 수가 늘어나므로 노가다하기는 싫다ㅠ";
var button2 = document.getElementById("button2");
var button3 = document.getElementById("button3");
var button4 = document.getElementById("button4");

: 초창기 시절 이러한 질문을 커뮤니티에 올리면 나오는 대답이 'eval을 쓰세요' 였다.

for (var i = 0 ; i < 4 ; i++) {
    eval("var button" + i + " = document.getElementById('button" + i + "');";
    eval("button" + i + ".dosomething = '이렇게 해도 되지만, eval을 사용했다ㅠ'";
}


하지만 이미 이전에 썼던 글 중에서 'eval is evil'이라는 것을 이전의 글 중에서 썼던 것을 기억한다면 eval을 사용하지 않는 것을 습관으로 들이면 좋을 것이라고 했고, 아직도 이렇게 조언하는 사람이 있다면 그 사람은 그렇게 고급 자바스크립트 개발자는 아니라는 것을 바로 눈치챌 수 있다. 이렇게 eval 없이는 안될 것 같은 것도 window의 이러한 스트링 인덱스를 통한 배열 접근을 한다면 해결이 가능하다.

for (var i = 0 ; i < 4 ; i++) {
    window["button" + i] = document.getElementById("button" + i);
    window["button" + i].dosomething = "eval 따위 없어도 돼!";
}

: 사실 이것은 window에 국한 된 것이 아니라 모든 자바스크립트의 객체에 대하여 가능하므로 참고를 하면 좋을 것이다. 이외에도 글로벌 영역에 선언한 함수들도 window객체의 속성으로 접근가능하다.

function myFunction () {
    alert("I'm invoked!");
}

window["myFunction"]();
window["myFunction"].call();

: 이러한 함수명이 없는 함수 리터럴이 아닌 일반 함수로 선언된 함수들도 이러한 식으로 쉽게 접근이 가능하고 변수로 다시 저장이 가능하다는 점은 요긴하게 쓰일 때가 많을 것이다. 글로벌 변수로 선언된 것은 window 객체의 속성으로 들어가기도 하고, 반대로 window 객체의 속성으로 선언된 변수/함수들을 글로벌 변수/함수가 되기도 하므로 양방향으로 필요할 때 요긴하게 사용하면 좋을 것이다.


: 자바스크립트에서 일반적으로 사용하는 변수들, 값들, 심지어 undefined나 NaN까지 window 객체의 속성들로 기본적으로 들어가있는 것을 보면 재미있을 것이고, 자바스크립트의 구조가 조금 더 이해가 될 것이다.




* 전역 변수 선언 방법

: 위의 몇가지 전역 변수를 선언하는 방법들 살펴봤는데 다음과 같이 볼 수 있다.

    • 글로벌 scope에서 var global = "global";
    • window객체가 재선언되지 않은 scope에서 window.global = "global";
    • window객체가 재선언되지 않은 scope에서 window["global"] = "global";
    • 해당 변수명으로 변수가 선언되지 않은 scope에서 선언없이 var 빼고 바로 global = "global"; 사용

: 여기서 붙은 조건들이 약간 독특하다고 생각할지도 모른다. 첫번째는 그냥 <script>를 쓰자마자 쓰면 글로벌이 되는 것이고, 네번째 항목도 위에서 살펴본바와 같이 안쪽의 function 에서 global이라는 변수가 상위 scope에 존재하지 않을 때라는 것을 살펴봤다. 그럼 두번째와 세번째의 window객체가 재선언되지 않은 scope이라는 것은 무슨 말인지 잠깐 살펴보고 넘어가자.

(function myLibrary (window, document, undefined) {
    // 생략
}(window, document));

: 위의 자바스크립트 소스는 자바스크립트 라이브러리를 만들 때 여러 가지 이유로 아주 기본적으로 사용되는 함수 형식 중 하나이다. 여기서 주목할 것이 바로 myLibrary라는 함수의 인자로 'window'와 'document'와 'undefined'를 다시 선언하고 있다는 점이다. 라이브러리를 만들 때 이러한 형식을 사용하는 하나의 이유는, 다른 라이브러리에서 만약 var window = {}; 등과 같이 window라는 객체를 새로 선언한다면 라이브러리안에서 전역변수로 사용하려고 했던 window 변수가 바뀌어서 예상치 못하는 결과가 일어날 수 있기 때문이다. 그리고 글로벌 scope가 아닌 경우에는 window라는 객체를 새롭게 정의하는 것 또한 가능하기 때문이다. 예를 들면, 아래와 같은 코드가 있을 수 있다.

function myLibrary() {
    var window = {    //#1
        popup: function () {
            window.open("http://unikys.tistory.com");    //#2
        },
        alert: function () {
            alert("I'm not the true alert!!");    //#3
        },
        open: function (url) {    //#4
            alert("I know where you are going.. " + url);
        }
    };
    window.alert();
    window.popup();
};

: 이 코드에서 보면 흔히 많이 쓰는 window.open() 함수를 사용하고 있다. 아마 많은 사람들은 그냥 open()이 아니라 윈도우 팝업을 새로 여는 기분으로 window.open()이라고 쓰고 있을 것이다. 하지만 여기서 이 window는 새로운 window를 연다는 window.open()이 아니라, 글로벌 영역에 있는 open 함수를 호출하는 것이다. 그리고 #1에 보면 이 window 객체를 재정의하고 있는데, 바로 위의 scope에 window라는 객체가 새로 생겨버려서 #2에서는 원래 의도하던 팝업을 띄우는 함수가 아닌 #4의 새로 정의된 window 객체의 open 함수를 호출하게 된다. 이것은 정말 의도치 않게 변경될수도 있고, 누군가는 악의적으로 이렇게 사용할수도 있으니 글로벌 변수의 사용은 최대한 조심하는 것이 좋다. 라이브러리에서 사용하고 있는 변수들도 글로벌 영역의 변수들을 사용하지 말라는 것은, 글로벌 변수를 사용하면 이러한 방법으로 어떠한 데이터가 오고가는지 쉽게 낚아 채는 것이 가능하기 때문이다.



* 전역 변수 선언 방법의 차이

: 그럼 각 방법들이 어떠한 차이를 가지는지 살펴보면 가장 대표적으로 다른 것은 파싱을 할 때 변수가 선언되는가, 아니면 실행할 때 변수가 선언되는가하는 차이이다. 이것이 무엇이 다른지는 아래의 예를 보면 알 수 있다.


//#1 var로 정의한 경우
console.log("1: " + varExists + " , " + window.hasOwnProperty("varExists"));  
var varExists = "var를 썼습니다";
console.log("2: " + varExists);  

: 이러한 경우 결과는 아래와 같이 1에서는 undefined, 2에서는 제대로된 값이 나오는데, 1에서 undefined라고는 나오지만 var varExists;라고 정의하는 행을 지나기 전에도 변수 자체는 이미 존재하여 undefined라는 값을 가지고 있다는 것이다.



: 다음은 var없이 바로 변수를 사용했을 때의 경우이다.

//#2 var 없이 사용한 경우
console.log("Exists?: " + window.hasOwnProperty("noVar"));
console.log("1: " + noVar);  
noVar = "var를 안 썼습니다";
console.log("2: " + noVar);  




: 이렇게 변수를 var 없이 바로 사용하게 된다면, noVar = "...";를 실제로 실행하기 전에는 window의 속성으로 정의되어있지 않고, noVar구문이 실행되기 전에 noVar에 접근하려고 하니 레퍼런스 에러가 일어났다. 이것은 var 구문으로 정의된 것은 처음에 파싱될 때 미리 해당 변수에 대한 선언을 먼저 정의해놓는 다는 것을 알 수 있고, var를 이용하지 않는 경우, window의 속성으로 설정하는 경우에는 실행 당시(runtime)에 그 속성을 새롭게 추가하고 설정하는 것이라고 볼 수 있다. 이에 대해서 다시 표준 정의를 찾아보자. 먼저 Variable에 대해서 설명하고 있는 부분을 보자.


http://www.ecma-international.org/ecma-262/5.1/#sec-12.2 

 

A variable statement declares variables that are created as defined in 10.5. Variables are initialised to undefined when created. A variable with anInitialiser is assigned the value of its AssignmentExpression when theVariableStatement is executed, not when the variable is created.

 

: 변수에 대해서 정의하고 있는 중간에 보면, 10.5에서 정의하듯이 초기화 단계는 해당 var 구문이 실행될 때 초기화가 일어나고, 변수가 생성될 때에는 일어나지 않는다고 하고 있다. 그렇다면 10.5의 내용을 더 살펴보자. 

 

 http://www.ecma-international.org/ecma-262/5.1/#sec-10.5

    On entering an execution context, bindings are created in the VariableEnvironment as follows using the caller provided code and, if it is function code, argumentList args:


    : 먼저 위의 설명 중에서 실행단계에 들어갈 때, 먼저 VariableEnvironment가 아래와 같이 생성된다고 되어있다. 이것이 어떠한 단계로 구성되어있는지 살펴보면 아래와 같다. 여기서 다른 내용들은 처음에 실행되기 전에 여러 함수/변수들을 초기화하는 단계이므로 한번 쭉 읽어봐도 재미있을 것이다. 하지만 변수와 관련된 것만 뽑자면, 마지막 8번 항목만 주의깊게 보면 될 것이다.

    1. Let env be the environment record component of the running execution context’s VariableEnvironment.
    2. If code is eval code, then let configurableBindings be true else letconfigurableBindings be false.
    3. If code is strict mode code, then let strict be true else let strict befalse.
    4. If code is function code, then
      1. Let func be the function whose [[Call]] internal method initiated execution of code. Let names be the value of func’s [[FormalParameters]] internal property.
      2. Let argCount be the number of elements in args.
      3. Let n be the number 0.
      4. For each String argName in names, in list order do
        1. Let n be the current value of n plus 1.
        2. If n is greater than argCount, let v be undefined otherwise let v be the value of the n’th element of args.
        3. Let argAlreadyDeclared be the result of calling env’sHasBinding concrete method passing argName as the argument.
        4. If argAlreadyDeclared is false, call env’sCreateMutableBinding concrete method passing argNameas the argument.
        5. Call env’s SetMutableBinding concrete method passingargNamev, and strict as the arguments.
    5. For each FunctionDeclaration f in code, in source text order do
      1. Let fn be the Identifier in FunctionDeclaration f.
      2. Let fo be the result of instantiating FunctionDeclaration f as described in Clause 13.
      3. Let funcAlreadyDeclared be the result of calling env’s HasBinding concrete method passing fn as the argument.
      4. If funcAlreadyDeclared is false, call env’s CreateMutableBinding concrete method passing fn and configurableBindings as the arguments.
      5. Else if env is the environment record component of the global environment then
        1. Let go be the global object.
        2. Let existingProp be the resulting of calling the [[GetProperty]] internal method of go with argument fn.
        3. If existingProp .[[Configurable]] is true, then
          1. Call the [[DefineOwnProperty]] internal method ofgo, passing fnProperty Descriptor {[[Value]]:undefined, [[Writable]]: true, [[Enumerable]]: true , [[Configurable]]: configurableBindings }, and true as arguments.
        4. Else if IsAccessorDescriptor(existingProp) or existingPropdoes not have attribute values {[[Writable]]: true, [[Enumerable]]: true}, then
          1. Throw a TypeError exception.
      6. Call env’s SetMutableBinding concrete method passing fnfo, andstrict as the arguments.
    6. Let argumentsAlreadyDeclared be the result of calling env’sHasBinding concrete method passing "arguments" as the argument.
    7. If code is function code and argumentsAlreadyDeclared is false, then
      1. Let argsObj be the result of calling the abstract operation CreateArgumentsObject (10.6) passing func, names, args, env andstrict as arguments.
      2. If strict is true, then
        1. Call env’s CreateImmutableBinding concrete method passing the String "arguments" as the argument.
        2. Call env’s InitializeImmutableBinding concrete method passing "arguments" and argsObj as arguments.
      3. Else,
        1. Call env’s CreateMutableBinding concrete method passing the String "arguments" as the argument.
        2. Call env’s SetMutableBinding concrete method passing "arguments", argsObj, and false as arguments.
    8. For each VariableDeclaration and VariableDeclarationNoIn d in code, in source text order do
      1. Let dn be the Identifier in d.
      2. Let varAlreadyDeclared be the result of calling env’s HasBinding concrete method passing dn as the argument.
      3. If varAlreadyDeclared is false, then
        1. Call env’s CreateMutableBinding concrete method passingdn and configurableBindings as the arguments.
        2. Call env’s SetMutableBinding concrete method passing dn,undefined, and strict as the arguments.


    : 여기서 8번을 보면, 모든 변수 선언된 것은 현재 env(현재 실행을 시작한 환경, 1번 항목 참고)에 대해서 해당 변수가 이미 정의되었는지 체크하고 정의가 안 되어있으면, 현재 환경에 var 구문으로 정의된 변수를 undefined로 정의하는 것을 확인할 수 있다. var로 변수를 정의하게 되면 이렇게 해당 환경에 진입하게 되면 모든 변수들을 찾아서 미리 정의를 하는 것이다. 따라서, 여기서 한가지를 알 수 있는 것이, "var로 정의하는 변수의 위치는 의미가 없고, 변수 초기화의 위치만 의미가 있다." 라는 것이다. 위와 같은 사항 때문에 코드를 짜다가 중간에 var로 변수를 정의하고 넣는 것이 성능상 아무런 영향이 없다는 것이다. 예를 들면 다음과 같다.

    function optimizedFunc(flag) {
        if (flag) {
            var lotsOfVariables1, lotsOfVariables2, lotsOfVariables3;    // #1
            // 생략
            console.log("1: " + lessVariables);
        } else {
            var lessVariables;    // #2
            // 생략
            console.log("2: " + lotsOfVariables1);
        }
    }
    optimizedFunc(true);
    optimizedFunc(false);
    

    : 위의 함수에서 보면 #1의 분기에서 엄청 많은 변수를 사용하고, #2에서는 변수를 조금 사용하기 때문에 메모리나 성능상, if 안에다가 각 분기별로 정의를 하면 메모리가 절약될 것 같은 느낌이다. 하지만 위에서 '실행 환경'이라하면 새로운 scope가 생성되는 함수로 들어갈 때에만 해당되고, if 구문으로는 새로운 환경이 생성되지 않으므로, optimizedFunc안에 들어갔을 때에는 이미 #1의 많은 변수들과 #2의 변수들 또한 먼저 전부다 정의가 되어버리기 때문에 이렇게 굳이 var를 분기별로 구분해서 사용할 필요가 없다. 아래의 실행 결과를 봐도, 위에서 처럼 정의가 안 되었을 때, 레퍼런스에러가 나는 것이 아니라 'undefined'로 표시되는 것은 어찌되었든 변수를 생성하였다는 것이다.



    : 이러한 특성 때문에 코드 검정툴인 JSLint에서 제안하고 있는 코딩 방식이 있는데, 이것은 로컬변수에 해당하는 내용이므로 다음에 로컬변수에 대해서 살펴볼 때 더 자세하게 알아보도록 하자.


    * 정리

      • 글로벌 변수의 사용은 프로그래머라면 어찌되었든 최소화하자.
      • var 선언 없이 사용하는 것은 글로벌 변수의 사용이 '될 수도' 있으므로 항상 조심하자.
      • var로 정의를 하면 현재 환경을 실행하기 전에 먼저 변수를 정의해 놓는다.


    글로벌 변수에 대한 내용은 일단 여기서 끊고 실제로 대부분의 변수로 사용하는 정말 실용적으로 사용할 수 있을 로컬변수에 대한 내용들을 살펴보도록 하자. 그리고 로컬 변수에 대해서 살펴본 다음에, 자바스크립트의 특성상 어쩔 수 없이 글로벌변수를 사용할 수 밖에 없는 상황도 많은데, 이러한 것을 최소화하는 방법들을 더 자세하게 알아보자.


    [속깊은 자바스크립트 강좌] 글로벌(전역) 변수와 window 객체 끝.



    - 다음 편

    2013/11/06 - [속깊은 자바스크립트 강좌] 변수 선언 방법에 대하여

    2016/11/13 - [속깊은 자바스크립트 강좌] 마무리(는 책으로!)



    공지사항
    최근에 올라온 글
    최근에 달린 댓글
    Total
    Today
    Yesterday
    «   2024/03   »
    1 2
    3 4 5 6 7 8 9
    10 11 12 13 14 15 16
    17 18 19 20 21 22 23
    24 25 26 27 28 29 30
    31
    글 보관함