본문 바로가기
  • GDG on campus Ewha Tech Blog
4-1기 스터디/Javascript-Typescript Deep Dive

[JS Deep Dive 스터디] 2주차 - 실행 컨텍스트

by 호프 2022. 11. 6.

일시: 2022.10.9

참석자: 윤수지, 유효주, 조민재, 오예린


금주 스터디는 윤수지님께서 정리하신 내용을 공유해주셨습니다.

2. 실행 컨텍스트

실행 컨텍스트란?

  • 스택 & 큐
  • 실행 컨텍스트: 실행할 코드에 제공할 환경 정보들을 모아놓은 객체
    • 동일한 환경에 있는 코드들을 실행할 때 필요한 환경 정보들을 모아 컨텍스트를 구성하고 이를 콜 스택(call stack)에 쌓아올렸다가, 가장 위에 쌓여있는 컨텍스트와 관련 있는 코드들을 실행하는 식으로 전체 코드의 환경과 순서를 보장
    • 실행 컨텍스트를 구성하는 방법: 전역공간, eval() 함수, 함수
// --------------------------------- (1)
var a = 1;
function outer() { 
    function inner() { 
        console.log(a); // undefined
        var a = 3;
    }
    inner(); // --------------------------------- (2)
    console.log(a); // 1
}
outer(); // --------------------------------- (3)
console.log(a); // 1

(1) : 콜 스택에 전역 컨텍스트 추가 —> 전역 컨텍스트와 관련된 코드 진행

(3) : 콜 스택에 outer 실행 컨텍스트 추가 —> outer 컨텍스트와 관련된 코드 진행

(2) : 콜 스택에 inner 실행 컨테스트 추가 —> inner 함수 내부 코드 진행 —> 함수 실행 종료되면 inner 컨텍스트 제거

(2) 다음 줄: outer 함수 실행 —> outer 컨텍스트 제거

(3) 다음 줄: 남은 코드 이어서 실행 —> 전역 컨텍스트 제거

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

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

VariableEnvironment

실행 컨텍스트를 생성할 때 VariableEnvironment에 정보를 먼저 담은 다음, 이를 그대로 복사해서 LexicalEnvironment를 만들고, 이후에는 LexicalEnvironment를 주로 이용한다.

  • VariableEnvironmentLexicalEnvironment의 내부는 environmentRecordouter-EnvironmentReference로 구성되어 있음.

LexicalEnvironment

environmentRecord와 호이스팅(hoisting)

  • environmentRecord
    • 현재 컨텍스트와 관련된 코드들의 식별자 정보들이 저장됨
    • 컨텍스트 내부 전체를 처음부터 끝까지 훑어나가며 순서대로 수집
    • 코드가 실행되기 전에 자바스크립트 엔진은 이미 해당 환경에 속한 코드의 변수명을 모두 알고 있는 상태

→ 이렇게 자바스크립트 엔진이 변수 정보를 수집하는 과정을 호이스팅(hoisting) 이라고 한다.

💡 참고
전역 실행 컨텍스트는 변수 객체를 생성하는 대신 자바스크립트 구동 환경이 별도로 제공하는 객체인 전역 객체(global object)를 사용한다.
브라우저의 window , Node.js의 global 객체 등이 전역 객체에 속하고, 이들은 자바스크립트 내장 객체(native object)가 아닌 호스트 객체(host object)로 분류된다.

호이스팅 규칙

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

function a() {
    var x = 1;
    console.log(x); // (1)
    var x;
    console.log(x); // (2)
    var x = 2;
    console.log(x); // (3)
}
a();

위의 코드에서 호이스팅이 끝나고 나면 다음과 같은 형태로 바뀐다고 볼 수 있다.

function a() {
        var x;
        var x;
        var x;

    x = 1;
    console.log(x); // (1) 1
    console.log(x); // (2) 1
    x = 2;
    console.log(x); // (3) 1
}
a();

즉, 변수를 선언하는 부분이 먼저 실행되는데 이때 이미 선언된 변수 x 가 있으므로 중복된 선언은 무시되고, 이후 x에 1을 할당하기 때문에 (1), (2) 모두 1을 출력한다.

실행 컨텍스트는 변수의 경우에는 선언부와 할당부를 나누어 선언부만 끌어올리지만 함수 선언은 함수 전체를 끌어올린다.

function a() {
    console.log(b); // (1) [Function: b]
    var b = 'bbb';
    console.log(b); // (2) 'bbb'
    function b() {}
    console.log(b); // (3) 'bbb'
}
a();

위의 경우 먼저 실행 컨텍스트에 의해 변수 b를 선언하고, 그 다음 함수 b를 선언한다. 이때 이미 선언된 변수 b가 있으므로 선언 과정은 무시되지만, 함수 선언의 경우 함수 전체를 끌어올리므로 함수는 별도의 메모리에 담기고 그 함수가 저장된 주솟값을 b와 연결된 공간에 저장한다.

따라서 (1) 에서는 함수 b가 출력되고, 이후 b에 다시 ‘bbb’ 값이 할당되기 때문에 (2), (3)은 ‘bbb’를 출력한다.

💡 참고
’함수 선언은 함수 전체를 끌어올린다’는 규칙 덕분에 함수를 선언한 위치와 무관하게 그 함수를 실행할 수 있게 되었지만 오히려 더 많은 혼란을 야기하기도 한다.

함수 선언문과 함수 표현식

  • 함수 선언문(function declaration) : 함수의 정의부만 존재하고 할당 명령이 없는 것
    • 함수 전체를 호이스팅
    function a() {} // 함수 선언문. 함수명 a가 곧 변수명
    a();
  • 함수 표현식(function expression) : 정의한 function을 별도의 변수에 할당하는 것
    • 변수 선언부만 호이스팅 → 함수를 하나의 값으로 취급
    var b = function () {} // 익명 함수 표현식. 변수명 b가 곧 함수명
    b();
    
    var c = function d() {} // 기명 함수 표현식. 변수명은 c, 함수명은 d
    c();
    d(); // error
    • 기명 함수 표현식은 외부에서 함수명으로 함수 호출 불가능. 함수 내부에서 재귀함수를 호출하는 용도로 함수명(d)을 쓸 수 있음.
console.log(sum(1, 2)); // 3
console.log(multiply(3, 4)); // 'multiply is not a function' error

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

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

스코프, 스코프 체인, outerEnvironmentReference

  • 스코프: 식별자에 대한 유효범위
    • ES5까지 자바스크립트는 전역공간을 제외하면 오직 함수에 의해서만 스코프가 생성됨.
  • 스코프 체인: 스코프를 안에서부터 바깥으로 차례로 검색해나가는 것
  • outerEnvironmentReference: 스코프 체인을 가능하게 하는 LexicalEnvironment의 두 번째 수집 자료. 현재 호출된 함수가 선언될 당시의 LexicalEnvironment를 참조.
// --------------------------------- (1)
var a = 1;
function outer() { 
    function inner() { 
        console.log(a); // undefined
        var a = 3;
    }
    inner(); // --------------------------------- (2)
    console.log(a); // 1
}
outer(); // --------------------------------- (3)
console.log(a); // 1
  • inner 함수 내부에서는 inner, outer, 전역 스코프 모두에 접근 가능
  • outer 함수의 console.log(a) 코드에서 먼저 outer 컨텍스트의 LexicalEnvironment 안의 environmentRecord에서 a를 탐색하고 해당 값이 없기 때문에 outerEnvironmentReference에 있는 environmentRecord(전역 LexicalEnvironment)를 탐색한다. a 가 있기 때문에 그 값인 1을 반환.
  • 변수 은닉화(variable shadowing): 변수는 무조건 스코프 체인 상에서 가까운 값부터 검색한다. 따라서 inner 함수 내부의 LexicalEnvironmenta 식별자가 존재하므로 스코프 체인 검색을 더 이상 진행하지 않고 a를 반환한다. 즉, inner 함수 내부에서 a 변수를 선언했기 때문에 전역 공간에서 선언한 동일한 이름의 a 변수에는 접근할 수 없다.

전역변수와 지역변수

  • 전역변수(global variable) : 전역 공간에서 선언한 변수
  • 지역변수(local variable) : 함수 내부에서 선언한 변수 → 함수 내부에서만 접근 가능.

this

실행 컨텍스트를 활성화하는 당시에 thisBindingthis로 지정된 객체가 저장된다.

함수를 호출하는 방법에 따라 그 값이 달라지는데, 지정되지 않은 경우에는 전역 객체가 저장된다.

정리

  • 실행 컨텍스트: 실행할 코드에 제공할 환경 정보들을 모아놓은 객체
    • 전역 컨텍스트(전역 공간에서 자동으로 생성), eval, 함수 실행에 의한 컨텍스트 등
    • 실행 컨텍스트 객체가 활성화되는 시점에 VariableEnvironment, LexicalEnvironment, ThisBinding 정보를 수집.
  • VariableEnvironmentLexicalEnvironment는 동일한 내용으로 생성되지만, LexicalEnvironment는 함수 실행 도중 변경되는 사항을 즉시 반영하고 VariableEnvironment는 초기 상태를 유지
  • LexicalEnvironment
    • environmentRecord: 매개변수명, 변수의 식별자, 선언한 함수의 함수명 등을 수집
      • 호이스팅: 변수 선언과 값 할당이 동시에 이뤄진 문장은 선언부만 호이스팅, 함수 선언문의 경우 함수 전체를 호이스팅
    • outerEnvironmentReference: 바로 직전 컨텍스트의 LexicalEnvironment 정보 참조
      • 스코프: 변수의 유효범위
  • 전역변수: 전역 컨텍스트의 LexicalEnvironment에 담긴 변수 / 지역변수: 함수에 의해 생성된 실행 컨텍스트의 변수

댓글