자바스크립트 실행 컨텍스트

휴먼스케이프

안녕하세요. 휴먼스케이프 Hugh 입니다.

이번 시간에는 책 “코어 자바스크립트' 의 2장 실행 컨텍스트에 대해 알아보겠습니다.

실행 컨텍스트란?

실행 컨텍스트는 실행할 코드에 제공할 환경 정보들을 모아놓은 객체입니다. 하나의 실행 컨텍스트를 구성할 수 있는 방법으로 전역공간, eval() 함수, 함수 등이 있습니다. 우리가 흔히 실행컨텍스트를 구성하는 방법은 함수를 실행하는 것 입니다.

var a = 1;
function outer() {
    function inner() {
        console.log(a);
        var a = 3;
    }
    inner();
    console.log(a);
}
outer();
console.log(a);

위 코드를 보며 실행 컨텍스트가 콜 스택에 어떤 순서로 쌓이고 실행되는 지 살펴보겠습니다.

<실행 컨텍스트와 콜 스택>

가장 먼저 자바스크립트 파일이 열리는 순간 전역 컨텍스트가 활성화됩니다. 따라서 전역 컨텍스트가 콜 스택에 쌓입니다. 그리고 outer 함수가 호출되면 자바스크립트 엔진은 outer에 대한 환경 정보를 수집해서 outer 실행 컨텍스트를 생성한 후 콜스택에 담습니다. 전역 컨텍스트와 관련된 코드의 실행을 중단하고 outer 실행 컨텍스트와 관련된 코드를 순차적으로 실행합니다. 다시 inner 함수의 실행 컨텍스트가 콜 스택의 가장 위에 담기면 outer 컨텍스트는 중단하고 inner 함수 내부 코드를 진행합니다. 실행이 종료되면 콜 스택에서 제거되고 그 다음 순서에 있는 코드를 이어서 진행합니다. 이 때 자바스크립트 엔진이 실행 컨텍스트 객체에 저장하는 값은 다음과 같습니다.

VariableEnvironment, LexicalEnvironment, ThisBinding

이제부터 위 내용들을 살펴보겠습니다.

VariableEnvironment

LexicalEnvironment와 같지만 최초 실행 시의 스냅샷을 유지한다는 점이 다릅니다. 실행 컨텍스트를 생성할 때 VariableEnvironment에 정보를 먼저 담은 다음, 이를 그대로 복사해서 LexicalEnvironment를 만들고 이후에는 LexicalEnvironment를 주로 활용합니다. 이들 내부는 environmentRecord와 outer-EnvironmentReference로 구성돼 있습니다. 자세한 내용은 LexicalEnvironment와 같으므로 아래에서 살펴보겠습니다.

LexicalEnvironment

environmentRecord와 호이스팅

environmentRecord에는 현재 컨텍스트와 관련된 코드의 식별자 정보들이 저장됩니다. 컨텍스트 내부 전체를 처음부터 훑어나가며 순서대로 수집(매개변수 이름, 함수 선언, 변수명 등)합니다. 수집이 끝나더라도 코드는 실행되기 전 상태입니다. 실행되기 전에 자바스크립트 엔진은 이미 해당 환경에 속한 변수 명들을 다 알고 있는 셈 입니다. 따라서 ‘자바스크립트 엔진은 식별자들을 최상단으로 끌어올려놓은 다음 실제 코드를 실행한다’ 라고 생각하더라도 문제가 없습니다. 여기서 호이스팅(hoisting)이라는 개념이 등장합니다. 호이스팅은 ‘끌어올리다’ 라는 의미이고 변수 정보를 수집하는 과정을 이해하기 쉬운 방법으로 추상화한 개념입니다. 실제로 자바스크립트 엔진이 끌어올리지는 않습니다.

호이스팅 규칙

아래는 호이스팅 전의 모습입니다.

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

아래는 호이스팅을 마친 상태입니다. 변수 선언부만 끌어올리고 할당은 원래 자리에 남겨둡니다.

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

이제 호이스팅이 끝났으니 코드를 실행할 차례입니다.

콘솔 로그로 찍은 x의 값은 순서대로 1, 1, 2 가 출력됩니다. 호이스팅을 마치기 전의 코드를 보면 호이스팅을 알지 못하면 예측하기 어려운 결과입니다.

다음은 함수가 포함된 코드의 호이스팅입니다.

function a () {
    console.log(b);
    var b = 'bbb';
    console.log(b);
    function b () {}
    console.log(b);
}
a();

아래는 호이스팅을 마친 상태입니다. 변수는 선언부만 끌어올리지만 함수는 전체를 끌어올립니다.

function a () {
    var b;
    function b () {}
    console.log(b);
    b = 'bbb';
    console.log(b);
    console.log(b);
}
a();

해석의 편의를 위해서 한 가지 더 바꿔보면, 함수 선언문은 함수명으로 선언한 변수에 함수를 할당한 것처럼 여길 수 있습니다.

function a () {
    var b;
    var b = function b () {} // 바뀐 부분
    console.log(b);
    b = 'bbb';
    console.log(b);
    console.log(b);
}

결과로는 b 함수, ‘bbb’, ‘bbb’ 가 차례로 출력되었습니다. 이 역시 호이스팅 개념을 알지 못하면 예측하기 어렵습니다.

함수 선언문과 함수 표현식

호이스팅을 알아보기 위해 같이 알아야 하는 부분입니다. 둘 다 함수를 새롭게 정의할 때 쓰는 방식입니다. 선언문은 정의부만 존재하고 별도의 할당이 없고, 표현식은 정의한 함수를 별도의 변수에 할당하는 것을 말합니다. 함수 선언문은 반드시 함수명이 정의돼 있어야 하고 함수 표현식은 없어도 됩니다. 일반적으로 함수 표현식은 익명 함수 표현식을 말합니다.

아래 예제를 통해 함수 선언문과 함수 표현식의 차이를 살펴보겠습니다.

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));
    console.log(multiply(3, 4));
    multiply = function (a, b) {
        return a * b;
    }

함수 선언문은 전체를 호이스팅한 반면 함수 표현식은 선언부만 호이스팅 했습니다. 따라서 첫 번째 sum 함수의 호출은 잘 실행되는 반면 mutiply 함수의 호출은 ‘multiply is not a function’ 이라는 에러 메시지가 출력됩니다. 함수 선언문의 경우 함수를 선언하기도 전에 문제 없이 실행되는 것이 받아들이기 어려울 수도 있습니다. 함수 선언문 보다는 선언한 후에 호출할 수 있는 함수 표현식이 상대적으로 안전합니다.

스코프, 스코프 체인, outerEnvironmentReference

스코프란 식별자에 대한 유효범위입니다. 유효범위를 안에서부터 바깥으로 차례로 검색해나가는 것을 스코프 체인이라고 합니다. 그리고 이것을 가능하게 하는 것이 LexicalEnvironment의 두 번째 수집 자료인 outerEnvironmentReference 입니다.

스코프 체인

outerEnvironmentReference는 현재 호출된 함수가 선언될 당시의 LexicalEnvironment를 참조합니다. 아래 예제를 보며 스코프 체인에 대해 알아보겠습니다.

var a = 1;
var outer = function () {
    var inner = function () {
        console.log(a);
        var a = 3;
    }
    inner();
    console.log(a);
}
outer();
console.log(a);

위 코드가 실행되면 가장 먼저 전역 컨텍스트가 활성화됩니다. 전역 컨텍스트의 environmentRecord에는 { a, outer } 식별자가 저장됩니다. 그 다음 outer 함수가 실행되어 전역 컨텍스트는 중단되고 outer 컨텍스트가 실행됩니다. outer environmentRecord에 { inner } 식별자가 저장이 되고, outer 함수는 전역 공간에서 선언됐으므로 전역 컨텍스트의 LexicalEnvironment를 참조복사합니다. 이를 표기하면 [ GLOBAL, { a, outer } ]입니다. 이 과정이 반복됩니다.

여기서 a 출력의 순서와 값은 inner 함수 안의 a(undefined), outer 함수 안의 a(1), 전역의 a(1) 이 됩니다. 이 결과는 스코프 체인 상에서 가장 가까운 a가 먼저 발견되었기 때문입니다.(inner 실행 컨텍스트의 environmentRecord에 a가 저장되었기 때문) 위와 같은 케이스를 변수 은닉화라고 합니다.

스코프 체인 중 현재 실행 컨텍스트를 제외한 상위 스코프 정보들을 개발자 도구를 통해 확인 가능합니다. 함수 내부에서 console.dir()을 통해 함수를 출력하는 것입니다. debugger를 활용하면 아래와 같이 좀 더 상세한 정보를 확인할 수 있습니다.

여기까지 실행 컨텍스트에 대해서 알아보았습니다. 위의 예제에서와 같은 혼란을 겪지 않기 위해서 책에서는 전역 변수의 사용은 자제하고, 함수는 표현식으로 사용을 권장하고 있습니다.

감사합니다.

Get to know us better! Join our official channels below.

Telegram(EN) : t.me/Humanscape KakaoTalk(KR) : open.kakao.com/o/gqbUQEM Website : humanscape.io Medium : medium.com/humanscape-ico Facebook : www.facebook.com/humanscape Twitter : twitter.com/Humanscape_io Reddit : https://www.reddit.com/r/Humanscape_official Bitcointalk announcement : https://bit.ly/2rVsP4T Email : support@humanscape.io

기업문화 엿볼 때, 더팀스

로그인

/