Javascript

JS ] 실행컨텍스트와 호이스팅, 스코프와 스코프 체인

여유로운 집순이 2021. 12. 7. 17:20

출처: https://github.com/BKJang/do-you-know-vanilla/issues/14

 


아래의 내용은 '코어 자바스크립트 - 02. 실행 컨텍스트' 부분을 제가 공부한 것과 함께 정리한 내용입니다.
제가 기억하기 위해 정리한 것이라 자세한 내용을 알고 싶으신 분은 책에 자세히 정리되어 있으니
읽어보시길 추천합니다.
 

 

실행컨텍스트


1) 실행컨텍스트란?

실행할 코드에 제공할 환경정보를 모아놓은 객체이다.
자바스크립트는 어떤 실행컨텍스트가 활성화 되는 시점에 선언된 변수를 위로 끌어올리고(hoisting), 외부 환경 정보를 구성하고, this 값을 설정하는 등의 동작을 수행한다.

자바스크립트의 엔진은 이 실행컨텍스트를 콜스택(stack)에 쌓아 올렸다가 가장 위에 쌓여있는 컨텍스트와 관련있는 코드들을 실행하는 식으로 전체 코드의 환경과 순서를 보장합니다. 

 

2) 실행컨텍스트 구성방법

실행컨텍스트를 구성하는 방법은 함수를 실행하는 것입니다. es6이상에서는 블록에 의해서도 실행 컨텍스트가 생성됩니다.

아래의 전역 컨텍스트라는 개념은 일반 실행 컨텍스트와 다를 것이 없습니다. 최상단의 공간은 코드 내부에서 별도의 실행 명령 없어도 브라우저에서 자동으로 실행된다는 점이 차이점입니다.

// -------------------------------------------- (1)
var a = 1;

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

}

outer(); -------------------------------------- (3)
console.log(a); // 1

 

위의 코드를 해석하면 다음과 같습니다.
> (1) 처음 자바스크립트 코드를 실행하는 순간 전역 컨텍스트가 콜스택에 담깁니다.
> 전역 컨텍스트안의 코드가 순차적으로 진행하다가
> (3) outer 함수가 실행되면 자바스크립트 엔진은 outer에 대한 환경 정보를 수집해서 outer 실행 컨텍스트를 만들어서 콜스텍에 담습니다.
> outer 함수 내부 코드들을 순차적으로 진행하다가
> (2) inner 함수의 실행컨텍스트가 콜스택에 담기면 outer 컨텍스트와 관련된 코드의 실행을 중단하고 inner함수 내부의 코드를 순서대로 진행합니다.
> inner 함수가 실행 종료되면 inner 실행 컨텍스트가 콜스텍에서 제거 됩니다.
> outer 함수가 실행 종료되면 outer 실행 컨텍스트가 콜스텍에서 제거 됩니다.
> 전역 공간에도 실행할 코드가 남아있지 않으면 전역 컨텍스트도 제거되고, 콜스택에는 아무것도 남지 않은 상태로 종료됩니다.

 

2) 실행컨텍스트 구성 정보

  • LexicalEnvironment: 현재 컨텍스트 내의 식별자들에 대한 정보(environmentRecord)외부 환경 정보(outerEnvironmentReference)로 변경사항이 실시간으로 반영됩니다.
  • VariableEnvironment : 선언시점의 LexicalEnvironment의 스냅샷으로 변경사항 반영하지 않습니다.
  • thisBinding: this 식별자가 바라봐야할 대상 객체입니다.

 

 

호이스팅


실행컨텍스트의 environmentRecord에는 현재 컨텍스트와 관련된 코드의 식별자 정보들이 저장됩니다.

코드가 실행되기 전에 실행컨텍스트가 만들어지므로 자바스크립트 엔진은 코드 실행 전 해당 환경에 속한 코드의 변수명들을 모두 알고있는 상황입니다.

그렇다면 엔진의 실제 동작 방식 대신에 '자바스크립트 엔진은 식별자들을 최상단으로 끌어올려놓은 다음 실제 코드를 실행한다'라고 생각하더라도 코드를 해석하는데는 문제될 것이 전혀 없을 것입니다.

여기서 '호이스팅'이라는 개념이 등장합니다. '끌어올리다'라는 의미의 hoist에 ing를 붙인 동명사인 호이스팅은 변수 정보를 수집하는 과정을 더욱 이해하기 쉬운 방법으로 대체한 가상의 개념입니다. 

 

[ 호이스팅 예제 ]

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

a(1);

위의 예제로 보면 (1) = 1, (2) = undefined, (3) = 2 가 나올 것 같지만

식별자가 호이스팅 되면서 다음과 같은 상황이 벌어진다. (실제로 다음과 같이 코드가 바뀌는 것이 아님을 주의!)

function a () {
	// 매개변수 x
	var x = 1;
	console.log(x);  // -------------- (1)
	var x;
	console.log(x);  // -------------- (2)
	var x = 2;
	console.log(x);  // -------------- (3)
}

a();
function a () {
	var x;
	var x;
	var x;

	// 매개변수 x
	x = 1;
	console.log(x);  // -------------- (1)
	
	console.log(x);  // -------------- (2)
	x = 2;
	console.log(x);  // -------------- (3)
}

a();
environmentRecord는 현재 실행될 컨텍스트의 대상 코드 내에 어떤 식별자들이 있는지에만 관심이 있기때문에 
변수를 호이스팅할 때 변수명만 끌어올리고 할당 과정은 원래 자리에 그대로 남겨둡니다.
참고) 함수 선언은 함수 전체를 끌어올립니다.

변경시킨 코드를 확인해보면 

(1) = 1, (2) = 1, (3) = 2 이라는 결과를 얻는 것을 볼 수 있고, 해당 코드를 실행하면 같은 결과를 얻게 됩니다.

 

스코프와 스코프 체인


 스코프란 식별자에 대한 유효범위입니다. es5까지는 함수에 의해서만 스코프가 형성되었고, es6부터는 블록에 의해서도 스코프 경계가 발생됩니다. 

es6에서도 var로 선언한 변수에 대해서는 블록스코프가 작용하지 않으니 블록스코프를 이용하려면 let, const, class, strict mode에서의 함수선언에 대해서만 범위로서 역할을 수행함을 기억해야합니다.

이러한 '식별자의 유효범위'를 안에서부터 바깥으로 차례로 검색해나가는 것을 스코프체인이라고 합니다. 그리고 이를 가능하게 하는 것이 실행컨텍스트의 outerEnvironmentReference 입니다. 

outerEnvironmentReference는 현재 호출된 함수가 선언될 당시의 LexicalEnvironment를 참조합니다.

곧, A 함수의 내부에 B함수를 선언했다면 B의 LexicalEnvironment의 outerEnvironmentReference는 함수 B가 선언되던 때의 A의 LecicalEnvironment를 참조합니다. 이런 구조적특성 덕분에 여러 스코프에서 동일한 식별자를 선언한 경우에는 무조건 스코프 체인상에서 가장 먼저 발견된 식별자에만 접근 가능하게 됩니다. 

 

[ 스코프체인 예제 ]

var a = 1;

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

outer();
console.log(a); // 1

위의 코드의 설명은 다음과 같습니다. 

this는 실행 컨텍스트를 활성화하는 당시에 지정된 this가 저장됩니다.

함수를 호출하는 방법에 따라 그 값이 달라지는데, 지정되지 않은 경우에는 전역객체가 저장됩니다.