언어/자바스크립트

[Javascript] 호이스팅(Hoisting ), 실행컨텍스트(execution context)

youngble 2022. 6. 13. 20:04

실행컨텍스트

실행컨텍스트(execution context)에는 실행할 코드에 제공할 환경 정보들을 모아놓은 객체인데 어떤 실행 컨텍스트가 활성화되는 시점에서 선언된 변수를 위로 끌어올리고(호이스팅), 외부 환경 정보를 구성하고, this 값을 설정하는 등의 동작을 수행하는데 이로인해  다른언어에서는 발견할수없는 특이한 현상들이 발생한다. 

이때 실행컨텍스트의 실행할 코드들을 실행할 때 필요한 환경 정보들을 모아 컨텍스트를 구성하고, 이를 콜 스택, call stack에 쌓아 올렸다가, 가장 위에 쌓여있는 컨텍스트와 관련 있는 코드들을 실행하는 식으로 전체 코드의 환경과 순서를 보장한다.

실행 컨테스트의 콜 스택 실행 순서 예시 코드

//-----(1)
var a = 1;
function outer(){
	function inner(){
    	console.log(a);
        var a = 3;
    }
    inner(); //----(2)
    console.log(a);
}
   
outer(); //----(3)
console.log(a);

먼저 자바스크립트 코드를 실해하는 순간 (1)전역 컨텍스트가 콜 스택에 담긴다. 전역 컨텍스트의 개념은 실행 컨텍스트와 특별히 다를 것이 없다. 최상단의 공간은 코드내부에서 별도의 실행 명령 없이도 브라우저에서 자동으로 실행하므로 자바스크립트 파일이 열리는 순간 전역 컨텍스트가 활성화된다고 생각하면된다. (1)가 콜스택에 담긴후, 코드 순서대로 진행하다가 (3)에서 outer 함수를 호출하면 자바스크립트 엔진은 outer에 대한 환경 정보를 수집해서 outer 실행 컨텍스트를 생성한 후 콜스택에 담는다. 콜스택 맨위에 outer 실행 컨텍스트가 놓인 상태가 되므로 전역 컨텍스트와 관련된 코드의 실행을 일시중단하고 대신 outer 실행 컨텍스트와 관련된 코드, 즉 outer 함수 내부의 코드들을 순서대로 진행한다. 이런식으로 (2) inner 함수의 실행컨텍스트가 콜스택의 가장 위에 담기고 진행중인 outer 코드 실행을 일시중단하고 inner 함수 내부의 코드들을 순서대로 진행한다. inner내부 코드를 모두 실행하면 콜스택에서 제거되고 (2)에서 일시중단된 다음 코드를 실행한다. (3) outer 함수를 종료하면서 해당 실행 컨텍스트가 콜스택에서 제거되고 아무것도 남지 않은 상태에서 종료된다.

 

Variable, Lexical Environment

실행컨텍스트가 활성화될 때 자바스크립트 엔진해당 컨텍스트에 관련된 코드들을 실행하는데 필요한 환경 정보들을 수집해서 실행 컨텍스트 객체에 저장한다. 객체는 자바스크립트 엔진이 활용할 목적으로 생성할 뿐 개발자가 코드를 통해 확인할 수는 없다.

이때 담기는 정보는 VariableEnvironment, LexicalEnvironment, ThisBinding 이다.

Variable Environment에 담긴 내용은 Lexical Environment에 담긴 내용과 같지만 최초 실행시의 스냅샷을 유지한다는 점에서 다르고 실행컨텍스트를 생성할 때 VariableEnvironment에 정보를 먼저 담고 이를 그대로 복사해서 LexicalEnvironment를 만들고 이후에는 LexicalEnvironment를 주로 활용하게 된다. 내부구성은 environmentRecordouterEnvironmentReference로 이루워지고 초기 과정 중에는 완전히 동일하고 코드 진행에 따라 서로 달라지게 된다.

 

호이스팅

environmentRecord에는 현재 컨텍스트와 관련된 코드의 식별자 정보들이 저장된다. 컨텍스트를 구성하는 함수에 지정된 매개변수 식별자, 선언한 함수, var로 선언된 변수의 식별자 등이 식별자에 해당한다. 처음부터 끝까지 순서대로 수집한다.

 

매개변수와 변수에 대한 호이스팅

function a(x){ // 수집 대상1 (매개변수)
    console.log(x); // (1)
    var x; // 수집 대상2 (변수 선언)
    console.log(x); // (2)
    var x = 2; // 수집대상3 (변수 선언)
    console.log(x); // (3)
}
a(1)

위의 코드에서 호이스팅시 함수 내부의 코드보다 매개변수(인자)의 선언 및 할당이 먼저 이루워진다. 이 매개변수 선언 및 할당이 먼저 이루워진다고 생각하고 짠 코드는 다음과 같다.

function a(){
    var x = 1;
    console.log(x);
    var x;
    console.log(x);
    var x = 2;
    console.log(x);
}

a();

위의 코드처럼 var x = 1 이라는 코드를 맨위에서 이루워진다고 생각하면된다.

이제 호이스팅처리를 한다면 다음과 같은 순서로 이루워진다.

function a(){
    var x;  //수집 대상1 변수선언
    var x;  //수집 대상2 변수선언
    var x;  //수집 대상3 변수선언
    
    x = 1; //수집 대상1의 할당
    console.log(x); //(1)
    console.log(x); //(2)
    x = 2; //수집 대상3의 할당
    console.log(x); //(3)
}

a();

결과

(1)은 1, (2)은 1, (3)은 2 출력

 

함수 선언의 호이스팅(1)

function a(){
    console.log(b);
    var b = 'bbb';
    console.log(b);
    function b(){}
    console.log(b);
}
a();

a함수가 실행하는 순간(a()), a함수의 실행컨텍스트를 생성하고 이때 변수명함수선언의 정보를 위로 끌어올린다(수집). 이때 중요한건 변수는 선언부와 할당부를 나누어 선언부만 끌어올리는 반면 함수선언은 함수 전체를 끌어올린다.

호이스팅이 이루워졌다고 생각한 코드의 순서는 다음과 같다.

function a(){
   var b; // 변수는 선언부만 끌어올림
   function b(){} // 함수선언은 전체를 끌어올림
   
   console.log(b);
   b = 'bbb'; // 변수의 할당부는 원래 자리에 남겨둠
   console.log(b);
   console.log(b);

}

이때 중요한건 함수 선언문은 함수명으로 선언한 변수에 함수를 할당한 것처럼 여길수 있다.

 

function a(){
   var b; // 변수는 선언부만 끌어올림
   var b = function b(){} // 함수선언은 전체를 끌어올림
   
   console.log(b); //(1)
   b = 'bbb'; // 변수의 할당부는 원래 자리에 남겨둠
   console.log(b); //(2)
   console.log(b); //(3)

}

결과

(1)은 [Function: b], (2)는 'bbb', (3)은 'bbbb'