본문 바로가기

Frontend/JavaScript

[JavaScript] 코어 자바스크립트 2장 - 실행 컨텍스트 (feat. 호이스팅, 스코프 체인)

반응형

아래 책을 읽고 정리한 내용입니다.

http://www.yes24.com/Product/Goods/78586788

 

코어 자바스크립트 - YES24

자바스크립트의 근간을 이루는 핵심 이론들을 정확하게 이해하는 것을 목표로 합니다최근 웹 개발 진영은 빠르게 발전하고 있으며, 그 중심에는 자바스크립트가 있다고 해도 결코 과언이 아니

www.yes24.com

 

 

💡실행 컨텍스트를 정확하게 알면 자바스크립트의 동작 방식을 이해할 수 있다. 특히 호이스팅과 스코프 체인에 대해 이해할 수 있다.

 

실행 컨텍스트란?

  • 실행 컨텍스트는 실행할 코드에 제공할 환경 정보들을 모아놓은 객체로, 일반적으로 함수를 실행(호출)할 때 구성된다.
  • 함수가 호출되면, 해당 함수에 필요한 환경 정보들을 모아 컨텍스트를 구성하고, 이를 콜 스택에 쌓아 올렸다가, 가장 위에 쌓여있는 컨텍스트와 관련 있는 코드들을 실행하는 식으로 전체 코드의 환경과 순서를 보장한다.

 

cf) 컨텍스트란?

  • 번역하면 상황, 문맥, 맥락. 실행할 코드(함수)의 주변 상황이라고 이해할 수 있다.
  • 그러한 주변 환경 정보들의 묶음이 실행 컨텍스트인 것이다.

 

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

 

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

 

 

LexicalEnviornment

Lexcial Enviornment, 어휘적 환경이라는 말로 잘 알려져 있다. 저자는 컨텍스트를 구성하는 환경 정보들을 사전에서 접하는 느낌으로 모아놓은 것이라고 표현한다.

 

이곳에서 수집하는 정보는 현재 컨텍스트 내의 1. 식별자들에 대한 정보(environmentRecord)와 2. 외부 환경 정보(outerEnvironmentReference)이다. 각각 자세히 알아보자.

 

 

environmentRecord

  • 현재 컨텍스트와 관련된 코드의 식별자(변수명) 정보들이 저장되는 곳이다.
  • 컨텍스트를 구성하는 함수에 지정된 매개변수명, 선언된 함수, var로 선언된 변수명 등이 저장된다. (cf. let, const는 아님)
  • 컨텍스트 내부 전체를 처음부터 끝까지 훑어나가며 순서대로 수집한다.

 

이러한 수집 과정을 통해 코드가 실행되기 전임에도 불구하고 자바스크립트 엔진은 이미 해당 환경에 속한 코드의 변수명을 모두 알고 있게 되는 셈이다. 그리고 바로 여기서 호이스팅의 개념이 등장한다.

 

변수들을 코드 상단으로 끌어올린다는 호이스팅이란 개념이, 사실은 변수 정보를 수집하는 과정을 이해하기 쉬운 방법으로 대체한 가상의 개념이었던 것이다. (호이스팅 = 변수 정보 수집 과정)

 

호이스팅 규칙 알아보기

  • 호이스팅은 변수명만 끌어올리고 할당부는 원래 자리에 그대로 둔다.
  • 변수는 선언부와 할당부를 나누어 선언부만 끌어올리는 반면, 함수 선언은 함수 전체를 끌어올린다.

 

예시를 통해 더 알아보자.

 

1. 매개변수와 변수에 대한 호이스팅

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

a(1);

 

호이스팅 결과 - 변수의 선언부만 끌어올리고 할당부는 원래 자리에 둠

function a(x){
    var x; //매개변수 x의 선언부
    var x; //var x 선언부
    var x; //var x = 2 선언부
    
    x = 1; //매개변수 할당부
    console.log(x);
    console.log(x);
    x = 2; //var x = 2 할당부
    console.log(x);
};

a(1);

 

출력 결과

1
1
2

 

 

2. 함수 선언의 호이스팅

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

a();

 

호이스팅 결과 - 함수 선언의 경우 전체를 끌어올림

function a(){
    var b; //var b = 'bbb' 선언부
    function b() { } //함수 선언 전체 (var b = function b() {}와 동일)

    console.log(b);
    b = 'bbb'; //var b = 'bbb' 할당부
    console.log(b);
    console.log(b);
};

a();

 

출력 결과

[Function: b]
'bbb'
'bbb'

 

 

3. 함수 선언문과 함수 표현식의 호이스팅

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;
}

 

호이스팅 결과 - 함수 표현식은 변수 선언부만 끌어올림

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;
}

 

출력 결과

3
TypeError: multiply is not a function

 

cf) 함수 표현식을 쓰는 이유

함수 선언문을 사용할 경우, 함수 선언보다 호출이 우선되어도 문제없이 코드가 돌아간다. 이러한 환경은 개발자가 의도하지 않는 동작이 일어날 수 있다는 것을 의미하고, 오류 발생 시 원인을 파악하기가 쉽지 않다. 따라서 상대적으로 안전한 함수 표현식을 쓰는 것이 좋다.

 

 

outerEnvironmentReference

  • 스코프 체인을 가능케 하는 것이 outerEnvironmentReference이다. (스코프란 식별자에 대한 유효범위)
  • outerEnvironmentReference는 호출된 함수가 선언될 당시의 LexicalEnvironment를 참조한다.
  • 연결 리스트의 형태를 띠며, 선언 시점의 LexicalEnvironment를 계속 찾아 올라가다 보면 마지막에는 전역 컨텍스트의 LexicalEnvironment가 있을 것이다.
  • 구조적인 특성상 여러 스코프에서 동일한 식별자를 선언한 경우, 무조건 스코프 체인 상에서 가장 먼저 발견된 식별자에만 접근 가능하다.  

 

스코프 체인 예시

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

outer();
console.log(a);

 

호이스팅 결과

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

outer();
console.log(a);

 

스코프 체인에 따른 출력 결과

undefined //inner안에서 a를 발견했지만 값이 없음
1 //outer에서 a발견하지 못함 -> 바깥에서 탐색 -> 전역에서 a발견
1 //전역에서 a발견

 

 

반응형