실행 컨텍스트 & 스코프체인 & 호이스팅

zimablue·2023년 5월 9일

javascript

목록 보기
6/30

함수(전역 포함)를 실행할 때 필요한 조건, 환경정보를 담은 객체입니다.


컨텍스트에 따른 console.log의 실행 순서

// 전역 컨텍스트
let a = 1;

function outer() {
  	// 함수 컨텍스트
	console.log(a); // 첫 번째
	
	function inner() {
    console.log(a);  // 두 번째
      
    let a = 3
    }
  
	inner();

	console.log(a);  // 세 번째
}
outer();

console.log(a);  // 네 번째

1. 전역 실행 컨텍스트

함수 외부에서 선언된 코드들로 이루어진 실행 컨텍스트입니다.
브라우저가 스크립트를 로딩해서 실행하는 순간 부터 모든 것을 포함합니다.

위의 코드에서는 let a = 1 , outer() , 네 번째 console.log(a) 순으로 실행됩니다.
function outer() 는 함수를 선언하는 코드일 뿐 실행하는 명령이 아니라서 제외했습니다.


2. 함수 실행 컨텍스트

전역 컨텍스트에서 outer() 코드가 선언된 function outer() 를 호출하여 실행하면서 outer 함수의 실행 컨텍스트가 생깁니다.

outer 함수 컨텍스트에서 첫 번째 console.log(a) 가 실행되고 inner()function inner() 를 호출하여 실행하면서 inner() 함수의 실행 컨텍스트가 생깁니다.

inner 함수 컨텍스트에서 두 번째 console.log(a) 가 실행되고 let a = 3 코드 이후 inner 함수 컨텍스트는 종료됩니다.

이후 outer 함수 컨텍스트에서 세 번째 console.log(a) 가 실행되고 outer 함수 컨텍스트는 종료됩니다.

전역 컨텍스트에서 네 번째 console.log(a) 가 실행되고 전역 컨텍스트가 종료됩니다.



call stack

위의 실행 순서를 보면 가장 먼저 실행되었던 컨텍스트가 가장 마지막에 종료되고 가장 마지막에 실행되었던 컨텍스트는 가장 처음에 종료됩니다.

이렇게 시간 순서상 데이터를 후입선출(나중에 들어온 데이터를 먼저 내보냄)하는 자료구조를 stack이라고 하며, 코드 실행해 관여하는 스택을 call stack이라고 합니다.

즉, 현재 어떤 함수가 동작중인지, 다음에 어떤 함수가 호출될 예정인지 등을 제어하는 자료구조입니다.



실행 컨텍스트의 내부

환경 정보

이미지 출처: NHN Cloud



1. Variable Environment

현재 환경과 관련된 식별자 정보 수집합니다.
컨텍스트 내부가 실행되는 동안 변수의 값이 변화가 생겨도 반영되지 않는 최초의 environmentRecord와 outerEnvironmentReference의 snapshot을 가지고 있습니다.


2. Lexical Environment

어떤 실행 컨텍스트를 구성하는 환경 정보를 모아 사전처럼 구성한 객체입니다.

현재 환경과 관련된 각 식별자의 데이터 추적합니다.
컨텍스트 내부가 실행되는 동안 변수의 값이 변화가 생기면 실시간으로 반영됩니다.

Lexical Environment는 environmentRecord와 outerEnvironmentReference를 가지고 있습니다.

  • environmentRecord
    현재 컨텍스트에서 선언되어 있는 식별자들의 정보를 수집합니다.(hoisting)

  • outerEnvironmentReference
    외부의 Lexical Environment에 대한 참조입니다.(scope chain)
    즉, 현재 문맥에 관련 있는 외부 식별자 정보입니다.

ex)
내부 식별자 a: 현재 값은 undefined 입니다.
내부 식별자 b: 현재 값은 20 입니다.
외부 정보: D를 참조합니다.


3. This Binding

this를 참조하기 위한 공간입니다.



Hoisting

environmentRecord의 정보 수집 과정을 좀 더 쉽게 이해하기 위해서 만든 허구의 개념입니다.
선언과 할당, 실행 코드가 있을 때 작성 순서와 관계없이 선언 코드부터 실행됩니다.

console.log(a());
console.log(b());
console.log(c());

function a() {
	return 'a';
}

var b = function bb() {
	return 'bb';
}
var c = function() {
	return 'c';
}

위의 코드는 아래와 같은 순서로 실행됩니다.

function a() {
	return 'a';
}

var b;
var c;

console.log(a());
console.log(b());
console.log(c());

b = function bb() {
	return 'bb';
}
c = function() {
	return 'c';
}

코드의 실행 순서로 보았을 때 함수 선언문은 전체가 위로 끌어올라가고 나머지 식별자는 선언만 끌어올려지는 것으로 보입니다.
이렇게 끌어올리는 것처럼 보이는 허구의 개념을 호이스팅이라고 합니다.


이렇게 끌어올려진 아래의 내용 전체가 실행 컨텍스트 내부의 환경 정보중 하나인 environmentRecord입니다.

function a() {
	return 'a';
}

var b;
var c;

변수를 선언하는 let과 상수를 선언하는 const도 var과 같이 hoisting 되지만 Temporal Dead Zone라는 영역에 속하면서 ReferenceError가 발생합니다.



Scope

변수에 접근할 수 있는 유효범위입니다.
실행 컨텍스트가 수집해 놓은, 실행컨텍스트 내부에서만 존재하는 정보입니다.
함수, 블록, 전역 범위로 나눠져 있습니다.

lexical scope

함수가 정의되는 시점에 결정되는 스코프입니다.
lexical scope는 함수가 정의된 위치에 따라서 함수의 상위 scope를 결정합니다.
JavaScript는 Lexical Scope를 따르고 있습니다.

블록 안에서 선언된 변수들은 지역 스코프를 갖기 때문에 블록 밖에서는 접근할 수 없습니다.
(반복문이나 조건문의 조건을 입력하는 ()도 포함)

let i = 5;

// 변수 i는 함수a 라는 지역 스코프를 갖음
function a(){
	let i = 10;
 	b();
};

function b(){ // 정의된 위치
	console.log(i); // 5
};

a();

예외

함수 선언식은 예외적으로 반복문이나 조건문에서 지역 스코프를 갖지 않습니다.

if (true) {
  function funcB() {
    console.log('funcB')
  }
}

funcB() // funcB
for (let i = 0; i < 10; i++) {
  function funcB() {
    console.log('funcB')
  }
}

funcB() // funcB

dynamic scope

함수가 호출되는 시점에 결정되는 스코프입니다.
dynamic scope는 함수가 실행되는 위치에 따라서 함수의 상위 스코프를 결정합니다.

var i = 5;

function a(){
	var i = 10;
 	b(); // 실행되는 위치
};

function b(){
	console.log(i); // 10
};

a();

Scope Chain

outerEnvironmentReference 를 통해 먼저 실행된 컨텍스트의 environmentRecord 에 접근할 수 있는 일종의 리스트입니다.

가장 마지막에 실행된 실행 컨텍스트는 가장 먼저 실행된 전역 컨텍스트의 environmentRecord 까지 접근할 수 있습니다.

각각의 컨텍스트들은 필요한 변수를 environmentRecord 에서 먼저 찾고 없을 경우 outerEnvironmentReference 를 타고 순차적으로 점점 멀리있는 environmentRecord 에 접근하여 필요한 변수를 찾습니다.


shadowing

필요한 변수가 실행 컨텍스트에 없을 때 가까운 컨텍스트로부터 먼저 찾아진 것만 접근이 가능한 개념입니다.
필요한 변수가 찾아지면 더 이상 다른 컨텍스트에 접근할 수 없습니다.

예시

let a = 1;

function outer() {
	console.log(a);
  	// 1이 출력됩니다.
	
	function inner() {
    console.log(a);
    // undefined가 출력됩니다.
    
    let a = 3
    }
  
	inner();

	console.log(a);
  	// 1이 출력됩니다.
}
outer();

console.log(a);
// 1이 출력됩니다.
  1. outer 함수 컨텍스트의 environmentRecord 에는 변수 a가 없으므로 outerEnvironmentReference 를 타고 전역 컨텍스트의 environmentRecord 에 있는 변수 a를 사용하여 1이 출력됩니다.

  2. inner 함수 컨텍스트 environmentRecord 에는 a가 선언되어 있으므로 outerEnvironmentReference 를 타고 외부의 environmentRecord 에 접근하지 않습니다.
    그러나 할당은 console.log(a) 이후에 되기 때문에 undefined 가 출력됩니다.

  3. outer 함수 컨텍스트의 마지막 console.log(a) 도 1번과 마찬가지로 1을 출력합니다.

  4. 전역 컨텍스트의 console.log(a) 는 전역 컨텍스트의 environmentRecord 에 있는 변수 a를 사용하여 1을 출력합니다.



요약

실행 컨텍스트가 무엇인가요?

함수(전역 포함)를 실행할 때 필요한 조건과 환경정보를 담은 객체입니다.

실행 컨텍스트는 어떻게 구성되어 있나요?

환경정보는 Variable Environment, Lexical Environment, This Binding으로 이루어져 있습니다.
Lexical Environment에는 현재 컨텍스트에서 선언되어 있는 식별자들의 정보를 수집하는 Environment Record와 현재 컨텍스트 외부의 Lexical Environment를 참조하는 Outer Environment Reference가 있습니다.

호이스팅에 대해 설명해주세요.

Environment Record의 정보 수집 과정을 좀 더 쉽게 이해하기 위해서 만든 허구의 개념입니다.
코드의 실행 순서로 보았을 때 선언과 할당, 실행 코드가 있으면 작성 순서와 관계없이 선언 코드가 먼저 실행됩니다.
선언 코드가 다른 코드들 보다 먼저 실행되는 것이 선언 코드가 위로 끌어올려지는 것처럼 보이는 현상을 호이스팅이라고 부릅니다.

0개의 댓글