실행 컨텍스트

김동영·2025년 3월 21일

자바스크립트

목록 보기
2/7
post-thumbnail

실행 컨텍스트란?

실행할 코드에 제공할 환경 정보들을 모아놓은 객체

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

실행컨텍스트에 담기는 정보들

  • VariableEnvironment : 현재 컨텍스트 내의 식별자들에 대한 정보 + 외부 환경정보 + 선언시점의 LexicalEnvironment의 스냅샷( 즉 변경사항 반영되지않음)
  • LexicalEnvironment: 처음에는 VariableEnvironment와 같지만 변경사항이 실시간으로 반영
  • ThisBinding: 식별자가 바라봐야할 대상 객체

VariableEnvironment 와 LexicalEnvironment

VariableEnvironmentLexicalEnvironment는 처음에는 같은 내용을 담고 있지만, VariableEnvironment는 변경되지 않는 반면 LexicalEnvironment는 실시간으로 변경됩니다.

VariableEnvironment 에 먼저 정보를 담은 다음 그대로 복사해서 LexicalEnvironment 를 생성하고 실행 시 활용합니다.

VariableEnvironmentLexicalEnvironment 의 내부에는 다음과 같이 구성되어 있습니다.

  • EnvironmentRecord : 현재 컨텍스트의 식별자 정보 저장
  • OuterEnvironmentReference: 외부 환경 정보 참조

VariableEnvironmentLexicalEnvironment를 분리할까?

  • ES6 이전에는 var만 존재했으며, VariableEnvironment에서 변수(var)와 함수 선언(function)을 관리하면 충분했습니다.
  • ES6 이후 letconst가 도입되면서 블록 스코프 가 추가되었습니다.
  • 하나의 실행 컨텍스트에서 여러 블록 스코프를 관리하려면, 실행 중에 변할 수 있는 환경(LexicalEnvironment)이 필요해졌습니다.

아래의 예시를 확인하겠습니다.

1. varlet/const의 스코프 차이

function example() {
    console.log(a); // undefined 
    console.log(b); // ReferenceError

    var a = 1;
    let b = 2;
}
example();
  • var함수 스코프이고, 호이스팅될 때 undefined로 초기화가됩니다.
  • let블록 스코프이고, TDZ(Temporal Dead Zone)가 존재해 접근 시 오류가 발생합니다.
  • 따라서 varVariableEnvironment, letconstLexicalEnvironment에서 관리해야 합니다.

여기서 잠깐! TDZ란?

letconst 변수는 선언되었지만 초기화되기 전까지 접근할 수 없는 구간으로 TDZ라고 합니다.
자바스크립트에서 letconstvar처럼 호이스팅되지만 초기화되지 않기 때문에 접근하면 ReferenceError가 발생하게 됩니다.

2. 블록 스코프 관리 필요성

function outer() {
    var x = 1;  // VariableEnvironment에 저장
    let y = 2;  // LexicalEnvironment에 저장

    if (true) {
        let y = 3;  // 새로운 LexicalEnvironment 생성(블록 스코프)
        console.log(y);  // 3
    }

    console.log(y);  // 2 (if 블록 내부의 y와 다름)
}
outer();
  • if 블록이 실행되면서 새로운 LexicalEnvironment가 생성됩니다.
  • 블록이 끝나면 if 블록의 LexicalEnvironment제거됩니다.
  • y의 값이 if 블록 안과 밖에서 다르게 유지됩니다.

여러 블록 스코프를 동적으로 관리하려면 LexicalEnvironment가 필요합니다.

EnvironmentRecord( 변수 및 함수 정보 저장)

environmentRecord 는 현재 컨텍스트와 관련된 코드의 식별자 정보들이 저장 되어있습니다

  • 컨텍스트를 구성하는 함수에 지정된 매개변수 식별자
  • 선언된 함수 자체
  • 변수의 식별자

변수정보를 수집하는 과정이 끝나더라도 실행 컨텍스트가 관여할 코드들은 실행되기 전 상태이기 때문에 코드가 실행되기 전에도 불구하고 자바스크립트엔진은 이미 해당 환경의 변수들을 모두 알고있는 것입니다.

즉 자바스크립트 엔진은 식별자들을 최상단으로 끌어올려놓은 다음 코드를 실행 한다라고 봐도 무방한것입니다.

이것이 호이스팅 입니다.

호이스팅 규칙

  • 변수 : 변수명만 끌어올리고 할당과정은 원래자리에서
  • 함수선언: 함수 전체를 끌어올림
  • 함수표현: 변수만 끌어올림

함수 선언문과 함수 표현문

아래와 같이 함수 선언문과 표현문을 표현했을때 호이스팅으로 인해 각각 다르게 동작합니다.

console.log(sum(1,2))
console.log(multiply(3,4))

function sum(a,b){
	return a+b;
}

var multiply= function (a,b){
	return a*b;
}

호이스팅이 된 상태

var sum = function sum(a,b){
	return a+b;
}

var multiply;
}

console.log(sum(1,2)) // 3
console.log(multiply(3,4))// ReferenceError

multiply= function (a,b){
	return a*b;
}

다음과 같이 함수 선언문은 자체로 호이스팅이 되지만 함수 표현식 은 변수만 호이스팅이 되어 함수 표현식에서 에러가 나오게 됩니다.

그렇다면 어떤것을 써야할까?

  • 함수 선언문을 쓰게 되면 물론 호이스팅이 되어 순서에 상관없이 사용할 수 있어서 편할 수 있다고 생각할 수 있지만 개발은 사람이 하는것 이기 때문에 선언된 후에 호출 할 수 있는 방법이 자연스러울 것 같습니다.
  • 또한 함수 선언문을 쓰게 되면 다음과 같은 문제가 발생 할 수 있습니다.

console.log(sum(3,4)) 

function sum(a,b){
	return a+b;
}

var a = sum(1,2)
console.log(a)
...

function sum(a,b){
	return a+'+'+b + ' = ' +(a+b);
}

var c = sum(1,4)
console.log(c)

다음과 같이 동일한 함수를 2개 선언해 버리면 호이스팅 때문에
마지막에 할당한 함수, 즉 마지막에 선언된 함수만 실행되어서 문제가 발생할 수 있습니다.

개발은 협업이기때문에 물론 잘 정해서 사용하면 될 수 있지만
이러한 부분을 사전에 차단하여 작성하는것도 중요하다고 생각합니다

스코프,스코프 체인, outerEnvironmentReference

스코프란?

식별자에 대한 유효범위
어느 범위에서 변수를 참조할 수 있는지를 결정합니다.
경계 밖에서 선언한 변수는 외부 뿐 아니라 내부에서도 접근이 가능하지만 경계 내부에서 선언한 변수는 내부에서만 접근이 가능합니다 .

스코프 체인과 outerEnvironmentReference

outerEnvironmentReference는 현재 호출된 함수가 선언될 당시의 LexicalEnvironment 를 참조합니다.

  • 코드상에서 어떤 변수에 접근하려고 하면
    현재 컨텍스트의 LexicalEnvironment 를탐색해서 발견되면 그값을 반환하고, 못하면 outerEnvironmentReference 에 담긴LexicalEnvironment 를 탐색합니다.
  • 전역 컨텍스트의 LexicalEnvironment 가지 없으면undefined 를 반환합니다.

스코프 체인 예제

var globalVar = "I am global";

function outer() {
    var outerVar = "I am outer";
    
    function inner() {
        var innerVar = "I am inner";
        console.log(innerVar);  // I am inner
        console.log(outerVar);  // I am outer
        console.log(globalVar); // I am global
    }
    
    inner();
}

outer();
console.log(outerVar);  // ReferenceError

스코프 체인 동작 방식:

  1. inner() 실행 시, 먼저 innerVar를 찾고, 없으면 outerVar를 찾고, 그래도 없으면 globalVar를 찾게됩니다.
  2. outerVarouter() 내부에서 선언되었으므로 inner()에서 접근 가능하지만, outer() 바깥에서는 접근할 수 없습니다.
  3. 전역 변수 globalVar는 어디에서든 접근 가능 합니다.
profile
안녕하세요 프론트엔드개발자가 되고싶습니다

0개의 댓글