JavaScript | 실행 컨텍스트 (execution context)

Kate Jung·2022년 8월 10일
0

JavaScript

목록 보기
39/39
post-thumbnail

📌 실행 컨텍스트란?

🔹 개념 및 특징

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

  • 클로저를 지원하는 대부분의 언어에서 유사 or 동일한 개념이 적용됨.

  • JS가 어떤 실행 컨텍스트가 활성화되는 시점에 수행하는 동작들

    • 호이스팅 (선언된 변수를 위로 끌어올림)

    • 외부 환경 정보를 구성

    • this 값 설정

      → 특이한 현상 발생 (다른 언어에서 발견 불가)

🔹 ‘동일한 환경’이란?

하나의 실행 컨텍스트를 구성할 수 있는 방법

  • 전역공간

    자동으로 생성됨

  • eval() 함수

  • 함수, 블록 ({})

    흔히 실행 컨텍스트를 구성하는 방법

🔹 원리

한 실행 컨텍스트가 콜 스택의 맨 위에 쌓이는 순간 ⇒ 현재 실행할 코드에 관여하게 되는 시점

  1. 동일한 환경에 있는 코드 실행 시, 필요한 환경 정보들을 모아 컨텍스트 구성

  2. 이를 콜 스택(call stack)에 쌓아 올림

  3. 가장 위에 쌓여 있는 컨텍스트와 관련된 코드들을 실행

    → 전체 코드의 환경 & 순서 보장

  • 예시

    1. 처음 JS 코드를 실행하는 순간(1) 전역 컨텍스트가 콜 스택에 담김.

      최상단의 공간: 브라우저에서 자동 실행 (별도의 실행 명령 없어도) → JS 파일이 열리는 순간 전역 컨텍스트가 활성화됨.

    2. 전역 컨텍스트 관련 코드 순차로 진행

    3. (3)에서 outer 함수 호출

      JS 엔진이 outer에 대한 환경 정보를 수집 → outer 실행 컨텍스트 생성 → 콜 스택에 담음. → 콜 스택 맨 위: outer 실행 컨텍스트 → 전역 컨텍스트 관련 코드 실행 일시중단 → outer 실행 컨텍스트 관련 코드 (outer 함수 내부 코드들) 순차 실행

    4. (2)에서 inner함수의 실행 컨텍스트가 콜 스택의 맨 위에 담김

      outer 컨텍스트 관련 코드 실행 중단 → inner 함수 내부의 코드를 순서대로 진행

    5. inner함수 실행 종료 후, inner 실행 컨텍스트를 콜 스택에서 제거

    6. 아래의 outer실행 컨텍스트 실행

      1. 중단했던 (2)의 다음 줄 부터
      2. outer함수 실행 종료 → outer 실행 컨텍스트를 콜 스택에서 제거
    7. 콜 스택에 전역 컨텍스트만 남음

      1. 중단했던 (3)의 다음 줄 부터 실행
      2. 전역 공간에 실행할 코드 無 → 전역 컨텍스트 제거
    8. 콜 스택에 남은 것 無 → 종료

🔹 환경 정보들

어떤 실행 컨텍스트가 활성화될 때, JS엔진은 환경 정보들(해당 컨텍스트 관련 코드들을 실행하는 데 필요한)을 수집 → 실행 컨텍스트 객체에 저장

활성화된 실행 컨텍스트의 수집 정보

  • VariableEnvironment
  • LexicalEnvironment
  • Thisbinding

📌 VariableEnvironment

  • 현재 컨텍스트 내의 식별자들에 대한 정보 + 외부 환경 정보
  • 선언 시점의 LexicalEnvironment의 스냅 샷 (변경 사항 반영 X)
  • 실행 컨텍스트 생성 시

    1. VariableEnvironment 에 정보를 먼저 담음

    2. 그대로 복사해서 LexicalEnvironment 제작 (이후 주로 활용)

📌 LexicalEnvironment

처음에는 VariableEnvironment 와 같음 (변경 사항 → 실시간 반영)

🔹 참고

  • 한국어 번역

    사전적 환경

    • 환경 정보들(컨텍스트를 구성하는)을 사전에서 접하는 느낌으로 모아놓은 것

      ex. “현재 컨텍스트 내부에는 a, b, c와 같은 식별자들이 있고 그 외부 정보는 D를 참조하도록 구성”

    • 통용되는 번역은 아님

  • 표기법

    • LexicalEnvironment : lexical environment (일반 추상적인 개념)를 JS에서 구현한 구체적인 대상

    • VariableEnvironment & variable environment 도 마찬가지

🔹 environmentRecord

식별자 정보들(현재 컨텍스트와 관련된 코드의) 저장

  • 식별자에 해당하는 것들

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

    • 함수 자체 (선언한 함수가 있을 경우)

    • var로 선언된 변수의 식별자

  • 컨텍스트 내부 전체를 처음부터 끝까지 훑으며 순서대로 수집함.

  • [참고] 전역 실행 컨텍스트

    전역 객체를 활용함. (변수 객체를 생성하는 대신)

    → 브라우저의 window, Node.js의 global 객체 등 (호스트 객체로 분류)

🔹 호이스팅 (hoisting)

hoist(끌어올리다) + ing

  • JS 엔진은 식별자들을 최상단으로 끌어 올려놓은 뒤, 실제 코드를 실행 한다

    원리: 변수 정보 수집 과정을 모두 마쳤더라도 실행 컨텍스트가 관여할 코드들은 실행되기 전의 상태. 그럼에도 불구하고 JS는 이미 해당 환경에 속한 코드의 변수명들을 모두 아는 셈.

  • JS 엔진이 실제로 끌어 올리지는 않음. but, 편의상 끌어올린 것으로 간주.

✦ 규칙 예시 1 (매개변수와 변수에 대한 호이스팅)

예상 (호이스팅 되지 않았을 때)실제
(1) 1, (2) undefined, (3) 2(1) 1, (2) 1, (3) 2
  1. 예제 2-2

    • 인자들과 함께 함수를 호출한 경우

      인자를 함수 내부의 다른 코드보다 먼저 선언 및 할당이 이뤄진 것으로 간주 가능

      • 코드 내부에서 변수를 선언한 것과 다른 점 無

        • arguments에 전달된 인자를 담는 것 제외

        • LexicalEnvironment 입장에서 완전 같음.

      • [참고] arguments

        • 실행 컨텍스트 생성 시점에 함께 만드는 정보 중 하나

        • 전달한 인자 모두 (지정한 매개변수의 갯수와 무관) arguments 정보에 담김

          → 함수의 자율성을 높이는 측면에서 광범위하게 활용했었음.

        • 문제점

          • 유사 배열 객체

            → 배열처럼 활용하려면 별도 처리 필요

          • 함수 내부에서 매개변수의 값 바꾸면 arguments 값도 함께 바뀜

            → 본래의 개념(‘전달된 인자를 모두 저장한 데이터’)과 상이

        • 대안

          나머지 파라미터 (rest parameter. ES6 등장)

          • arguments 온전히 대체 가능
    • 호이스팅 처리

      • environmentRecord

        • 변수명만 끌어올림. 할당 과정은 원래 자리에 남겨둠.

          매개변수도 마찬가지

        • 수집 대상 1, 2, 3 순서 대로 끌어올림

        → 예제 2-4

  2. 실제 코드 실행 (예제 2-4)

    1. 2번째 줄 : 변수 x 선언

      메모리에서 저장할 공간 미리 확보 → 확보한 공간의 주솟값을 변수 x에 연결

    2. 3, 4번째 줄 : 다시 변수 x 선언 → 무시 (이미 선언)

    3. 6번째 줄 : x에 1 할당

      1을 별도의 메모리에 담음 → x와 연결된 메모리 공간에 1을 가리키는 주솟값 입력

    4. 7, 8번째 줄 : x 출력

      1 출력

    5. 9번째 줄 : x에 2 할당

      2를 별도의 메모리에 할당 → 그 주솟값을 들고 x와 연결된 메모리 공간으로 감 → 기존의 주솟값 (1 가리키는)을 대치함 → 변수 x는 2를 가리킴

    6. 10번째 줄 : x 출력

      (3)에서 2출력 → 함수 내부 모든 코드가 실행됨 → 실행 컨텍스트가 콜 스택에서 제거 됨

✦ 규칙 예시 2 (함수 선언의 호이스팅)

  1. 원본 코드

    function a() {
      console.log(b)  //(1)
      var b = 'bbb'   //수집대상 1
      console.log(b)  //(2)
      function b() {} //수집대상 2
      console.log(b)  //(3)
    }
    a()
    예상 (호이스팅 되지 않았을 때)실제
    (1) 에러 또는 undefined, (2) ‘bbb’, (3) b 함수(1) b 함수, (2) ‘bbb’, (3) ‘bbb’
  2. 호이스팅을 마친 상태 (함수 선언문 → 함수 표현식)

    function a() {
      var b           // 수집 대상 1. 변수: 선언부만 끌어올림
      var b = function b() {} // 수집 대상 2. 함수 선언: 전체 끌어 올림
    
      console.log(b)  //(1)
      var b = 'bbb'   // 변수의 할당부는 원래 자리에 남겨둠
      console.log(b)  //(2)
      console.log(b)  //(3)
    }
    a()
    • 동작 원리

      1. a 함수 실행하는 순간 → a 함수의 실행 컨텍스트가 생성됨

      2. 이 때, 호이스팅 발생변수명 & 함수 선언 정보를 순서대로 위로 끌어올림 (수집)

        • 변수 : 선언부만
        • 함수 선언 : 함수 전체
      3. 호이스팅 끝난 상태에서의 함수 선언문 : 함수 선언문 → 함수 표현식

        함수명으로 선언한 변수에 함수를 할당한 것처럼 여길 수 있음.

  3. 실행 컨텍스트 내부의 코드 순서대로 실행

    1. 2번째 줄 : 변수 b 선언

      메모리에서 저장할 공간 미리 확보 → 확보한 공간의 주솟값을 변수 b에 연결

    2. 3번 째 줄: 변수 b 재선언. 함수 b를 변수 b에 할당하라

      선언 과정 무시 (이미 선언). 함수는 별도의 메모리에 담김 → 함수가 저장된 주솟값을 b와 연결된 공간에 저장 → 변수 b는 함수를 가리킴

    3. 5번째 줄 : 변수 b에 할당된 함수 b 출력

    4. 6번째 줄 : 변수 b에 ‘bbb’ 할당

      b와 연결된 메모리 공간에 ‘bbb’가 담긴 주솟값으로 덮어씀 → 변수 b는 ‘bbb’를 가리킴

    5. 7, 8번째 줄 : (2), (3) 모두 ‘bbb’ 출력

      함수 내부의 모든 코드가 실행됨 → 실행 컨텍스트가 콜 스택에서 제거됨

🔹 함수 선언문 & 함수 표현식

  • 공통점

    함수를 새롭게 정의할 때 쓰이는 방식

  • 차이점

  • 코드 형태

    function a () { }	      // 함수 선언문. 함수명 a가 곧 변수명.
    a();  // 실행 OK
    
    var b = function () { } // (익명) 함수 표현식. 변수명 b가 곧 함수명.
    b();  // 실행 OK
    
    var c = function d() { } // 기명 함수 표현식. 변수명은 c, 함수명은 d.
    c();	// 실행 OK
    d();	// 에러!
  • 예시 | 실질적 차이

    • 원본 코드

      console.log(sum(1, 2));
      console.log(multiply(3, 4));
      
      function sum (a, b) {   // 함수 선언문 sum
        return a + b;
      }
      var multiply = function (a, b) {  // 함수 표현식 multiply
        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
      }
    • 내부 코드들 순서대로 실행

      1. 1번째 줄: 메모리 공간 확보. 확보된 공간의 주솟값을 변수 sum에 연결

      2. 4번째 줄: 또 다른 메모리 공간 확보. 그 공간의 주솟값을 변수 multiply에 연결

      3. 1번째 줄 (다시): sum 함수를 또 다른 메모리 공간에 저장 → 그 주솟값을 변수 sum 공간에 할당 → 변수 sum은 함수 sum을 바라보는 상태

      4. 5번째 줄: sum 실행 → 정상 실행 → 3 출력

      5. 6번째 줄: multiply에 값 할당 X → 비어있는 대상을 함수로 여겨 실행하라고 명령 → multiply is not a function 에러 msg 출력 → 8번째 줄은 6번째 줄의 에러로 실행 X. 런타임 종료

  • 함수의 호이스팅 원리

    전역 컨텍스트가 활성화될 때 전역 공간에 선언된 함수들이 모두 가장 위로 끌어올려짐.

    동일한 변수명에 서로 다른 값을 할당 시, 나중에 할당한 값이 먼저 할당한 값을 덮어씌움 (override)

    코드 실행 중 실제 호출되는 함수 ⇒ 마지막에 할당한 함수

  • 문제점

    • 함수(문제의 원인) 에러 반환 無

      이 함수를 활용하는 다른 함수에서도

  • 결론

    • 전역 공간에서 함수 선언 or 동명의 함수 중복 선언 → X

    • 함수 표현식이 (상대적으로) 안전

      의도대로 잘 동작 & 빠르고 쉬운 디버깅 (바로 에러 검출)

🔹 스코프 (체인), outerEnvironmentReference

✦ 스코프 (scope)

식별자에 대한 유효범위

  • 접근 가능 범위

    어떤 경계 A의 __에서 선언한 변수의 접근 가능 범위외부내부
    A의외/내부내부
  • ES5 vs ES6

    ES5ES6
    ~에 의해서 scope 생성함수 (전역 공간 제외)함수 & 블록
    • 블록

      새로 생긴 let, const, class, strict mode에서의 함수 선언 등에 대해서만 범위로서의 역할 수행 (var로 선언한 변수에 대해서는 적용 X)

    • 함수 스코프, 블록 스코프 용어 사용

      둘 구분을 위해

✦ 스코프 체인 (scope chain)

‘식별자의 유효범위’를 안에서 바깥으로 차례로 검색해나가는 것

✦ outerEnvironmentReference

스코프 체인을 가능케 하는 것

  • 현재 호출된 함수가 선언될 당시의 LexicalEnvironment를 참조

    • ‘선언하다’라는 행위가 실제로 일어날 수 있는 시점

      콜 스택 상에서 어떤 실행 컨텍스트가 활성화된 상태일 때

  • 특징

    1. 연결리스트(linked list)형태를 띔

      ‘선언 시점의 LexicalEnvironment’를 계속 찾아 올라가면 마지막엔 전역 컨텍스트의 LexicalEnvironment가 있을 것

    2. 변수 은닉화 (variable shadowing)

      무조건 스코프 체인 상에서 가장 먼저 발견된 식별자에만 접근 가능

      가장 가까운 요소부터 차례대로만 접근 가능 (다른 순서로 접근 불가)

      → 여러 스코프에서 동일한 식별자 선언 시, 무조건 스코프 체인 상에서 가장 먼저 발견된 식별자에만 접근 가능

  • 표기

    [ 실행 컨텍스트의 이름, environmentRecord 의 객체 ]

    ex. [ GLOBAL, { a, outer } ]

✦ 예시

var a = 1
var outer = function () {
  var inner = function () {
    console.log(a)
    var a = 3
  }
  inner()
  console.log(a)
}
outer()
console.log(a)
  • 동작 원리 | 설명 요약 표

    • 단어

      • L.E: LexicalEnvironment
      • e: environmentRecord
      • o: outerEnvironmentReference
      • [숫자]: 코드 줄 번호
    • 특징

      • 왼쪽에서 오른쪽으로 바라보면

        규모 작아짐. But 접근 가능한 변수의 수 증가(스코프 체인 타고)

      • 변수의 은닉화

  • 동작 원리 | 설명

    1. 시작: 전역 컨텍스트 활성화

      • 전역 컨텍스트

        • environmentRecord : { a, outer } 식별자 저장
        • outerEnvironmentReference : 아무것도 안 담김. (전역 컨텍스트는 선언 시점 無)
      • this: 전역 객체

    2. 1, 2번째 줄: 전역 스코프에 있는 변수 a에 1을, outer에 함수를 할당

    3. 10번째 줄: outer함수 호출

      • 전역 컨텍스트 코드 → 임시 중단 (10번째 줄에서)
      • outer 실행 컨텍스트 활성화 (2번째 줄로 이동)
    4. 2번째 줄:

      • outer 실행 컨텍스트의

        • environmentRecord : { inner } 식별자 저장

        • outerEnvironmentReference : outer 함수 선언 시(전역 공간 에서 선언 → 전역 컨텍스트)의 LexicalEnvironment 가 담김

          • 표기: [ GLOBAL, { a, outer } ]
      • this: 전역 객체

    5. 3번째 줄: outer 스코프에 있는 변수 inner에 함수 할당

    6. 7번째 줄: inner 함수 호출

      • outer 실행 컨텍스트의 코드 → 임시중단
      • inner 실행 컨텍스트 → 활성화 (3번째 줄로 이동)
    7. 3번째 줄:

      • inner 실행 컨텍스트의

        • environmentRecord : { a } 식별자 저장

        • outerEnvironmentReference : inner함수 선언 시(outer함수 내부에서 선언 → outer함수)의 LexicalEnvironment 가 담김

          • 표기: [ outer, { inner } ]
      • this: 전역 객체

    8. 4번째 줄: 식별자 a에 접근

      • 활성화 상태인 inner 컨텍스트의 environmentRecord 에서 a 검색
      • a 발견 → 할당된 값 無 → undefined 출력
    9. 5번째 줄: inner 스코프에 있는 변수 a에 3할당

    10. 6번째 줄: inner 함수 실행 종료

      콜 스택에서 inner 실행 컨텍스트 제거 → 바로 아래의 outer 실행 컨텍스트 활성화 → 중단했던 7번째 줄 다음으로 이동

    11. 8번째 줄: 식별자 a에 접근

      JS 엔진이 활성화된 실행 컨텍스트의 LexicalEnvironment 에 접근
      첫 요소의 environmentRecord 에서 a 찾고 없으면 → outerEnvironmentReference 에 있는 environmentRecord 로 넘어가는 식으로 계속 검색

      → 전역 LexicalEnvironment 에 a 있음 → a에 저장된 값 (1) 반환

    12. 9번째 줄: outer 함수 실행 종료

      콜 스택에서 outer 실행 컨텍스트 제거 → 바로 아래의 전역 컨텍스트 활성화 → 중단했던 10번째 줄의 다음으로 이동

    13. 11번째 줄: 식별자 a에 접근

      활성화 상태인 전역 컨텍스트의 environmentRecord 에서 a 검색 → a 찾음 → 1출력 → 모든 코드 실행 완료 → 콜 스택에서 전역 컨텍스트 제거 → 종료

✦ [참고] 상위 스코프 정보 확인

크롬 브라우저 환경에서 스코프 체인 중 상위 스코프 정보들(현재 실행 컨텍스트 제외)을 콘솔로 확인 가능

  • 특징

    함수 내부에서 실제로 호출할 외부 변수들의 정보만 보여줌

  • 방법

    1. 함수 내부에서 함수 출력

      var a = 1;
      var outer = function () {
        var b = 2;
        var inner = function () {
      		console.log(b) // 추가 시, 결과 2
          console.dir(inner); // 결과 1
        };
        inner();
      };
      outer();
      • 결과 1
      • 결과 2
    2. debugger로 더 제대로 된 정보 확인 가능

      • 모든 모던 브라우저에서 통용 (크롬, 파이어 폭스 → 가장 자세함)
      • 방법 : debugger 입력

🔹 전역변수 & 지역변수

  • 구분

    전역 변수지역 변수
    ~에서 선언한 변수전역 공간함수 내부
  • 전역 변수 사용 최소화 권고 (코드의 안전성을 위해)

    • 안전성

      지역 변수 > 함수 표현식 > 함수 선언문

    • 전역 변수 사용 최소화에 도움 주는 방법

      • 즉시실행함수 (IFE), 네임 스페이스, 모듈 패턴, 샌드박스 패턴

      • AMD (모듈 관리 도구), CommonJS, ES의 모듈 등

✅ 요약

  • 실행 컨텍스트

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

    • 전역 컨텍스트 (전역 공간에서 자동으로 생성), eval, 함수 실행에 의한 컨텍스트 등

    • 활성화되는 시점에 3가지 정보 수집 (VariableEnvironment, LexicalEnvironment, ThisBinding)

    • 생성 시 VariableEnvironmentLexicalEnvironment 가 동일한 내용으로 구성됨

      • 구성 내용

        • environmentRecord

          매개변수명, 변수의 식별자, 선언한 함수의 함수명 등을 수집

        • outerEnvironmentReference

          바로 직전 컨텍스트의 LexicalEnvironment 정보를 참조

      • 차이점

        • LexicalEnvironment → 함수 실행 도중에 변경되는 사항이 즉시 반영

        • VariableEnvironment → 초기 상태 유지

  • 호이스팅

    environmentRecord 의 수집 과정을 추상화한 개념

    • 실행 컨텍스트가 관여하는 코드 집단의 최상단으로 이들을 ‘끌어올린다’고 해석

    • 변수 선언과 값 할당이 동시에 이뤄진 문장

      ‘선언부’만을 호이스팅 & 할당 과정은 원래 자리에 남음 → 여기서 함수 선언문 / 표현식의 차이 발생

  • 스코프

    변수의 유효 범위

    • outerEnvironmentReference

      해당 함수가 선언된 위치의 LexicalEnvironment 를 참조

      • 코드 상에서 어떤 변수에 접근하려고 하면

        현재 컨텍스트의 LexicalEnvironment 를 탐색 → 발견되면 그 값 반환. 발견하지 못할 경우 다시 outerEnvironmentReference 에 담긴 LexicalEnvironment 를 탐색하는 과정을 거침

        전역 컨텍스트의 LexicalEnvironment 까지 탐색해도 해당 변수를 찾지 못하면 undefined 반환

  • 전역/지역 변수

    • 전역 변수

      전역 컨텍스트의 LexicalEnvironment 에 담긴 변수

    • 지역 변수

      그 밖의 함수에 의해 생성된 실행 컨텍스트의 변수들

    • 전역 변수 사용은 최소화 추천 안전한 코드 구성을 위해
  • this

    • 실행 컨텍스트를 활성화하는 당시에 지정된 this가 저장됨

    • 함수를 호출하는 방법에 따라 그 값이 달라짐

      지정되지 않은 경우에 전역 객체가 저장됨


참고

  • 코어 자바스크립트_정재남
profile
복습 목적 블로그 입니다.

0개의 댓글