실행 컨텍스트와 클로저
실행 컨텍스트 개념
실행 가능한 자바스크립트 코드 블록(대부분 함수)가 실행 되는 환경 . ECMAScript에ㅓ는 실행 컨텍스트가 형성되는 경우를 세가지로 규정하고 있다.
1. 전역 코드
2. eval() 함수로 실행되는 코드
3. 함수안의 코드를 실행할 경우
현재 실행되는 컨텍스트에서 이 컨텍스트와 관련 없는 실행 코드가 실행되면, 새로운 컨텍스트가 생성되어 스택에 들어가고 제어권이 그 컨텍스트로 이동한다.
실행 컨텍스트 생성 과정
function execute(param1 , param2) {
var a = 1, b = 2;
function func(){
return a+b;
}
return param1 + param2 + func();
}
자바스크립트에서는 함수를 실행하여 실행 컨텍스트가 생성되면 자바스크립트 엔진은 다음과 같은 정해진 순서대로 실행한다.
1.활성객체 생성
실행 컨텍스트가 생성되면 자바스크립트 엔진은 해당 컨텍스트에서 실행에 필요한 여러 가지 정보를 담을 객체를 생성하는데, 이를 활성 객체라고 한다. 이 객체에 앞으로 사용하게 될 매개변수나 사용자가 정의한 변수 및 객체를 저장하고 , 새로 만들어진 컨텍스트로 접근(엔진 내부에서) 가능하게 되어 있다.
2.arguments 객체 생성
앞서 만들어진 활성 객체는 argument 프로퍼티로 이 arguments 객체를 참조한다.
3.스코프 정보 생성
현재 컨텍스트의 유효 범위를 나타내는 ㅡ코프 정보를 생성한다. 이 스코프 정보는 현재 실행 중인 실행 컨텍스트 안에서 연결 리스트와 유사한 형식으로 만들어 진다. 이 리스트에서 찾지 못한 변수는 결국 정의되지 않는 변수에 접근하는 것으로 판단하여 에러를 검출한다. 이 리스트를 스코프 체인이라고 하며, [scope] 프로퍼티로 참조된다. 현재 생성된 활성 객체가 스코프 체인의 최상단에 추가된다.
4.변수 생성
실행 컨텍스트 내부에서 사용되는 지역 변수의 생성이 이루어진다. execute() 함수 안에 정의 된 변수 a,b 와 함수 func가 생성된다. 여기서 주의할 점은 이 과정에서는 변수나 내부 함수를 단지 메모리에 생성만 하고 , 초기화는 각 변수나 함수에 해당하는 표현식이 실행되기 전까지는 이루어지지 않는다는 점이다.
5.this 바인딩
마지막 단계에서는 this 키워드를 사용하는 값이 할당된다. this는 함수 호출방식에 따라 다르게 바인딩 된다.
6.코드 실행
이렇게 하나의 실행 컨텍스트가 생성되고, 변수 객체가 만들어진 후에, 코드에 있는 여러 가지 표현식 실행이 이루어진다. 이렇게 실행되면서 변수의 초기화 및 연산, 또 다른 함수 실행 등이 이루어진다.
스코프 체인
자바를 예로 들 경우 { } 안의 변수를 선언하고 , { } 안의 코드실행을 마치면 { } 안의 변수들은 사라져 버린다. 하지만 자바스크립트에서는 오직 함수만이 유효 범위의 한 단위가 된다. 이 유효범위를 나타내는 스코프가 [scope] 프로퍼티로 각 함수 객체 내에서 연결리스트 형식으로 관리하는데 이를 스코프 체인이라고 한다. 실행 컨텍스트의 변수 객체가 구성요소인 리스트와 같다.
각각의 함수는 [scope] 프로퍼티로 자신이 생성된 실행 컨텍스트의 스코프 체인을 참조한다. 함수가 실행되는 순간 실행 컨텍스트가 만들어지고, 이 실행 컨텍스트는 실행된 함수의 [scope]프로퍼티를 기반으로 새로운 스코프 체인을 만든다.
전역 실행 컨텍스트의 스코프 체인
var var1 = 1;
var var2 = 2;
console.log(var1);
console.log(var2);
위의 코드는 전역 코드이다. 이 코드를 실행하면 먼저 전역 실행 컨텍스트가 생성되고, 변수객체가 만들어진다. 이 변수 객체의 스코프 체인은 어떻게될까..? 현재 전역 실행 컨텍스트 단 하나만 실행되고 있어 참조할 상위 컨텍스트가 없다. 자신이 최상위에 위치하는 변수 객체인 것이다. 따라서, 이 변수 객체의 스코프 체인은 자기 자신만을 가진다. 변수 객체가 곧 전역 객체가 되는 것이다.
함수를 호출한 경우 생성되는 실행 컨텍스트의 스코프 체인
ex1)
var value = "value1";
function printFunc(){
var value = "value2";
function printValue(){
return value;
}
console.log(printValue());
}
위의 예제의 결과는 어떻게 될까..?
1.전역객체에서 printFunc 실행 컨텍스트를 생성하고 printFunc실행 컨텍스트는 전역객체의 스코프체인 위에 새로운 변수객체를 추가한다.
[printFunc 변수객체 - 전역객체]
2.printFunc 함수에서는 printValue함수를 정의하고 printValue를 실행한다. 함수를 실행하였으므로 printValue 실행 컨텍스트가 실행되며 , 새로운 변수객체가 스코프 체인에 추가된다.
[printValue 변수객체 - printFunc 변수객체 - 전역객체]
3.printValue 변수객체에는 value 변수값이 없으므로 다음 printFunc 변수객체를 참조하여 value값을 출력한다.
4. 최종 출력값은 value2이다
ex2)
var value = "value1";
function printValue(){
return value;
}
function printFunc(func) {
var value = "value2";
console.log(func());
}
printFunc(printValue)
위의 예제의 출력값은 어떻게 될까..?
1. 전역객체에서 printValue 와 printFunc 함수를 정의한 뒤 printFunc를 실행할 때 printValue함수의 참조값을 전달하였다. printFunc함수가 실행되었으므로 printFunc실행 컨텍스트가 실행되며 , 새로운 변수객체가 스코프체인에 추가 된다.
[printFunc 변수객체 - 전역객체 ]
2.printFunc 함수에서는 전달받은 printValue함수를 실행시키므로 printValue실행 컨텍스트가 실행되며 새로운 변수객체를 기존 스코프체인 최상단에 추가한다. 그렇다면 printValue가 참조하는 스코프 체인은 어떤 상태일까? [ printValue - printFunc - 전역객체 ] 라고 생각할 수 있겠지만 [ printValue - 전역객체]가 옳다. 각 함수 객체가 처음 생성될 당시 실행 컨텍스트가 무엇인지 생각해본다면 , printValue는 전역객체 스코프에서 생성되었으므로 전역객체 상단에 변수객체를 추가한다.
3.최종출력값은 value1이다.
글로 표현하기 어려운 예제인것 같다.. 중요한 것은 스코프체인을 추가할 때 [기존 스코프체인] 에 현재 변수객체를 추가하는 것인데 , [기존 스코프체인] 이란 함수가 정의될 때 위치인 것을 생각해야하는 것 같다.
클로저
이미 생명 주기가 끝난 외부 함수의 변수를 참조하는 함수를 클로저라고 한다. 클로저로 참조되는 외부 함수의 변수를 자유 변수라고 한다.
function outerFunc(arg1, arg2){
var local = 8;
function innerFunc(innerArg){
console.log( (arg1 + arg2) / (innerArg + local) );
}
return innerFunc;
}
var exam1 = outerFunc(2,4);
exam1(2);
이때 outerFunc가 실행되면서 생성되는 변수 객체가 스코프 체인에 들어가게 되고, 이 스코프 체인은 innerFunc의 스코프 체인으로 참조된다. 즉 outerFunc() 함수가 종료되었지만, 여전히 내부 함수(innerFunc()) 의 [scope] 으로 참조되므로 가비지 컬렉션의 대상이 되지 않고 여전히 접근 가능하게 살아있다.