• [JS] 코어자바스크립트 - 02 실행 컨텍스트

    2024. 10. 15.

    by. 지은이: 김지은

    728x90

     

     

     

    01. 실행 컨텍스트란?

    자바스크립트 코드가 실행될 때 필요한 환경 정보를 모아놓은 객체

    좀 더 쉽게 말하자면, 자바스크립트 엔진이 코드를 실행할 때 무엇을 참조해야 하고, 어떤 변수를 사용해야 하며 this가 가리키는 대상들을 관리하는 '환경'

     

    1) 스택과 큐

    스택: 출입구가 하나뿐인 깊은 우물 같은 데이터 구조로 마지막에 들어간 것이 가장 먼저 나오며 데이터가 한쪽 끝에서만 추가, 제거될 수 있는 선형 자료 구조

    큐: 양쪽이 모두 열려있는 파이프 구조로 먼저 들어간 것이 먼저 나오며 한쪽 끝에서 데이터가 추가되고 다른 한쪽 끝에서 데이터가 제거되는 선형 자료 구조

     

     

     

    2) 실행 컨텍스트와 콜 스택

    콜 스택(Call Stack)이란 자바스크립트 엔진이 코드 실행 순서를 관리하는 데이터 구조로, 실행 중인 함수의 실행 컨텍스트가 스택에 쌓이고 함수가 완료되면 스택에서 제거된다.

     

    // -------------------------(1)
    const a = 1;
    function outer() {
        function inner() {
            console.log(a); //undefined
            const a = 3;
        }
        inner(); // ------------(2)
        console.log(a); // 1
    }
    
    outer();  // ---------------(3)
    console.log(a); // 1

    1. 처음 자바스크립트 코드를 실행하는 순간 (1) 전역 실행 컨텍스트가 생성되고, a가 1로 초기화

    * 전역 컨텍스트는 자바스크립트 파일이 실행되면 가장 먼저 전형 실행 컨텍스트가 생성되는데 코드의 최상위에 있는 모든 변수와 함수에 대한 정보를 담고 있음

    2. (3)에서 outer() 함수를 호출되면 outer함수의 실행 컨텍스트가 콜 스택에 담겨 전역 컨텍스트는 일시 중단 되며 outer 함수 내부의 코드들을 순차로 실행

    3. inner() 함수가 호출되어 실행 컨텍스트에 추가되고, a값은 초기화 되지 않은 상태로 console을 찍고 있기 때문에 undefined 출력 후 실행이 끝나면 Inner 실행 컨텍스트 제거

    4. inner 함수가 끝나면 outer 함수로 돌아와 전역 스코프의 a 값 1 출력

    5. outer 함수 실행이 끝나고 전역 코드로 돌아와 a 값 1 출력

     

     

    02. VariableEnvironment 와 LexicalEnvironment

    VariableEnvienment: 실행 컨텍스트가 생성될 때 초기화되며, 변수와 함수 선언에 대한 정보를 담고 있다.

    실행이 시작될 때의 상태를 스냅셧으로 저장. 즉, 변수의 값이 초기화된 시점의 정보를 유지

     

    LexicalEnvironment: 이 환경도 변수와 함수 선언에 대한 정보를 포함하지만, 실행 중에 변수의 값이 변경될 떄마다 그 값을 실시간으로 반영

     

    03. environmentRecord와 호이스팅

    현재 실행 컨텍스트와 관련된 코드의 식별자 정보(매개변수, 변수 선언 등)를 처음부터 끝까지 순서대로 저장

    참고) 전역 컨텍스트는 변수 객체를 생성하는 대신 전역 객체를 사용(브라우저의 window나 Node.js의 global 객체와 같은 호스트 객체로 분류

     

    여기서 호이스팅이라는 개념이 등장하는데

    호이스팅이란? 변수와 함수 선언이 코드의 최상단으로 '끌어올려진' 것처럼 간주하는 개념으로 JavaScript 엔진이 실제로 수행하는 과정은 아님

     

    1) 호이스팅 규칙

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

    es6문법에서 var를 거의 사용하지 않지만, 호이스팅 개념을 쉽게 이해하기 위해서 위 코드의 출력을 예상해보면

    a함수가 호출될 때 전달된 인자로 (1)은 1, (2)는 변수 선언만 되었기 때문에 (3)은 undefined가 될 거 라고 생각할 수도 있다.

     

    function a(x) {
        var x;          // 수집 대상 1
        var x;          // 수집 대상 2
        var x;          // 수집 대상 3
        
        x = 1;          // 수집 대상 1의 할당 부분
        console.log(x); // (1)
        console.log(x); // (2)
        x = 2;          // 수집 대상 3의 할당 부분
        console.log(x); // (3)
    }
    
    a(1);

    environmentRecord는 현재 실행될 컨텍스트의 어떤 식별자들이 있는지에만 관심이 있고, 어떤 값이 할당될 것인지는 관심이 없기 때문에 변수를 호이스팅할때 위처럼 변수명만 끌어올려진것처럼 동작한다.

    그래서 실제 출력 결과는 (1) 1, (2) 1, (3) 2가 출력 되는 것

     

    번외로 let, const를 사용해도 호이스팅을 설명할 수 있지만, let과 const는 일시적 사각지대(Temporal Dead Zone, TDZ)가 있어 변수를 참조할 수 없는 구간이 생긴다.

    function a(x) {
        console.log(x); // (1) // x는 함수 매개변수로 사용 가능
        let y;          
        console.log(y); // (2) // y는 호이스팅되지만 TDZ에 있어 오류 발생
        y = 2;
        console.log(y); // (3) // y는 2로 설정된 후 출력됨
    }
    a(1);

     

    2) 함수 선언문과 함수 표현식

    함수 선언문은 코드의 어디에 작성되어 있든 상관없이 실행 전에 전체 함수가 호이스팅

    함수 표현식은 변수 선언부만 호이스팅되어 함수 자체는 실행 시점에 할당된 이후에만 호출 가능

    console.log(sum(1, 2));      //3
    console.log(multiply(3, 4)); // 오류 발생: multiply is not a function
    
    function sum(a, b) {
      return a + b;
    }
    
    const multiply = function (a, b) {
      return a * b;
    };

    sum함수는 선언전에 호출해도 아무 문제 없이 실행되는데 이러한 문제는 큰 프로젝트에서 같은 이름의 함수가 다른 위치에 선언되어 의도하지 않게 덮어씌워지는 문제가 발생할 수 있다.

    multiply함수는 함수가 정의되지 않은 상태로 호출하려고 했기 때문에 에러가 발생하며, 이러한 함수 표현식은 선언 후에만 호출할 수 있기 때문에 덮어씌우기 문제를 방지하는데 더 유리하다.

     

    3) 스코프, 스코프 체인, outerEnvironmentReference

    • 스코프: 변수의 유효 범위. 어떤 블록(함수, {})을 기준으로 그 안에서 선언된 변수는 그 블록 안에서만 사용 가능(=블록 스코프)
    • 스코프 체인: 변수를 찾는 과정. 어떤 변수를 찾을 때 현재 스코프(블록)에서 찾고, 없으면 외부 스코프에서 찾음
    • outerEnvironmentReference: 함수가 선언될 당시의 스코프를 참조하는것을 의미. 즉, 함수를 호출할 때가 아닌, 함수를 선언했을 때의 외부 스코프를 참조하여 함수 내부에서 외부 변수를 사용할 수 있다.
    • Lexical Environment: 현재 코드 실행 시 접근할 수 있는 변수, 함수들을 관리하는 공간

    outerEnvironmentReference는 만약 Lexical Environment 안에 변수가 없을 때 함수가 선언될 당시의 외부 환경에 있는 변수를 찾을 수 있도록 연결해주는 역할

     

    let a = 1;
    
    const outer = () => {
      const inner = () => {
        let a = 3;
        console.log(a);
      };
    
      inner();
      console.log(a);
    };
    
    outer();
    console.log(a);
    1. 전역에서 let a = 1 변수가 선언되었고, 이 변수는 전역에서 접근 가능
    2. outer() 함수가 호출되어 새로운 함수 스코프가 생성. 이때, 전역에서 실행 중이던 코드는 임시 중단
    3. outer 함수 내부에서 inner() 함수가 호출되어 또 다른 함수 스코프가 생성되고, outer 실행 컨텍스트는 임시 중단
    4. inner 함수 내부에서 let a = 3이 선언되는데 이때, a는 inner 함수 스코프에서만 유효. console.log(a) = 3 출력
    5. inner() 함수 실행이 끝나면 다시 outer 함수로 돌아와서 console.log(a) 호출. 이때 outer 함수 스코프 내에 a변수가 없기 때문에 전역 스코프에서 a = 1 을 찾아 출력
    6. outer() 함수 실행이 끝나고 다시 전역 실행 컨텍스트가 활성화 되어 마지막 console.log(a) = 1 출력

     

    4) 전역변수와 지역변수

    • 전역 변수: 함수 밖에서 선언한 변수로, 프로그램 어디서든 접근 가능
    • 지역 변수: 함수 내부에서 선언한 변수로, 그 함수 안에서만 접근 가능 (function outer() { let a = 1; })

    전역변수를 가능한 적게 사용하는 것이 좋은데, 다른 코드에서 우연히 전역 변수와 같은 이름을 사용하게 되면 충돌이 발생할 수 있음

     

     

    댓글