[ JavaScript ] 실행 컨텍스트(feat. 호이스팅, 스코프)

exceed_96·2024년 1월 29일

JavaScript

목록 보기
6/18
post-thumbnail

JavaScript의 클로져(Closure), 호이스팅(Hoisting) 등등 중요개념들을 알기 위해 먼저 공부해야하는 실행 컨텍스트(Excutation Context)에 대해 정리해보자


1. 실행 컨텍스트(Excutation Context)이란 무엇일까?

실행 컨텍스트는 코드가 실행되는 환경을 구성해준다.

그럼 어떤 환경이 조성되어 있을까?

먼저 JavaScript가 내부적으로 함수들과 내부 변수들을 어떻게 정의하고 호출하는지에 대한 이해가 필요하다.



2. 실행 컨텍스트의 생성단계, 실행단계

JavaScript코드를 실행시키면 JavaScript 엔진은 Call Stack에 전역 실행 컨텍스트를 담는다.



전역 실행 컨텍스트를 담은 이후에는 위에서부터 아래로 로직을 순환한 다음에 함수를 호출할 때 해당 함수에 대한 실행 컨텍스트를 생성한다. 해당 단계를 생성단계(Create Phase)라고 한다.

실행컨텍스트를 생성하면 코드 제어의 흐름은 막 생성된 함수실행컨텍스트로 넘어간다.

즉, Call Stack에서 제일 위에 있는 실행컨텍스트가 가지고 있던 프로그램의 제어권이 막 생성된 실행컨텍스트에게 넘어가고 제어권을 가지고 있던 기존 실행컨텍스트는 동작을 멈추게 된다.



2.1 생성단계

const fruit = "apple";

function func1() {
  const color = "red";
}

func1();
생성단계에서는 해당 함수가 가지고 있는 변수들을 확인해서 실행 컨텍스트 내부에 있는 Lexical Environment에 정의한다.

Lexical Environment에는 2가지 구성요소가 있다.

  • Environment Record
    해당 함수 실행 컨텍스트 내부에 있는 변수에 대한 정의를 하는 곳이다.
  • Reference to the Outer Lexical Environment
    해당 함수 실행 컨텍스트 한단계 외부에 있는 Environment Record와 연결을 하는 요소다.

2.2 실행단계

생성단계 이후에는 선언문 외 나머지 코드를 순차적으로 실행한다. 해당 단계를 실행단계(Execution Phase)라고 한다.

실행단계에서는 필요한 경우 Lexical Environment를 참조하거나 재정의 한다.


모든 플로우를 마무리한 실행 컨텍스트는 Call Stack에서 Pop으로 제거되며 제어권은 제거된 실행컨텍스트의 한단계 아래에 있는 실행컨텍스트에게 넘어간다.

그럼 생성단계에서 함수 혹은 전역실행 컨텍스트 내부에 있는 변수에 대한 정의는 어떻게 하는걸까?



3. 실행 컨텍스트의 Environment Record, outer Lexical Environment 정의

실행컨텍스트를 생성할때 선언문을 먼저 실행하여 실행컨텍스트의 Environment Record에 정의한다.

이때 등장하는 개념이 바로 호이스팅(Hoisting)이다.


3.1 Environment Record

3.1.1 호이스팅(Hoisting)

JavaScript를 처음배우다 보면 호이스팅(Hoisting)개념에 대해서 얼핏 들어보거나 실제 코드 작성시에 호이스팅(Hoisting)으로 인한 다른언어에서는 볼 수 없었던 신기한 상황을 경험해봤을 것이다.

그럼 대체 호이스팅(Hoisting)이란 어떤 현상일까?

변수, 함수, 클래스, import를 선언하기 전에 접근이 가능한 개념으로, 코드의 최상단에 해당 변수나 함수가 최상위로 끌어올린것처럼 보이는 현상이다.


3.1.2 호이스팅 예제

변수 호이스팅

var

var는 선언과 동시에 초기화가 이뤄지는 변수타입이다.

console.log(fruit);

var fruit = "apple";

console.log(fruit);

  1. 전역 실행컨텍스트가 생성되어서 Call Stack에 push 된다.
  2. 실행 컨텍스트가 생성될 때 선언문을 먼저 살펴보고 선언문이 존재한다면 해당 선언문을 Environment Record에 정의한다.
    • 바로 이 부분에서 일어나는 작업을 호이스팅(Hoisting)이라고 한다.
    • 선언을 하면 메모리에 올라간다.
  3. var일 경우 해당 선언문은 선언과 동시에 초기화(undefined)가 된다.
  4. 선언문 탐색이 끝나고 실행단계에서 특정 변수에 대한 할당문을 만난다면 해당 변수의 값을 재정의 해준다.

정리하자면 var는 실행컨텍스트를 생성하는 생성단계에서 선언문 탐색이 발생하고 탐색을 통하여 찾은 var는 선언과 동시에 초기화가 되므로 위와같이 "undefined"값이 출력된 것이고 할당문을 통해서 해당 값을 재정의하여 "apple"이라는 값이 출력되는 것이다.



const, let

const,let은 선언만 이뤄지는 변수타입이다.

console.log(fruit);

const fruit = "apple"

console.log(fruit);


1. 전역 실행컨텍스트가 생성되어서 Call Stack에 push 된다.
2. 실행 컨텍스트가 생성될 때 선언문을 먼저 살펴보고 선언문이 존재한다면 해당 선언문을 Environment Record에 정의한다.
3. const,let일 경우 해당 선언문은 선언만 하여 메모리에 올라가고 초기화하지 않는다.

위 예시를 보면 const로 선언한 변수가 값까지 정의되기 전에 console.log함수를 통해 호출하여서 에러가 발생한걸 볼 수 있다.

해당 에러의 내용은 "fruit"변수를 초기화 하기 전에는 접근할 수 없다는 에러이다.
정리하자면 const,let은 실행컨텍스트를 생성하는 생성단계에서 선언문 탐색이 발생하고 탐색을 통하여 찾은 const, let은 선언만 이뤄져서 메모리에 올라가고 초기화는 하지 않으므로 위와같이 에러가 발생할 것이다.


결국 var, let, const 3개의 변수타입은 모두 호이스팅이 일어난다.

다만, var은 선언과 동시에 초기화가 일어나서 선언전에 호출하여도 에러 발생없이 "undefined"값에 접근하는 것이고

let,const은 선언만 이뤄지고 초기화를 하지 않으므로 선언전에 호출하면 "초기화 하지 않은 변수에 접근할 수 없다."라는 에러가 발생하는 것이다.



함수 호이스팅

함수 선언문

함수 선언문은 선언과 동시에 함수가 생성되어 선언 전에도 함수사용이 가능하다.

callFruit();

function callFruit(){
	console.log("apple");
};

  1. 전역 실행컨텍스트가 생성되어서 Call Stack에 push 된다.
  2. JavaScript 엔진이 함수 선언문으로 정의한 함수의 선언과 동시에 완성된 객체를 생성하여 Environment Records에 정의한다.

즉, 실행 컨텍스트의 생성단계에서 선언문 탐색을 할 때 Environment Records함수 표현식으로 정의한 함수를 선언과 동시에 할당까지 진행한다.

그래서 해당 함수를 코드상의 선언전에 호출해도 에러없이 동작하는 것이다.



함수 표현식

함수표현식varlet, const에 의해 다르게 정의된다.

JavaScript에서는 함수를 특정 변수에다가 정의할 수 있다.


var
변수 호이스팅과 마찬가지로 var함수 표현식은 선언과 동시에 "undefined"로 초기화만 된다.

callFruit();

var callFruit = () => {
  console.log("apple");
};


var는 선언과 동시에 초기화를 "undefined"값으로 하기 때문에 선언전에 해당 함수를 실행하려고 하면 "함수가 아닌데 왜 함수실행문법을 쓰는거야?"라는 에러가 발생한다.



const, let

변수 호이스팅과 마찬가지로 const, let함수 표현식은 선언만 된다.

callFruit();

let callFruit = () => {
  console.log("apple");
};

const, let은 선언만 하고 초기화를 하지 않으므로 해당 함수를 선언 전에 실행하려고 하면 "정의되지 않은 변수를 왜 호출하는거야?"라는 에러가 발생한다.

결국 호이스팅은 var에만 일어나는것도 아니고 특정 변수타입에만 일어나는게 아닌 변수, 함수, 클래스 등의 선언은 모두 호이스팅이 되어 먼저 Environment Records에 선언된다.

그 후, 각 타입이나 혹은 정의방법에 따라서 초기화가 일어나고 혹은 할당까지 하는 프로세스를 거치게 되는 것이다.


3.2 Outer Lexical Environment

Outer Lexical Environment는 해당 함수 실행 컨텍스트보다 한단계 위의 실행컨텍스트를 참조하는 요소이다.

const fruit = "apple";

function func1() {
  function func2() {
    function func3() {
      console.log(fruit);
    }
    func3();
  }
  func2();
}

func1();

위와같이 "func3"에서 "fruit"를 호출하려고 했지만 "func3"실행 컨텍스트 내부에 "fruit"변수가 정의되어 있지 않다면 해당 상위 실행 컨텍스트(func2)로 가서 찾아본다.

만약, 상위 실행 컨텍스트에도 없다면 한단계 더 위인 실행 컨텍스트(func1)에 가서 해당 변수를 찾아본다.

즉, 자신의 실행 콘텍스트에 호출하려는 변수가 존재하지 않다면 상위 컨텍스트로 가서 찾는 것이다.

변수나 함수의 값을 결정해내는 것을 식별자 결정이라고 한다.

const fruit = "apple";

function func1() {
  function funcSpecial() {
    console.log("hello world");
  }
  function func2() {
    function func3() {
      funcSpecial();
    }
    func3();
  }
  func2();
}

func1();

변수뿐만 아니라 함수, 클래스 등등을 상위 실행컨텍스트로 가서 찾을 수 있다.

위와같이 식별자를 결정하기 위해서 위로 타고타고 올라가 찾는 과정 자체를 스코프 체이닝(Scope Chaining)이라고 한다.

스코프 체이닝(Scope Chaining) : 식별자를 결정할 때 활용하는 스코프들의 연결리스트를 통해 식별자를 찾는 과정


또한 식별자를 결정할 때 활용하는 스코프들의 연결리스트를 스코프 체인(Scope Chain)이라고 한다.

스코프 체인(Scope Chain) : 식별자를 결정할 때 활용하는 스코프들의 연결리스트



4. 주의할 점

  1. 만약 동일한 함수를 여러번 실행하게 된다면 실행 컨텍스트는 어떻게 Call Stack에 쌓일까?
  • 동일한 이름이라고 해서 하나의 실행 컨텍스트만 생성돼서 Call Stack에 쌓이는게 아닌 동일한 함수 호출이여도 호출마다 독립적인 실행 컨텍스트를 생성하여서 Call Stack에 push한다.

  1. 위와같이 2개의 함수가 전역 실행 컨텍스트에 선언되어 있을때는 어떻게 동작할까?

    ```
    let fruit = "apple";
    
    function func1() {
      let fruit = "banana";
      func2();
    }
    
    function func2() {
      console.log(fruit); // "apple"
    }
    
    func1();
    ```

    여기서 알아야 할 점은 외부 렉시컬 환경 즉, 자신의 실행 컨텍스트 보다 상위에 있는 실행컨텍스트의 환경은 함수가 실행되는 시점이 아니라 선언된 시점의 외부 환경을 가르킨다는 점이다.

즉, 함수가 실행되는 시점이 아닌 선언된 시점이므로 "func2"의 외부 렉시컬 환경은 "func1"을 가르키는게 아닌 전역 렉시컬 환경을 참조하고 있다.

만약 JavaScript가 Dynamic Scope를 따랐다면 "banana"가 출력됐지만 JavaScript는 Lexical Scope(Static Scope)를 따르기 때문에 "apple"값이 출력된 것이다.

Dynamic Scope : 함수가 어디서 호출되었는지에 따라 스코프가 정해지는 것
Lexical Scope(Static Scope) : 함수를 어디서 선언했는지에 따라 상위 스코프가 결정되는 것



5. 마치며

사실 실행 컨텍스트 개념에 대해서는 대충 알고 있었는데 파고파고 하다보니 어느새 딥다이브한 개념이란걸 느낀 포스팅 주제 였다

사실 호이스팅에 관한 개념도 어떤식으로 돌아가는지 모르고 var, let, const에 따라 호이스팅의 작용과 접근 범위에 대해서만 알고 있었는데 실행컨텍스트랑 관련이 있다는걸 뒤늦게 안 내 자신 반성한다..



6. Reference

https://www.youtube.com/watch?v=EWfujNzSUmw
https://kwangsunny.tistory.com/37
https://developer.mozilla.org/ko/docs/Glossary/Hoisting

profile
개발진행형

0개의 댓글