말장난으로 시작한 제목과는 달리 역설적으로 JavaScript에선 global space를 더럽히는 일은 죄악처럼 여겨진다. 특히나 통제할 수 없는 다른 사람이 작성한 JavaScript 코드가 섞여들어 갈 가능성이 큰 Client-Side JavaScript에선 더욱 그러하다.
그래서 뜻하지 않는 이런 실수를 저지르지 않으려면 변수가 global 해지는 경우를 정확하게 알고 있어야 하는데, 그 예로 다음과 같은 몇 가지 경우가 있다.

var로 시작되지 않는 변수 선언.

잘 알려진 내용으로 var 없이 선언된 변수는 global object의 properties에 붙게 된다.
참고로, 이렇게 만들어진 property는 var 선언문으로 생성된 property와는 다르게 다시 나중에 delete로 지울 수 있다.

var x = 1;      // A properly declared global variable, nondeletable. 
= 2;          // Creates a deletable property of the global object. 
this.= 3;     // This does the same thing. 
delete x;       // => false: variable not deleted 
delete y;       // => true: variable deleted 
delete this.z;  // => true: variable deleted 

다음은 JavaScript의 delete operator 작동 원리에 대한 자세히 설명해놓은 글. – Perfection Kills – Understanding delete

function 밖에서 선언된 변수.

JavaScript에선 다른 languages와는 다르게 block scope이 아닌, function scope 규칙을 따른다. 또 한 가지 재미있는 사실은 function 안에 있는 변수는 초깃값으로 선언되기 이전이라도 function body 전체 어디서든 인식되고 사용할 수 있게 된다. 이것을 보통 끌어올림(hoisting) 현상이라고 부르는데, function 안 변수 선언문은 function body의 최상위로 끌어올려 지는 것과 같다. 그래서 혹시라도 global 변수와 이름이 같은 변수를 function 안에도 선언해서 허용할 때 다음과 같은 예기치 않은 부작용을 가져올 수 있어서 주의해야 한다.

var scope = "global";
function f() {
  console.log(scope);   // Prints "undefined", not "global" 
  var scope = "local";  // Variable initialized here, but defined everywhere 
  console.log(scope);   // Prints "local" 
}

그래서 변수 선언은 미리 function body 맨 위에다 해주는 습관을 들으면 예기치 않은 혼란을 피할 수 있다.

또, function() constructor를 써서 생성된 function은 항상 최상위 레벨의 global function으로서 자기만의 private scope을 지닌 또 하나의 eval() 버전으로 생각할 수 있는데, 잘 쓰이지는 않지만, 다음과 같은 예에서 이런 현상을 잘 보여준다.

var scope = "global";
function constructFunction() {
  var scope = "local";
  return new Function("return scope"); // Does not capture the local scope! 
}
// This line returns "global" because the function returned by the 
// Function() constructor does not use the local scope. 
constructFunction()(); // => "global" 

function 이름

function 이름은 global 오브젝트의 method로 다음과 같은 scope을 생성할 수 있다.

function aGlobal( param ) { //==window.aGlobal(); 
                            //param is only accessible in this function 
  var scopedToFunction = {
    //can't be accessed outside of this function 
 
    nested : 3 //accessible by: scopedToFunction.nested 
  };
 
  anotherGlobal = {
    //global because there's no `var` 
  };
}

그래서 JavaScript에선 function을 global namespace를 더럽히지 않으려는 하나의 임시 namespace 도구로 많이 사용된다.
한 가지 주의할 것은 일반 ECMAScript 3에선 this가 global 오브젝트를 가리키지만, ECMAScript 5 strict mode에선 undefined를 돌려준다.

with 선언문 안에서 사용된 변수 선언

먼저 with 선언문은 임시로 scope chain을 확장하려고 할 때 사용되는데, JavaScript가 코드를 최적화하기 어렵고 strict mode에선 사용이 금지되어 있으며 다른 코드로 쉽게 대체할 수 있기 때문에 사용할 일은 거의 없겠지만, 혹시라도 다음과 같은 코드를 짜게 된다면 문제가 발생할 수 있다.

with(o) x = 1;

여기서 만약에 o 오브젝트에 x property가 정의되어 있다면 1의 값을 지정하겠지만, 그렇지 않을 때는 with 선언문 없이 그냥 x = 1;과 같아서 x라는 이름의 local 혹은 global 변수의 값을 지정하거나 global 오브젝트의 새로운 property를 생성하게 되므로 주의해야 한다.

변수 선언 중 불필요한 꼼수 부림

가끔가다 한꺼번에 여러 변수에 어떤 하나의 값을 지정하려 할 때 다음과 같은 꼼수가 쓰이는 것을 보게 된다.

var a = b = 1;

하지만, 여기엔 예기치 않은 커다란 부작용이 있는데, 실제로는 결과적으로 다음과 같이 의도하지 않은 global 변수를 선언하게 된다.

= 1;
var a = b;

글자 몇 개 줄이려다 큰 실수를 범하지 말고 다음과 같이 의도한 대로 써줘야 한다.

var a = 1, b = 1;

let keyword

참고적인 얘기로 JavaScript엔 없는 block scope을 만회하려고 Mozilla의 JavaScript extension인 “Javascript 1.7″에선 비표준인 let keyword가 소개되었는데, 이놈은 새로운 block-scope 변수를 선언할 때 다음과 같이 사용될 수 있다.

var a = 4;
 let (= 3) {
   alert(a);  // 3 
 }
alert(a);     // 4 

아직 ECMAScript 표준에는 채택되지 않아서 일반 JavaScript 코딩에는 사용될 수 없다.

흔히 컴퓨터 프로그래밍은 사람이 발명한 것 중에도 가장 복잡한 것 중 하나로 꼽는데, 여기에다 JavaScript는 다른 언어보다도 코딩 자유도가 높고 또 이와 맞물려 예기치 않은 결과를 가져올 수 있는 요소도 곳곳에 숨어 있다. 그래서 예측하기 쉬운 코드를 짜도록 노력한다면 쉽게 눈치챌 수 있는 명확한 에러는 줄일 수 있을 것이다. 이와 관련해서 Douglas Crockford씨가 TXJS 2011에서 한 개막 연설은 귀담아서 들어야 할 내용이 담겨 있다.

관련된 주제의 글

댓글을 남겨 주세요