반응형

1. 실행 컨택스트 개념

 자바스크립트가 실행될 떄에는 실행 단위인 실행 컨텍스트하에서 실행된다. 실행 가능한 자바스크립트 코드 블록이 실행되는 환경이라고 할 수 있다. 실행 컨텍스트(Execution Context)는 "실행 가능한 코드의 형상화하고 구별하는 추상적인 개념(the abstract concept by used by ECMA-262 specification for typification and differentiation of an executable code)" 라고 정의된다.

 ECMA 표준에서는 이에 대한 정확한 구조를 정의하지 않는다. 이는 ECMAScript-Engine의 구현에서의 문제인 것이다. 실행 가능한 코드(Executable code)는 1) Global Code, 2) Function Code, 3) eval() Code가 있다. 활성화된(Active) 실행 컨텍스트는 Stack(Execution Context Stack: ECStack) 을 형성하는데, 이 Stack의 맨 아래에는 Global Context가 있으며 함수가 호출되면 Stack에 해당 Context가 쌓이게 된다. 현재 실행 중인 Context가 Stack에 맨 위에 있게 된다.(아래 그림 참조)

 전역 컨텍스트는 가장 먼저 실행되는 컨텍스트이다. 


1.1. Global Code

 Global Code는 프로그램 단위에서 처리된다. 즉 프로그램이 실행되는 시점에 생성된다. node.js에서 .js 파일을 실행하거나 브라우저가 <script></script> 테그를 만나게 되면 생성된다고 할 수 있다. 


1.2. Function Code

 함수 호출(function () 호출)을 하게 되면 ECStack 맨 위에 새로운 실행 컨텍스트를 생성한다. 그리고 새롭게 생성한 실행 컨텍스트로 제어가 넘어가게 되며 function() 이 return 할때 빠져나오게 된다. 제어가 함수를 호출한 원래의 실행 컨텍스트로 넘어오게 되면 해당 실행 컨텍스트는 garbage collection 대상이 되는데, 이때 closure를 생성하게 되면 Garbage Collection에서 제외된다.


1.3. Eval() Code

 Eval Code는 새로운 실행 컨텍스트를 생성하게 되지만 이 실행 컨텍스트는 호출하는 컨텍스트의 변수 객체를 이용하게 된다. 이를 calling context라고 표현하는데 ES5 의 strict-mode에서는 Eval Code는 calling context에 영향을 주지 않는다. 대신 local sandbox 에서 code를 실행하게 된다. 따라서 eval() 함수의 코드는 eval() 함수를 실행하는 컨텍스트의 변수객체가 영향을 받게 된다.  다음의 코드에서 처럼 global context에서 eval code의 실행은 global context의 변수에 영향을 준다. 그리고 FunctionCxt에서는 해당 Function Context의 변수가 영향을 받게 된다.


1
2
3
4
5
6
7
8
eval('var x=1');
function FunctionCxt(){
    eval('var y=2');
    console.log(y);
};
console.log(x); // 1
FunctionCxt();  // 2
console.log(y); //undefined
cs




2. 실행 컨텍스트 생성

 실행 컨텍스트는 변수객체(Variable Object)와 스코프체인(Scope Chain) 그리고 This 객체를 가진다.


2.1. 변수객체(Variable Object)

 변수 객체는 실행 컨텍스트와 연관된 데이터를 담고 있는 객체이다. 해당 실행 컨텍스트에서 사용하게 될 매개변수지역 변수, 사용할 함수 선언(Function Declaration)등을 저장한다. 이때 주의해야 할 것은 함수 표현은 저장되지 않는다. 예를 들어 다음의 코드에서 bar() 함수는 변수 객체로 저장되지만, baz() 함수는 변수 객체 내에 등록되지 않는다. 따라서 baz 함수는 해당 실행 컨텍스트에서 참조될수 없다.

1
2
3
4
5
var foo = 10
function bar() {console.log('hello bar'}; //function declaration
(function baz() {console.log('hello baz')}); // function expression
bar(); //hello bar
baz(); //ReferenceError : baz is not defined
cs

 함수가 호출되어서 활성화(activated 또는 called) 되면 활성객체(activation object)가 생성되는데 activation object는 함수가 호출되면서 받은 파라미터를 가지고 있는 arguments 객체를 가지며 해당 함수의 실행 컨텍스트에 변수 객체로 활용된다. 즉 activation object는 arguments 객체를 가진 변수 객체인 것이다. 

 활성 객체가 생성되고 arguments 객체가 생성되면 지역 변수를 생성한다. 지역 변수는 메모리에 생성만 되어 있는 상태로 null또는 undefined 된 상태가 된다. 이 지역 변수들은 표현식이 실행되기 전까지는 초기화 되지 않는다. 


2.2. 스코프 체인(Scope Chain)

 스코프 체인은 현재의 컨텍스트에서 나타나는 식별자를 찾는 범위를 나타내는 리스트 객체이다. [[SCOPE]]로 표현된다. 자바스크립트에서 스코프의 범위는 함수 내의 {}만이 유효 범위를 설정하는 유일한 단위이다. C에서는 함수의 {} 뿐만 아니라 if, for문의 {}도 하나의 유효범위를 설정하지만 자바스크립트에서는 for나 if문의 {}은 유효범위가 없다. 다시 말해 for문이나 if문의 {}에서 선언한 변수는 {} 밖에서도 접근 가능하다.

 실행 컨텍스트에서 식별자는 변수 이름, 함수 선언, 파라미터 등이 될수 있다. 어떤 함수의 코드에서 참조하는 식별자가 지역 변수가 아닐 경우 이를 자유변수(free variable)이라고 하는데, 이 자유변수를 찾을때 스코프 체인이 이용 된다.

 일반적으로 스코프 체인은 해당 함수가 생성될 당시의 실행 컨텍스트의 변수객체에 해당 함수의 실행 컨텍스트의 변수객체(활성객체)를 더한 것이다. 결국 스코프체인=현재 실행 컨텍스트의 변수 객체+상위 컨텍스트의 스코프 체인 이라고 할 수 있다. 예를 들어 다음의 코드를 살펴보겠다.




 func2의 실행 컨텍스트에서는 func1Var 와 func2Var가 func2의 활성 객체 내에 존재한다. 그런데 globalVar의 경우에는 func2활성 객체에 존재하지 않고, func1 활성객체에도 존재하지 않는다. 스코프체인을 따라가면서 변수를 찾는데, 결국 Global 변수 객체에서 globalVar를 찾아서 출력하게 된다.

 func1의 실행 컨텍스트에서는 func1Var가 존재하고 func2Var는 찾을수가 없다. 따라서 func2Var를 출력하려고 하면 Reference Error가 발생하게 된다.

 func2에서 func1Var를 참조할때 func1 활성객체에 func1Var가 존재하여도 스코프체인상에서 Func2 활성객체에서 먼져 참조하게 되므로 func1Var는 "func2 AO"가 출력되게 된다.


여기에서 주의해야 할 것은 실행 컨텍스트는 해당 함수의 정의된 부분에 따라 달라진다는 것이다. 이 부분이 헷갈리게 만드는 부분인데, 실행하면서 어떤 함수를 호출하는 상태에 따라서 스코프가 정해지는 것이 아니고 그 함수가 어디에 정의되어 있느냐에 따라서 스코프가 정해진다. 다음의 예제가 이를 설명해 준다.

 

 (3)에서 oddCxtFunction() 을 호출하면 호출하는 실행 컨텍스트에 oxxCxtFunction이 연결된 [[SCOPE]]가 발생하는 것이 아니고 (1)에서처럼 global 변서 객체에 oddCxtFunction의 활성객체가 연결된 [[SCOPE]]가 생성된다. 즉 해당 코드가 어디에 실행되느냐가 아니라 어디에 존재하느냐에 따라서 스코프 체인이 결정되는 것이다.

 스코프 체인은 with 와 catch 키워드에 따라서 변경될수 있다. with 구문은 표현식을 실행하는데 표현식이 객체이면 현재 실행 컨텍스트의 스코프 체인에 추가된다. 예를 들어 다음의 코드에서 with에 {z:50}을 전달하면 with의 block에서 현재 실행 컨텍스트 [[SCOPE]]의 상위에 {z:50}이 추가되어 {z:50}을 먼져 참조하게 된다.


1
2
3
4
5
var z=10;
with({z:50}){
    console.log(z); //50
}
console.log(z);//10
cs


2.3. this Binding

 this는 유일하게 사용자가 실행 컨텍스트에서 접근 가능한 부분이다. this 는 현재 컨텍스트가 참조하고 있는 객체를 가르키며 이 값은 어떻게 함수가 호출되었는지에 따라 달라진다. (예를 들어 new 와 함께 호출되면 새로운 객체를 할당하고 this로 설정한다.) this 가 참조하는 값이 없으면 전역 객체를 가르킨다.


3. 코드 실행

 실행 컨텍스트가 만들어지고 변수객체가 만들어지면 코드에 있는 여러 표현식들의 실행이 이루어진다. 전역 실행 컨텍스트는 일반적인 실행 컨텍스트와 약간 다른데, argument객체가 없으며, 전역 객체 하나만을 포함하는 스코프 체인이 있다. 전역 실행 컨텍스트에서는 변수 객체가 전역 객체로 사용된다. 즉 전역 실행 컨텍스트에서는 변수 객체가 곧 전역 객체이다. 전역적으로 선언된 함수와 변수가 전역 객체의 프로퍼티가 되는 것이다. 


4. 함수 호이스팅에 대하여

 함수 호이스팅은 함수를 선언문 형태로 정의할때 함수가 자신이 위치한 코드와 상관 없이 함수의 유효범위가 코드의 맨 처음부터 시작되는 현상이다. 실행 컨텍스트가 생성될떄 함수 선언문으로 선언된 함수의 코드가 실행되기 전에 참조점이 만들어지기 때문에 발생하는 현상이다. (함수 표현식은 저장되지 않는다.)

다음의 코드가 실행된다고 해보자


1
2
3
4
5
6
7
8
9
10
11
foo(); //Type Error
bar(); //bar:undefind
var foo=function(){ //함수 표현식 방식의 정의
    console.log("foo:"+x);
}
function bar(){ //함수 선언문 방식의 정의
    console.log("bar:"+x);
}
var x=10;
foo(); //foo:10
bar(); //bar:10
cs


이 코드의 실행 컨텍스트가 만들어질때 


변수객체에는 foo, x와 함수 bar() 가 저장된다. 이때 foo와 x에는 초기화 되어 undefined로 설정된다.  


코드가 실행될때 


라인 1에서 foo()를 만나게 되면 foo의 값은 undefined이므로 Type Error가 발생한다. 

라인 2의 bar()를 호출하게 되면 function bar()가 저장되어 있으므로 bar의 코드를 실행하게 되는데, bar에 x는 x에 변수를 할당하는 표현식이 실행되지 않았으므로 undefined가 출력되게 된다. 

라인3에서 foo에 함수를 할당한다.

라인9에서 x에 10을 할당한다.

라인10에서 foo()는 foo에 할당된 함수를 실행하게 되며 foo함수에서는 x를 참조하는데 x에는 라인9가 실행되어 10을 할당하였으므로 foo:10을 출력하게 된다.

라인11에서 bar()는 bar함수를 호출하고 역시 라인9에서 x에 10이 할당 되었으므로 bar:10을 출력하게 된다.


라인3과 라인 6에서 함수 표현식 방식과 선언문 방식의 정의에 따라 동작하는 방식이 달라지는데, 이는 실행 컨텍스트를 생성하는 과정에 따라 달라지는 것이다. 변수 생성은 실행 컨텍스트를 생성하면서 만들고 변수의 값은 코드가 실행되면서 할당되기 때문인데, 함수 선언은 변수 생성시 만들어지기 떄문에 함수 선언문 방식으로 선언한 함수는 코드의 어느 위치에 있던 유효 범위가 맨 처음부터 시작되는 것이다.



참조 사이트

http://dmitrysoshnikov.com/ecmascript/chapter-1-execution-contexts/

http://dmitrysoshnikov.com/ecmascript/javascript-the-core/

한빛미디어 "인사이드 자바스크립트" 책


반응형
Posted by alias
,