실행 컨텍스트(Execution Context) - 2 ( 호이스팅 직접 보고👀 이해하기💡)

Seob·2021년 2월 25일
1

Core JavaScript

목록 보기
2/3
post-thumbnail

아래 내용은 코어자바스크립트를 참고하여 정리한 내용으로, 스크린샷을 제외한 대부분의 내용과 사진의 출처는 <코어자바스크립트 저자 정재남 출판 위키복스>입니다!

실행 컨텍스트(Execution Context)에서 콜 스택에 무엇이 담기는지 알아보았다.

활성화된 실행 컨텍스트의 수집 정보는 다음과 같다.

Variable Environment

VariableEnvironment에 담기는 내용은 LexicalEnvironment와 같지만 최초 실행 시의 스냅샷을 유지한다는 점은 다르다.

VariableEnvironment에 대해 요약하면 다음과 같다.

  • 실행 컨텍스를 생성할 때 VariableEnvironment에 정보를 담는다.
  • 담긴 정보를 복사해서 LexicalEnvironment를 만들고 활용한다.
  • VariableEnvironmentLexicalEnvironment의 내부는 environmentRecordouter-EnvironmentReference로 구성되어 있다.

💡
위에 나온 영어 이름들은 처음 봤어도 괜찮고 무슨 의미인지 몰라도 괜찮다. 앞으로 하나씩 살펴보고 나면 알게 될 테니까! 🥸 영어단어를 따로 해석해서 이해를 해도 좋겠지만, 새로운 개념을 가진 새로운 단어를 새롭게 배운다고 가볍게 생각해도 좋을것 같다.

LexicalEnvironment

environmentRecord와 호이스팅

environmentRecord에는 현재 컨텍스트와 관련된 코드의 식별자 정보들이 저장된다. 컨텍스트 내부 전체를 처음부터 끝까지 훑으며 순서대로 수집한다.

식별자 정보는 다음과 같은 것들이 있다.

  • 컨텍스트를 구성하는 함수에 지정된 매개변수 식별자
  • 선언한 함수가 있을 경우 그 함수 자체
  • var로 선언된 변수의 식별자

변수 정보를 수집하는 과정은 코드가 실행되기 전에 모두 끝나게 되므로 자바스크립트 엔진은 코드를 실행하기 전 해당 환경에 속한 코드의 변수명들을 모두 알고 있는 상태이다. 그렇기때문에, 이미 알고 있는 변수를 위로 끌어올려 놓고 코드를 실행한다고 생각하면서 호이스팅(hoisting) 이라는 개념이 등장하게 된다.

호이스팅이란 '끌어올리다'라는 의미의 동명사인데, 자바스크립트 엔진이 실제로 끌어올리지는 않지만 편의상 끌어올린 것으로 간주하여 변수 정보를 수집하는 과정을 더욱 이해하기 쉽게 대체한 가상의 개념이다.

호이스팅 규칙

environmentRecord에는 매개변수의 이름, 함수 선언, 변수명 등이 담긴다고 했다. 이를 이용하여 몇 가지 예제를 살펴보자.

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)

우선 호이스팅이 되지 않을 때 (1), (2), (3)에서 어떤 값들이 출력될지를 예상해보면 1, undefined, 2가 출력될 것으로 예상된다.

실제로는 어떤 값이 출력될까? 예제를 통해 알아보자!

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

a();

LexicalEnvironment 입장에서는 arguments에 전달 된 인자를 담는 것을 제외하면 위의 코드처럼 내부에서 변수를 선언한 것과 다른 점이 없다. 즉 인자를 함수 내부의 다른 코드보다 먼저 선언 및 할당이 이뤄진 것으로 간주할 수 있다.

이 상태에서 호이스팅을 처리해 보자. (실제로는 일어나지 않으며 이해를 돕기 위한 예시일 뿐이다.)

  • environmentRecord는 현재 실행될 컨텍스트의 대상 코드 내에 어떤 식별자들이 있는지에만 관심이 있다.
  • 각 식별자에 어떤 값이 할당될 것인지는 관심이 없다.
  • 변수를 호이스팅할 때 변수명만 끌어올리고 할당 과정은 원래 자리에 그대로 남겨둔다. (매개변수도 마찬가지)

environmentRecord의 관점에서 수집대상 1, 2, 3을 순서대로 끌어올리고 나면 다음과 같은 형태로 바뀐다.

1  function a () {
2    var x;		// 수집 대상 1의 변수 선언 부분
3    var x;		// 수집 대상 2의 변수 선언 부분
4    var x;		// 수집 대상 3의 변수 선언 부분
5  
6    x = 1;		// 수집 대상 1의 할당 부분
7    console.log(x);	// (1)
8    console.log(x);	// (2)
9    x = 2;		// 수집 대상 3의 할당 부분
10   console.log(x);	// (3)
11 }
12
13 a(1);

스코프체인 수집 및 this 할당 과정은 생략하고 호이스팅이 끝난 후 코드가 실행되는 순서를 살펴보자.

  • 2번째 줄: 변수 x를 선언 -> 메모리에 저장할 공간을 미리 확보 -> 확보한 공간의 주솟값을 변수 x에 연결.
  • 3, 4번째 줄: 다시 변수 x를 선언 -> 이미 선언된 변수 x가 있으므로 무시
  • 6번째 줄: x에 1을 할당 명령 -> 숫자 1을 별도의 메모리에 담음 -> x와 연결된 메모리 공간에 숫자 1을 가리키는 주솟값을 입력
  • 7, 8번째 줄: 각 x를 출력명령 -> (1), (2) 모두 1을 출력
  • 9번째 줄: x에 2를 할당 명령 -> 숫자 2를 별도의 메모리에 담음 -> x와 연결된 메모리 공간에 숫자 1을 가리키는 주솟값이 이미 있음 -> 숫자 2를 가리키는 주솟값으로 대치 -> 변수 x는 숫자 2를 가리키게 됨
  • 10번째 줄: x를 출력 명령 -> (3)에서는 2가 출력 -> 함수 내부의 모든 코드가 실행됐으므로 실행 컨텍스트가 콜 스텍에서 제거됨
  • 결과적으로 1, 1, 2가 출력되었다.

    위의 이미지는 실제로 코드를 실행해 본 모습이다.

이처럼 호이스팅의 개념을 제대로 이해하지 못하고 있다면 (2)에서 1이 아니라 undefined가 출력된다고 예측하게 된다.

함수 선언을 추가한 예제를 하나 더 살펴보자!

function a () {
  console.log(b);	// (1)
  var b = 'bbb';	// 수집 대상 1(변수 선언)
  console.log(b);	// (2)
  function b() {}	// 수집 대상 2(함수 선언)
  console.log(b);	// (3)
}

a();

호이스팅을 생각하지 않고 출력 결과를 예상해 본다면 (1), (2), (3)은 순서대로 undefined, 'bbb', 함수 b 실행 이라고 예상된다. 하지만 실제로는 어떨까? 🤔

a함수를 실행하는 순간 a 함수의 실행 컨텍스트가 생성된다. 이때 변수명과 함수 선언의 정보를 수집한다. (끌어올린다) 변수는 선언부와 할당부를 나누어 선언부만 끌어올리는 반면 함수 선언은 함수 전체를 끌어올린다. 수집 대상 1수집 대상 2를 순서대로 끌어올리고 나면 다음과 같은 형태로 변환된다.

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

a();

이때 함수 선언문은 함수명으로 선언한 변수에 함수를 할당한 것처럼 여길 수 있으므로 편의를 위해 다음과 같이 한번 더 바꿔본다.

01 function a() {
02   var b;
03   var b = function b () { }	// 바뀐 부분
04   
05   console.log(b);		// (1)
06   b = 'bbb';
07   console.log(b);		// (2)
08   console.log(b);		// (3)
09 }
10 
11 a();

코드를 차례대로 실행하면 다음과 같다.

  • 2번째 줄: 변수 b를 선언 -> 메모리에 저장할 공간을 미리 확보하고, 확보한 공간의 주솟값을 변수 b에 연결
  • 3번째 줄: 다시 변수 b를 선언하고 함수 b를 선언된 변수 b에 할당 시도 -> 이미 선언된 b가 있으므로 선언 과정을 무시 -> 함수는 별도의 메모리에 담김 -> 그 함수가 저장된 주솟값을 b와 연결된 공간에 저장 -> 변수 b는 함수를 가리킴
  • 5번째 줄: 변수b에 할당된 함수 b를 출력
  • 6번째 줄: 변수 b에 'bbb'를 할당 시도 -> b와 연결된 메모리 공간에는 함수가 저장된 주솟값이 있었지만 'bbb'가 담긴 주솟값으로 덮어씀 -> 변수 b는 문자열 'bbb'를 가리키게 됨
  • 7, 8번째 줄: (2)(3) 모두 'bbb'를 출력 -> 모든 코드가 실행됐으므로 콜 스택에서 실행 컨텍스트가 제거됨

호이스팅을 고려하지 않았을 때의 결과와 다른 결과가 출력되었다.

다음은 호이스팅과 함께 알아두면 좋은 함수 선언문(function declaration)과 함수 표현식(function expression)을 정리해야겠다. 🤓

profile
Hello, world!

0개의 댓글