실행 컨텍스트 & 호이스팅 & TDZ

Tori·2024년 12월 8일
post-thumbnail

자바스크립트에서 실행 컨텍스트와 실행 스택 이해하기 - Medium




학습 목표

  • 실행 컨텍스트
  • 호이스팅
  • this
  • let, const 키워드와 블록 레벨 스코프
  • TDZ




실행 컨텍스트(execution context)

실행 컨텍스트는 실행할 코드에 제공할 환경 정보들을 모아놓은 객체로, 자바스크립트의 동적 언어로서의 성격을 가장 잘 팔악할 수 있는 개념이다.

  • 자바스크립트는 어떤 실행 컨텍스트가 활성화되는 시점에 선언된 변수를 위로 끌어올린다. (호이스팅)
  • 외부 환경 정보 구성
  • this 값 설정



실행 컨텍스트란?

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



동일한 환경이란?

: 하나의 실행 컨텍스트를 구성할 수 있는 방법

  • 전역 공간
  • 함수
  • eval() 함수

자동으로 생성되는 전역공간과 😈 악마로 취급받은 eval을 제외하면
흔히 실행 컨텍스트를 구성하는 방법은 함수를 실행하는 것 뿐이다.



코드를 통해 실행 컨텍스트와 콜 스택 살펴보기

1 // ----------------------------------- (1)
2    var a = 1;
3    function outer() {
4      function inner() {
5        console.log(a); // undefined
6        var a = 3;
7      }
8      inner(); // ---------------------- (2)
9      console.log(a); // 1
10   }
11   outer(); // ------------------------ (3)
12   console.log(a); // 1
  1. 처음 자바스크립트 코드를 실행하는 순간(1) 전역 컨텍스트가 콜 스택에 담긴다. 최상단의 공간은 코드 내부에서 별도의 실행 명령이 없어도 브라우저의 자바스크립트 엔진에서 script 요소를 만나는 시점(자바스크립트 파일이 열리는 순간)에 자동으로 실행하므로 전역 컨텍스트가 활성화된다고 이해하면 된다.




  1. (3)에서 outer 함수를 호출하면 자바스크립트 엔진은 outer에 대한 환경 정보를 수집해서 outer 실행 컨텍스트를 생성한 후 콜 스택에 담는다.
    콜 스택의 맨 위에 outer 실행 컨텍스트가 놓인 상태라서 전역 컨텍스트와 관련된 코드의 실행을 일시 중단하고 outer 실행 컨텍스트와 관련된 outer 함수 내부의 코드들을 순차적으로 실행한다.




  1. outer 함수의 실행 컨텍스트를 순차적으로 실행하면서 inner 함수가 호출되는 (2)에서 inner 함수의 실행 컨텍스트가 콜 스택의 가장 상단에 담기게 된다. 이때 outer 컨텍스트와 관련된 코드의 실행은 중단되고, inner 함수 내부의 코드를 순서대로 진행하게 된다.




  1. inner 함수 내부에서 변수 a에 값 3을 할당하고 나면 inner 함수의 실행이 종료되면서 inner 실행 컨텍스트가 콜 스택에서 제거된다.




  1. 이제 outer 컨텍스트가 콜스택의 맨 위에 존재하고 중단됐던 (2)의 다음 줄부터 이어서 실행된다.
    a 변수의 값을 콘솔로 출력하고나면 outer 함수의 실행이 종료되고 outer 실행 컨텍스트도 콜 스택에서 제거된다.




  1. 전역 컨텍스트만이 콜 스택에 남아있고, 실행을 중단했던 (3)의 다음 줄부터 이어서 실행된다.
    a 변수의 값을 콘솔로 출력하고나면 전역 공간에는 더는 실행할 코드가 없어서 전역 컨텍스트도 제거되고, 콜 스택에는 아무것도 남지 않은 상태로 종료된다.




이렇게 어떤 실행 컨텍스트가 활성화될 때 자바스크립트 엔진은 해당 컨텍스트에 관련된 코드들을 실행하는 데 필요한 환경 정보들을 수집해서 실행 컨텍스트 객체에 저장한다.



실행 컨텍스트 생성 로직

위에서는 자바스크립트 엔진에서 어떻게 실행 컨텍스트를 관리하고 동작하는지에 대해 알아보았다면 이번에는 엔진이 실행 컨텍스트를 만드는 과정에 대해서 알아볼 것이다.


1. 생성 단계 (Cration Phase)

실행 컨텍스트는 생성 단계에서 생성되는데 이 단계에서는 두 가지 일이 일어난다.

  • Variable Environment 구성 요소 생성
  • Lexical Environment 구성 요소 생성

실행 컨텍스트는 개념적으로 아래와 같이 나타낼 수 있다.

ExecutionContext = {
  VariableEnvironment = <ref. to VariableEnvironment in memory>, // 메모리의 VariableEnvironment를 참조
  LexicalEnvironment = <ref. to LexicalEnvironment in memory>, // 메모리의 LexicalEnvironment를 참조
}

활성화된 실행 컨텍스트의 수집 정보


1.1 Variable Environment

Variable Environment에 담기는 내용은 Lexical Environment와 같지만 최초 실행 시의 스냅샷을 유지한다.

실행 컨텍스트를 생성할 때 Variable Environment에 정보를 먼저 담은 다음 이를 그대로 복사해서 Lexical Environment를 만들고, 이후에는 Lexical Environment를 주로 활용하게 된다.

Variable Environment와 Lexical Environment의 내부에는 environmentRecord와 outerEnvironmentReference로 구성되어있다.

초기화 과정 중에는 사실상 완전히 동일하고, 이후 코드 진행에 따라 서로 달라지게 된다.

실행 컨텍스트를 생성할 때는 VariableEnvironment와 LexicalEnvironment가 동일한 내용으로 구성되지만 LexicalEnvironment는 함수 실행 도중에 변경되는 사항이 즉시 반영되는 반면 VariableEnvironment는 초기 상태를 유지한다.

ES6에서 VariableEnvironment와 LexicalEnvironment의 한 가지 차이점

VariableEnvironment는 변수 var 바인딩만 저장하는 데 사용하고, LexicalEnvironment는 함수 선언과 변수(let, const) 바인딩을 저장하는 데 사용된다.


1.2 Lexical Environment

Lexical Environment는 '현재 컨텍스트 내부에는 a, b, c와 같은 식별자들이 있고 그 외부 정보는 D를 참조하도록 구성돼있다'와 같이 컨텍스트를 구성하는 환경 정보들을 사전에서 접하는 느낌으로 모아놓은 것이라고 생각하면된다.

쉽게 말해 식별자(identifier)-변수(variable) 매핑(mapping)을 저장하는 구조라고 생각하면 된다.

식별자
: 변수/함수의 이름을 의미하며

변수
: 실제 객체(함수 객체 및 배열 객체 포함) 도는 primitive 값에 대한 참조를 의미한다.


코드를 통해 자세히 알아보기

var a = 20;
var b = 40;
function foo() {
  console.log('bar');
}

위에 코드의 LexicalEnvironment는 다음과 같다.

LexicalEnvironment = {
  a: 20,
  b: 40,
  foo: <ref. to foo function>
}

LexicalEnvironment에는 세 가지 구성 요소가 있다.
1. EnvironmentRecord
2. outerEnvironmentReference
3. This Binding

EnvironmentRecord

Lexical Environment안에 함수와 변수 선언을 저장하는 곳

> Declarative environment record

: 변수 및 함수 선언을 저장한다. 함수 코드의 lexical environment에는 선언전 환경 레코드가 포함되어 있다.

> Object environment record

: 객체 환경 레코드가 포함되어있다. 객체 환경 레코드에는 변수 및 함수 선언 외에도 전역 바인딩 객체(브라우저의 경우 창 객체)도 저장된다.
따라서 바인딩 객체의 각 속성(브라우저의 경우 브라우저 창 객체에 제공하는 속성 및 메서드가 포함됨)에 대해 레코드에 새 항목이 생성된다.



environmentRecord와 호이스팅

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

컨텍스트를 구성하는 함수에 지정된 매개변수 식별자, 선언한 함수가 있을 경우 그 함수 자체(함수 선언), var로 선언된 변수의 식별자 등이 식별자에 해당한다.

컨텍스트 내부 전체를 처음부터 끝까지 쭉 훑어나가며 순서대로 수집한다.

변수 정보를 수집하는 과정을 모두 마쳤지만 아직 실행 컨텍스트가 관여할 코드들은 실행되기 전의 상태이다.
코드가 실행되기 전이지만 자바스크립트 엔진은 이미 해다 환경에 속한 코드의 변수명들을 알고있게 되는 셈이다.

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





outerEnvironmentReference

외부 환경에 대한 참조는 외부 lexical environment에 접근할 수 있음을 의미한다.
즉, 자바스크립트 엔진은 현재 lexical environment에서 변수를 찾을 수 없는 경우 outer environment 내부에서 변수를 찾을 수 있다.



outerEnvironmentReference와 클로저

클로저: 비공개 변수/자유 변수를 가질 수 있는 환경에 있는 함수

비공개 변수/자유 변수는 클로저 함수 내부에 생성한 변수가 아니고, 매개변수도 아닌 변수를 의미한다.

클로저를 말할 때는 스코프/컨텍스트/비공개 변수와 함수의 관계를 항상 같이 말해야 된다.


클로저에 대해서는 더 자세하게 작성한 페이지를 분리하려고 합니다. 글이 작성되면 여기 클로저 글 링크를 달겠습니다.





This

실행 컨텍스트의 thisBinding에는 this로 지정된 객체가 저장된다.

실행 컨텍스트 활성화 당시에 this가 지정되지 않은 경우 this에는 전역 객체가 저장된다.

그 밖에는 함수를 호출하는 방법에 따라 this에 저장되는 대상이 다르다.

상황에 따라 달라지는 This

  • 전역 공간에서의 this(브라우저 환경 - window, nodejs 환경 - global)
  • 메서드로서 호출할 때
  • 함수로서 호출할 때
  • 콜백 함수 호출 시
  • 생성자 함수 내부
const person = {
  name: 'peter',
  birthYear: 1994,
  calcAge: function() {
    console.log(2018 - this.birthYear);
  }
}
person.calcAge(); // ------------------ (1)
const calculateAge = person.calcAge;
calculateAge(); // -------------------- (2)

(1)에서는 caclAge가 person 객체 참조로 호출(메서드로서 호출)되었으므로 this는 person 객체를 나타낸다.

(2)에서는 this 객체 참조가 지정되지 않았으므로(함수로서 호출) 전역 객체(window)를 참조한다.



추상적으로 lexical environment는 의사 코드(pseudocode)에서 다음과 같이 보인다.

// 글로벌 실행 컨텍스트
GlobalExecutionContext = {
  LexicalEnvironment: {
    EnvironmentRecord: {
      Type: "Object"
      // 식별자 바인딩
    },
    OuterEnvironmentReference: <null>,
    ThisBinding: <global object>
  }
}
// 함수 실행 컨텍스트
FunctionExecutionContext = {
  LexicalEnvironment: {
    EnvironmentRecord: {
      Type: "Declarative",
      // 식별자 바인딩
    },
    OuterEnvironmentReference: <global or outer function environment reference>, // 글로벌 또는 외부 함수 환경 참조
    ThisBinding: <depends on how function is called> // 함수 호출 방식에 따라 다름
  }
}

This에 대해서는 더 자세하게 작성한 페이지를 분리하려고 합니다. 글이 작성되면 여기 This 글 링크를 달겠습니다.




2. 실행 단계 (Execution Phase)

실행 단계에서는 모든 변수에 대한 할당이 완료되고 코드가 최종적으로 실행된다.

코드를 통해 실행 컨텍스트의 2단계(Creation, Execution) 알아보기

let a = 20;
const b = 30;
var c;

function multiply(e, f) {
 var g = 20;
 return e * f * g;
}

c = multiply(20, 30);

Creation Phase

위의 코드가 실행되면 자바스크립트 엔진은 전역 코드를 실행하기 위해 전역 실행 컨텍스트를 생성한다.
따라서 생성 단계에서 글로벌 실행 컨텍스트는 다음과 같은 모양이 된다.

GlobalExecutionContext = {
  LexicalEnvironment: {
    EnvironmentRecord: {
      Type: "Object",
      // 식별자 바인딩
      a: <uninitalized>, // 초기화되지 않음
      b: <uninitalized>,
      multiply: <func>
    },
    OuterEnvironmentReference: <null>,
    ThisBinding: <Global Object>
  },
  VariableEnvironment: {
    EnvironmentRecord: {
      Type: "Object",
      // 식별자 바인딩
      c: undefined,
    },
    OuterEnvironmentReference: <null>,
    ThisBinding: <Global Object>
  }
}



Execution Phase

실행 단계에서는 변수 할당이 수행된다. 따라서 실행 단계에서 글로벌 실행 컨텍스트는 다음과 같이 보인다.

GlobalExecutionContext = {
  LexicalEnvironment: {
    EnvironmentRecord: {
      Type: "Object",
      // 식별자 바인딩
      a: 20,
      b: 30,
      multiply: <func>
    },
    OuterEnvrinomentReference: <null>, 
    ThisBingding: <Global Object>
  },
  VariableEnvironment: {
    EnvironmentRecord: {
      Type: "Object",
      // 식별자 바인딩
      c: undefined,
    },
    OuterEnvironmentReference: <null>,
    ThisBinding: <Global Object>
  }
}



multiply(20, 30) 함수에 대한 호출이 발생하면 함수 코드를 실행하기 위해 새 함수 실행 컨텍스트가 생성된다. 따라서 함수 실행 컨텍스트는 생성 단계에서 다음과 같은 모양이 된다.

FunctionExecutionContext = {
  LexicalEnvironment: {
    EnvironmentRecord: {
      Type: "Declarative", // 선언적
      // 식별자 바인딩
      Arguments: {0, 20, 1: 30, length: 2}
    },
    OuterEnvironmentReference: <GlobalLexicalEnvironment>,
    ThisBinding: <Global Object or undefined>, // 전역 객체 또는 정의되지 않음
  },
  VariableEnvironment: {
    EnvironmentRecord: {
      Type: "Declarative",
      // 식별자 바인딩
      g: undefined
    },
    OuterEnvironmentReference: <GlobalLexicalEnvironment>,
    ThisBinding: <Global Object or undefined>
  }
}



그 후 실행 컨텍스트는 실행 단계를 거치며, 이는 함수 내부의 변수에 대한 할당이 완료됨을 의미한다. 따라서 실행 단계에서 함수 실행 컨텍스트는 다음과 같이 보인다.

FunctionExecutionContext = {
  LexicalEnvironment: {
    EnvironmentRecord: {
      Type: "Declarative",
      // 식별자 바인딩
      Arguments: {0: 20, 1: 30, length: 2},
    },
    OuterEnvrinonemtReference: <GlobalLexicalEnvironment>,
    ThiaBinding: <Global Object or undefined>
  },
  VariableEnvironment: {
    EnvironmentRecord: {
      Type: "Declarative",
      // 식별자 바인딩
      g: 20,
    },
    OuterEnvrinonemtReference: <GlobalLexicalEnvironment>,
    ThiaBinding: <Global Object or undefined>
  }
}

함수가 완료된 후 반환된 값은 변수 c에 저장되므로 GlobalLexicalEnvironment가 업데이트된다.
그 후 GlobalExecutionContext가 완료되고 프로그램이 종료된다.



참고) 생성 단계(creation phase)에서 한 가지 살펴보기 - var, let, const

생성 단계에서 letconst 로 정의된 변수에는 연결된 값이 없지만, var 로 정의된 변수에는 undefined가 할당된 것을 볼 수 있다.

이는 생성 단계에서 코드에 변수 및 함수 선언이 있는지 스캔하는 동안 함수 선언은 환경에 전체가 저장되는 반면, 변수는 처음에 var의 경우 undefined로 설정되거나 let, const는 초기화되지 않은 상태로 유지되기 때문이다.

이것이 바로 선언되기 전(정의되지는 않았지만)에 var 정의 변수에 접근할 수 있지만, 선언되기 전에 let 및 const 변수에 접근할 때 참조 오류(reference error)가 발생하는 이유이다. 이를 호이스팅이라고 한다.

참고) 실행 단계(execution phase)에서 한 가지 살펴보기

실행 단계에서 자바스크립트 엔진이 소스 코드에서 실제 선언된 위치에서 let 변수의 값을 찾을 수 없는 경우, undefined를 할당한다.







호이스팅

변수 선언의 실행 시점과 변수 호이스팅

변수 선언문보다 변수를 참조하는 코드가 앞에 있다.

console.log(str); // undefined

var str; // 변수 선언문

자바스크립트 코드는 인터프리터에 의해 한 줄씩 순차적으로 실행된다.

console.log(str);가 실행되는 시점에선 str 변수의 선언이 실행되지 않았으므로 참조에러(ReferenceError)가 발생할 것처럼 보이지만, undefined가 출력된다.

그 이유는 변수 선언이 소스코드가 한 줄씩 순차적으로 실행되는 시점, 즉 런타임(runtime)이 아니라 그 이전 단계에서 먼저 실행되기 때문이다.

자바스크립트 엔진은 소스코드를 한 줄 씩 순차적으로 실행하기에 앞서 먼저 소스코드의 평가 과정을 거치며 소스코드를 실행하기 위한 준비를 한다.

이때 소스코드 실행을 위한 준비 단계인 소스코드의 평가 과정에서 자바스크립트 엔진은 변수 선언을 포함한 모든 선언문(변수 선언문, 함수 선언문 등)을 소스코드에서 찾아내 먼저 실행한다. 그리고 소스코드의 평가 과정이 끝나면 비로소 변수 선언을 포함한 모든 선언문을 제외하고 소스코드를 한 줄씩 순차적으로 실행한다.

호이스팅(variable hoisting)이란?

  • 변수를 선언하고 초기화했을 때 선언 부분이 최상단으로 끌어올려지는 현상을 의미한다. (초기화 또는 대입 부분은 그대로 남아있다.)

  • 함수 선언식일 때는 식 자체가 통째로 끌어올려진다.





let, const, 블록 레벨 스코프


var에 대해 알아보기

var 키워드로 선언한 변수의 문제점

var 키워드로 선언한 변수는 같은 스코프 내에서 중복 선언을 허용한다.

var x = 100;
var y = 7;

// 초기화 문이 있는 변수 선언문은 자바스크립트 엔진에 의해 var 키워드가 없는 것처럼 동작한다.
var x = 500;

// 초기화 문이 없는 변수 선언문은 무시된다.
var y;

console.log(x); // 500
console.log(y); // 7

var 키워드로 선언한 변수를 중복서언하면 초기화문(변수 선언과 동시에 초기값을 할당하는 문) 유무에 다라 다르게 동작한다.

  • 초기화문이 있는 변수 선언문은 자바스크립트 엔진에 의해 var 키워드가 없는 것처럼 동작한다.
  • 초기화문이 없는 변수 선언문은 무시된다. (에러 발생 안함)



함수 레벨 스코프

var 키워드로 선언한 변수는 오로지 함수의 코드 블록만을 지역 스코프로 인정한다.

따라서 함수 외부에서 var 키워드로 선언한 변수는 코드 블록 내에서 선언해도 모두 전역 변수가 된다.

var x = 1;

if (true) {
  // x는 전역 변수
  // 이미 선언된 전역 변수 x가 있으므로 x 변수는 중복 선언된다.
  var x = 100;
}

console.log(x); // 100

<br/>
  

for 문의 변수 선언문에서 var 키워드로 선언한 변수도 전역 변수가 된다.

var i = 10;

// for문에서 선언한 i는 전역 변수다. 이미 선언된 전역 변수 i가 있으므로 중복 선언된다.
for (var i = 0; i < 5; i++) {
  console.log(i); // 0 1 2 3 4
}

// 의도치 않게 i 변수의 값이 변경되었다.
console.log(i); // 5

함수 레벨 스코프는 전역 변수를 남발할 가능성을 높인다. 이로 인해 의도치 않게 전역 변수가 중복 선언되는 경우가 발생한다.


변수 호이스팅

var 키워드로 변수를 선언하면 변수 호이스팅에 의해 변수 선언문이 스코프의 선두로 끌어 올려진 것처럼 동작한다.

즉, 변수 호이스팅에 의해 var 키워드로 선언한 변수는 변수 선언문 이전에 참조할 수 있다.

단, 할당문 이전에 변수를 참조하면 undefined를 반환한다.

-> 변수 선언문 이전에 변수를 참조하는 것은 변수 호이스팅에 의해 에러를 발생시키지는 않지만, 프로그램의 흐름상 맞지 않고 가독성을 떨어뜨리며 오류를 발생할 여지를 남긴다.



let에 대해 알아보기

var 키워드의 단점을 보완하기위해 ES6에서는 새로운 변수 선언 키워드인 letconst를 도입했다.

var 키워드와의 차이점을 중심으로 let 키워드를 살펴보자.

변수 중복 선언 금지

var 키워드로 이름이 동일한 변수를 중복 선언하면 에러는 발생하지 않지만, 변수를 중복 선언하면서 값까지 할당했다면 의도치 않게 먼저 선언된 변수 값이 재할당되어 변경되는 부작용이 발생한다.

하지만 let 키워드로 이름이 같은 변수를 중복 선언하면 문법 에러(SyntaxError)가 발생한다.

let bar = 300;
let bar = 400; // Uncaught SyntaxError: Identifier 'bar' has already been declared



블록 레벨 스코프

var 키워드로 선언한 변수는 오로직 함수의 코드 블록만을 지역 스코프로 인정하는 함수 레벨 스코프를 따른다.

하지만 let 키워드로 선언한 변수는 모든 코드 블록(함수, if문, while 문, try/catch 문 등)을 지역 스코프로 인정하는 block-level scope를 따른다.

let foo = 1; // 전역 변수

{
  let foo = 2; // 지역 변수
  let bar = 3; // 지역 변수
}

console.log(foo); // 1
console.log(bar); // Uncaught ReferenceError: bar is not defined



TDZ(Temporal Dead Zone)

변수 호이스팅

var 키워드로 선언한 변수와 달리 let 키워드로 선언한 변수는 호이스팅이 발생하지 않는 것처럼 동작한다.

console.log(foo); // Uncaught ReferenceError: foo is not defined
let foo;

이처럼 let 키워드로 선언한 변수를 변수 선언문 이전에 참조하면 참조 에러(ReferenceError)가 발생한다.



var 변수 선언

var 키워드로 선언한 변수는 런타임 이전에 자바스크립트 엔진에 의해 암묵적으로 "선언 단계"와 "초기화 단계"가 한 번에 진행된다.
즉, 선언 단계에서 스코프(실행 컨텍스트의 Lexical Environment)에 변수 식별자를 등록해 자바스크립트 엔진에 변수의 존재를 알린다. 그리고 즉시 초기화 단계에서 undefined로 변수를 초기화한다.
따라서 변수 선언문 이전에 접근(참조)해도 스코프에 변수가 존재하기 때문에 에러가 발생하지 않는다. 다만 undefined를 반환하고, 변수 할당문에 도달하면 비로소 값이 할당된다.

// var 키워드로 선언한 변수는 런타임 이전에 선언 단계와 초기화 단계가 실행된다.
console.log(foo); // undefined

var foo;
console.log(foo); // undefined

foo = 1; // 할당문에서 할당 단계가 실행
console.log(foo);



let 키워드로 선언한 변수는 "선언 단계"와 "초기화 단계"가 분리되어 진행된다.


즉, 런타임 이전에 자바스크립트 엔진에 의해 암묵적으로 선언 단계가 먼저 실행되지만 초기화 단계는 변수 선언문에 도달했을 때 실행된다.

만약 초기화 단계가 실행되기 이전에 변수에 접근하려고 하면 참조 에러(ReferenceError)가 발생한다.

let 키워드로 선언한 변수는 스코프의 시작 지점부터 초기화 단계 시작 지점(변수 선언문)까지 변수를 참조할 수 없다.

스코프의 시작 지점부터 초기화 시작 지점까지 변수를 참조할 수 없는 구간을 일시적 사각지대(TDZ: Temporal Dead Zone)이라고 부른다.

// 런타임 이전에 선언 단계 실행, 아직 변수는 초기화되지 않음
// 초기화 이전에 일시적 사각지대에서는 변수를 참조할 수 없다.
console.log(foo); // Uncaught ReferenceError: foo is not defined

let foo; // 변수 선언문에서 초기화 단계가 실행
console.log(foo); // undefined

foo = 1; // 할당문에서 할당 단계가 실행
console.log(foo); // 1


let 키워드로 선언한 변수는 변수 호이스팅이 발생하지 않는다?

let 키워드로 선언한 변수는 호이스팅이 발생하지 않는 것처럼 보이지만 그렇지 않다.

let foo = 1; // 전역 변수

{
  console.log(foo); // ReferenceError: Cannot access 'foo' before initialzation
  let foo = 100; // 지역 변수
}

let 키워드로 선언한 변수의 경우 변수 호이스팅이 발생하지 않는다면 위 예제코드에서 전역 변수 foo 값을 출력해야 한다.
하지만 let 키워드로 선언한 변수도 여전히 호이스팅이 발생하기 때문에 참조 에러(ReferenceError)가 발생한다.

자바스크립트는 ES6에서 도입된 let, const를 포함해서 모든 선언(var, let, const, function, function*, class 등)을 호이스팅한다.

단 ES6에서 도입된 let, const, class를 사용한 선언문은 호이스팅이 발생하지 않는 것처럼 동작한다.
그 이유는 변수의 생명주기와 관련되어있다.

변수의 생명주기

var: 실행 컨텍스트의 생성 단계에서 호이스팅이 일어나면서 undefined로 초기화된다.

let, const: 실행 컨텍스트의 실행 단계에서 호이스팅은 일어나지만 초기화가 되어있지 않고, 초기화문을 만날 때 그때 초기화되고, 선언만 됐을경우 undefined로 초기화된다.





profile
🌿

0개의 댓글