[Javascript] 실행 컨텍스트와 렉시컬 환경 / 호이스팅이 발생하는 이유

kaya·2023년 11월 30일

Javascript

목록 보기
11/13
post-thumbnail

실행 컨텍스트(Execution Context)

  • 현재 실행 중인 코드에 대한 세부 정보(제어 흐름의 위치, 선언된 변수, 함수, this, arguments 등)을 담고 있는 데이터 구조
  • 함수 실행, 호이스팅, 렉시컬 환경, 클로저 같은 개념을 관통하는 큰 개념

➡️ 현재 실행 중인 코드에 대한 정보를 담고 있음

실행 컨텍스트의 종류

  • global execution context : 전체 스크립트에 대한 실행 컨텍스트
    • 전체 스크립트가 실행될 때 call stackpush
  • Function execution context : 호출된 함수에 대한 실행 컨텍스트
    • 함수가 호출될 때 call stackpush
    • 그래서 재귀함수와 같이 함수를 많이 호출하면 함수가 호출될 때마다 실행 컨텍스트가 생기기 때문에 메모리를 많이 소요하고, 너무 많이 하면 Uncaught RangeError: Maximum call stack size exceeded 에러가 난다

실행 컨텍스트 생성과 실행

  • 바로 위에서 말했듯이 스크립트가 실행될 때 global execution context가 콜 스택에 push되고, 함수가 있다면 Function execution context로 콜 스택에 push된다
  • 함수의 경우 함수 실행이 완료되면 pop되고, global의 경우 앱이 종료되면 스택에서 pop된다
// 스크립트가 실행됨
// Global Execution Context가 call stack에 push됨
let name;  // 렉시컬 환경의 EnvironmentRecord에 name: <uninitialized>로 올라감
function func1(name) {
    console.log('hello');
    // func2 호출 => func2 실행 컨텍스트 생성, call stack에 push
  	func2(name); 
}

function func2(name) {
    console.log('world!')
}

name = 'kaya'  // EnvironmentRecord { name: 'kaya' }로 변경
// func1 호출 => 호출되었으니 Function Execution Context가 stack에 push됨
func1(name);  

// func2가 먼저 실행이 완료되면 pop
// 그 다음 func1이 실행이 완료되면 pop
// script 전체가 종료되면 global execution context도 pop

실행 컨텍스트의 내부

  • 실행 컨텍스트 안에 Variable Environment, Lexical Environment,ThisBinding가 있다

1. Lexical Environment

  • 내부에 EnvironmentRecord 객체, outerEnvironmentReference로 구성
  • 아래에서 더 자세히 설명하겠습니당

2. Variable Environment

  • 내부 내용은 Lexical Environment와 같음
  • but 최초 실행 시 스냅샷을 유지
    ➡️ 실행 컨텍스트 생성 시에 여기에 먼저 정보를 담고, 이걸 복사해서 Lexical Environment 생성
  • 주로 사용하는 것 ➡️ Lexical Environment
  • 최초 실행 시의 스냅샷을 찍는 용도 ➡️ Variable Environment

3. ThisBinding


렉시컬 환경(lexical environment)

실행 컨텍스트의 일부이기 때문에, 스크립트가 실행될 때, 함수가 호출될 때 각각 렉시컬 환경이 생성된다

렉시컬 환경의 특징

  • 이론상 객체로 직접 조작할 수 없음
  1. EnvironmentRecord 객체

    • 렉시컬 환경이 생성될 때 모든 변수, 함수 선언문으로 선언된 함수가 이 객체에 프로퍼티로 올라간다

    (1) 변수

    • var로 선언: 렉시컬 환경에 올라갈 때 변수명: undefined로 올라간다
    • let, const로 선언: 렉시컬 환경에 올라갈 때 변수명: <uninitialized>로 올라간다
      ➡️ 호이스팅이 발생하는 주요한 이유!

    (2) 함수

    • 함수를 호출해 실행하면 새로운 렉시컬 환경이 만들어짐 ➡️ 이 환경 안에 함수 호출 시 넘겨받은 매개변수, 함수의 지역 변수가 저장됨
    • 위의 예시에서 보면 스크립트가 실행될 때 아래처럼된다.
EnviormentRecord {
  name: <uninitialized>,
  func1: Function,
  func2: Function
}
  1. outerEnvironmentReference
  • 내부 렉시컬 환경이 가리키는 외부 렉시컬 환경을 뜻한다
  • 만약 찾는 변수, 함수 등이 내부 렉시컬 환경에 없으면 이 참조를 따라서 외부 렉시컬 환경에서 찾는다 ➡️ 검색 범위가 전역 렉시컬 환경으로 확장될 때까지 반복됨

호이스팅이 발생하는 이유

기본적으로 모든 변수와 함수에서 호이스팅이 발생한다. 다만 그 선언 방법에 따라 호이스팅이 안되는 것처럼 보이는 게 있다

예시 1: 호이스팅 발생

console.log(value)  // undefined

var value;

value = 'hello'
value = 'world'

console.log(value) // 'world'
  • var로 변수를 선언하면 렉시컬 환경의 EnvironmentRecordvalue: undefined의 형태로 올라간다

  • console.log(value) 코드를 실행할 때 valueundefined라는 프로퍼티값이 할당되어 있으므로 undefined라는 결과가 나오게 된다

  • 그리고 value = 'hello', value = 'world' 코드를 거치면서 EnvironmentRecord의 프로퍼티값이 'hello' ➡️ 'world'로 바뀐다

  • 함수 선언문으로 선언한 함수도 렉시컬 환경에 바로 올라가기 때문에 호이스팅되는 것이다

예시 2: 호이스팅 발생 x

console.log(value) // Uncaught ReferenceError: value is not defined

let value;

value = 'hello'
value = 'world'
  • let으로 변수를 선언하면 EnvironmentRecordvalue: <uninitialized> 형태로 올라간다
  • 이렇게 되어있으면 let을 만나기 전까지 이 변수를 참조할 수 없다
    ➡️ 그래서 ReferenceError가 나는 것이다

함수 표현식 비교(var로 선언 vs. let으로 선언)

  • 함수 표현식으로 선언하면 호이스팅이 되지 않기 때문에 var로 선언하든, let으로 선언하든 둘 다 에러가 난다
  • 하지만 에러 종류가 다르다
func4()  // TypeError: func4 is not a function
console.log(typeof func4)  // undefined

var func4 = function(){
    console.log('hello js');
}
func4()  // ReferenceError: Cannot access 'func4' before initialization

let func4 = function(){
    console.log('hello js');
}
  • var로 선언하면 undefined로 올라가기에 TypeError가 나는 것이고
  • let으로 선언하면 <uninitialized>로 올라가서 참조를 할 수가 없다는 ReferenceError가 발생하는 것이다

참고 페이지

profile
🏟 튼튼한 성은 튼튼한 벽돌로부터

0개의 댓글