Javascript에서 Scope (1) - 기본
Javascript가 가장 어렵다고 느껴질때가 Scope가 헷갈릴 때가 아닐까 생각이 듭니다.
웬만큼 프로그램좀 짜봤다 싶은 사람도 헷갈리기 십상인게 Javascript Scope 입니다.
시간을 내어서 간단히 정리해보았습니다. 말로 정리하는 것은 정리하는 것도 어렵고, 나중에 보기도 힘들어서 예제를 중심으로 정리 하였습니다. 다른 분들도 도움이 되시기 바랍니다.
#0. 테스트를 위한 함수 추가Object.prototype.print =
function (comment) {
comment = comment || "";
document.writeln(comment+": "+this);
};
실전에서 사용하기에는 좋은 방법은 아니지만, 테스트 코드 작성의 편의성을 위해서 아무것이나 print()를 하면 화면에 출력되도록 하는 함수를 만들었습니다. 아래 예제에서 사용되는 함수이니 참조하세요.
#1. var로 선언하지 않은 변수는 이전 Context를 참조한다.
function ScopeTest(){
score = 50;
score.print("2nd");
}
var score = 10;
score.print("1st");
ScopeTest();
score.print("3rd");
2nd: 50
3rd: 50
function ScopeTest()
{
var score = 50;
score.print("2nd");
}
var score = 10;
score.print("1st");
ScopeTest();
score.print("3rd");
2nd: 50
3rd: 10
두 결과가 다르게 나오는 것은 var가 붙고 안 붙고 차이 입니다.
Javascript는 var로 선언한 변수 경우, Execution Context(이하 줄여서 Context)1가 생성할 때 해당 Context에 변수를 생성 합니다. 만약 해당 Context에 선언되지 않은 변수명를 사용하면, 이전 Context들을 차례로 뒤지면서 해당 변수명을 탐색 합니다. 최상위 Context에서도 변수를 발견하지 못하면 변수는 undefined 값을 가지고 되고, assignment 구문에서 사용되었다면 해당 변수명을 가진 변수를 새롭게 생성합니다. ( 이때, 변수는 최상위 Context에 생성 됩니다.)
Javascript는 함수 호출이 일어나게 되면 새로운 Context를 하나 생성합니다. 그리고 이 Context는 함수가 선언된 곳 하위에 Chian으로 차례로 연결됩니다. 위의 두 번째 예제에서는 ScopeTest()라는 함수가 호출될 때, 새로운 Context가 생성이 되었고 이는 가장 최상위 Context의 하위에 연결 되었습니다. 이 곳에서 새로운 변수 score를 선언 하였기 때문에 최상위 Context의 score와 다른 변수가 만들어 졌습니다.
#2. 이전 Context에 있는 변수를 사용 하려면 ??
function ScopeTest()
{
score = 20
var score = 50;
score.print("2nd");
}
var score = 10;
score.print("1st");
ScopeTest();
score.print("3rd");
1st: 10
2nd: 50
3rd: 10
function ScopeTest(context){
context.score = 20
var score = 50;
score.print("2nd");
}
var score = 10;
score.print("1st");
ScopeTest(this);
score.print("3rd");
2nd: 50
3rd: 20
Javascript는 해당 Context 내의 위치에서 어디라도 한번이상 변수가 선언(var)되면, Context를 생성할 때 변수도 같이 생성 합니다. (즉, 함수 내부에서 var score를 가장 아래에서 선언을 하나 가장 위에서 선언하나 동일한 효과를 지닙니다.) 따라서, 첫번째 예제는 score 변수가 새롭게 생성 도기 때문에 최상위 Context에 있는 score를 접근할 수 없는 것입니다.
코드의 가독성을 높이기 위해서는 함수 블럭의 최상위에서 변수 선언을 하는 것이 좋습니다.
C나 C++처럼 쓰고 싶은 상황이라면, 새 블럭마다 사용하는 변수를 모두 선언 해주는 것이 좋습니다.
이전의 Context에 있는 변수를 접근하려면 어떻게 해야 할까요? 일단 이전의 Context에 접근할 수 있는 직접적인 방법은 없습니다. 위의 두번째 예제에서는 this를 써서 접근하는 것과 같은 흉내를 내보이지만 사실상 this는 (Object를 생성하지 않은 상황이라서) window와 동일한 효과를 지니며, ScopeTest() 함수 내부에서도 역시 context.score는 window.score,this.score와 모두 동일하다고 볼 수 있습니다. ( 여기에 대한 자세한 사항은 깊게 언급할 수 없어서 넘어 갑니다. )
#3. 함수 파라미터로 선언한 변수들..
function ScopeTest(score)
{
score = 20;
score.print("2nd");
}
var score = 10;
score.print("1st");
ScopeTest(score);
score.print("3rd");
2nd: 20
3rd: 10
함수의 파라미터로 선언된 변수들은 함수가 호출될 때 생성되는 Context에 새롭게 생성 됩니다. 따라서 위의 예제에서는 ScopeTest() 함수가 호출되는 새로운 Context에 score 변수가 생성되기 때문에 var를 붙이지 않더라도 기존 Context의 변수와 분리됩니다.
#4. Context를 하나 더 만들기...
function ScopeTest1()
{
var score = 20;
ScopeTest2();
score.print ("2nd");
}
function ScopeTest2()
{
score = 30;
}
var score = 10;
score.print("1st");
ScopeTest1();
score.print("3rd");
2nd: 20
3rd: 30
function ScopeTest1()
{
var score = 20;
var ScopeTest2 = function () { score = 30; };ScopeTest2();
score.print("2nd");
}
var score = 10;
score.print("1st");
ScopeTest1();
score.print("3rd");
2nd: 30
3rd: 10
위의 두 예제가 어떻게 다른지 유심히 보시면 아실 것 같습니다. Context가 만들어 질 때, Scope Chain이 어떻게 연결되는지를 판단할 수 있어야 합니다. 중요한 것은 함수가 '어디서 호출 되느냐'가 아니라 함수가 '어디서 선언 되느냐'입니다.
두 번째 부분을 아래와 같은 InnerFunction 형태로도 나타낼 수 있습니다.
function ScopeTest1()
{
var score = 20;
function ScopeTest2()
{
score = 30;
}
ScopeTest2();
score.print("2nd");
}
좀 더 직관적으로 볼 수 있도록 도식화 해 보았습니다.
위의 그림과 같이 위의 두 예제에서 ScopeTest2()에서 score 변수는 다른 Context의 것을 참조 합니다. (참조: window는 최상위 Context에 위치한 Global Object를 나타냅니다. )
더 세부적이고, 기술적인 글은 Microsoft JScript 블로그의 Scope chain of JScript Functions을 참조하시면 도움이 되실 겁니다.
- ECMAScript Language Specification a-10 [본문으로]