JavaScript

[코어 자바스크립트] 02. 실행 컨텍스트

개발하는 크롱 2021. 8. 5. 02:04
반응형

01) 실행 컨텍스트란?

  • 실행 컨텍스트: 실행할 코드에 제공할 환경 정보들을 모아놓은 객체

동일한 환경에 있는 코드들을 실행할 때 필요한 환경 정보들을 모아 컨텍스트를 구성하고, 이를 콜 스택에 쌓아올렸다가, 가장 위에 쌓여있는 컨텍스트와 관련있는 코드를 실행하는 식으로 전체 코드의 환경과 순서를 보장함.

  • “동일한 환경”이란? 하나의 실행 컨텍스트를 구성할 수 있는 방법으로 전역공간, eval() 함수, 함수 등이 있음.
  • 자동으로 생성되는 전역공간과, eval을 제외하면 우리가 흔히 실행 컨텍스트를 구성하는 방법은 함수 실행뿐임.

 

  • 처음 자바스크립트 코드를 실행하는 순간 전역 컨텍스트가 콜 스택에 담김. 최상단의 공간은 코드 내부에서 별도 실행 명령이 없어도 브라우저에서 자동 실행하므로 자바스크립트 파일이 열리는 순간 전역 컨텍스트 활성화됨.
  • 전역 컨텍스트와 관련된 코드를 순차적으로 진행하다가 함수를 호출하면 자바스크립트 엔진은 해당 함수에 대한 환경 정보를 수집해서 해당 함수의 실행 컨텍스트를 생성한 후 콜 스택에 담음. 콜 스택 맨 위에 해당 함수의 실행 컨텍스트가 놓인 상태가 됐으므로 전역 컨텍스트와 관련된 코드의 실행을 일시중단하고 해당 함수의 실행 컨텍스트와 관련된 코드, 즉 해당 함수 내부 코드를 순차로 실행.

 

  • 스택 구조 상 한 실행 컨텍스트가 콜 스택의 맨 위에 쌓이는 순간이 곧 현재 실행할 코드에 관여하게 되는 시점. (기존 컨텍스트는 더 아래에 위치하기 때문)

 

  • 어떤 실행 컨텍스트가 활성화될 떄 자바스크립트 엔진은 해당 컨텍스트에 관련된 코드를 실행하는 데 필요한 환경 정보를 수집해서 실행 컨텍스트 객체에 저장.
  • 실행 컨텍스트 객체는 js엔진이 활용할 목적으로만 생성할 뿐, 개발자는 확인 불가.

 

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

 

02) VariableEnvironment

  • “선언 시점”의 LexicalEnvironment의 스냅샷으로 변경사항은 반영 안됨.
  • 실행 컨텍스트 생성 시 VariableEnvironment에 정보를 먼저 담은 다음, 이를 그대로 복사해서 LexicalEnvironment를 만들고, 이후에는 LexicalEnvironment를 주로 활용.

 

  • VariableEnvironment와 LexicalEnvironment의 내부는 environmentRecord와 outerEnvironmentReference로 구성. 초기화 과정 중에는 둘이 완전히 동일하고 이후 코드 진행에 따라 서로 달라짐.

 

03) LexicalEnvironment

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

 

1. environmentRecord와 호이스팅

  • environmentRecord에는 현재 컨텍스트와 관련된 코드의 식별자 정보들이 저장됨: 컨텍스트를 구성하는 함수에 지정된 매개변수 식별자, 선언한 함수가 있을 경우 그 함수 자체, var로 선언된 변수의 식별자 등.
  • 컨텍스트 내부 전체를 처음부터 끝까지 훑어나가며 순서대로 수집함.
  • 변수 수집 과정을 모두 마쳤더라도 실행 컨텍스트가 관여할 코드들은 아직 실행 전. => 코드 실행 전임에 불구하고 자바스크립트 엔진은 이미 해당 환경에 속한 코드의 변수명을 모두 알게 있게 됨. => 호이스팅. 실제로 식별자들을 최상단에 끌어올리는 것은 아니나 그렇게 이해해도 무방하므로, 변수정보 수집 과정을 더욱 이해하기 쉬운 방법으로 대체한 가상의 개념.

 

  • c.f) 전역 실행 컨텍스트는 변수 객체 생성 대신 자바스크립트 구동환경이 별도로 제공하는 “전역 객체”(global object)를 활용. 전역 객체에는 브라우저의 window, Node.js의 global 객체 등이 있음. 이들은 자바스크립트 네이티브 객체가 아니라 호스트 객체로 분류됨.

 

  • <정리> 어떤 함수가 호출되면 자바스크립트 엔진이 해당 함수를 실행하는 데 필요한 환경 정보 수집해서 실행 컨텍스트 객체 생성 후 콜 스택에 담음. 이렇게 환경정보를 수집하는 과정에서 컨텍스트 내부 전체의 식별자 정보를 모두 수집하므로 코드 실행 전에 해당 환경에 속한 코드의 모든 변수명을 알게 되는 것. 이것을 마치 “‘변수명’을 모두 최상단에 끌어 올린 다음 코드를 실행하는 것”(호이스팅)으로 생각하더라도 코드를 해석하는 데 문제없음.

 

[호이스팅 규칙]

  • environmentRecord는 현재 실행될 컨텍스트의 대상 코드 내에 어떤 식별자들이 있는지에만 관심이 있고, 각 식별자에 어떤 값이 할당될 것인지는 관심이 없음. 따라서 호이스팅 시 변수명만 끌어올리고 할당 과정은 원래 자리에 남겨둠.
  • 변수는 선언부와 할당부를 나누어 선언부만 끌어올리는 반면 함수 선언은 함수 전체를 끌어올림.
  • 호이스팅이 끝난 상태에서의 함수 선언문은 함수명으로 선언한 변수에 함수를 할당한 것처럼 여길 수 있음.
    // ex)
    function b ( ) { } // var b = function b ( ) { }

 

[함수 선언문과 함수 표현식]

  • 둘 모두 함수를 새롭게 정의할 때 쓰이는 방식
  • 함수 선언문: function 정의부만 존재하고 별도의 할당 명령이 없는 것. 반드시 함수명이 정의되어 있어야 함.
  • 함수 표현식: 정의한 function을 별도의 변수에 할당하는 것을 말함. 함수명 정의 필요없음. 함수명을 정의한 함수 표현식을 ‘기명 함수 표현식’, 정의하지 않은 것을 ‘익명 함수 표현식’이라고 부르기도 함. 일반적으로 함수 표현식은 익명 함수 표현식을 말함.
function a () {  /*….*/  }     // 함수 선언문. 함수명 a가 곧 변수명.
a();     // 실행 OK

var b = function () {  /*….*/  }     // (익명) 함수 표현식. 변수명 b가 곧 함수명.
b();     // 실행 OK

var c = function d () {  /*….*/  }     // 기명 함수 표현식. 변수명은 c, 함수명은 d.
c();     // 실행 OK
d();     // 에러!

 

  • 기명 함수 표현식 주의점: 외부에서는 함수명으로 함수 호출 불가. 함수명은 오직 함수 내부에서만 접근 가능. 해당 함수 내에서 재귀 호출용도로 사용 가능. 과거에 디버깅 편해서 사용했는데 이제는 필요 없음.

 

  • 함수 선언문과 함수 표현식은 호이스팅 시 큰 차이 발생.
  • 함수 선언문은 전체를 호이스팅한 반면 함수 표현식은 변수 선언부만 호이스팅함.
  • 함수 선언문으로 선언한 함수는 아래에서 선언하더라도 위에서 문제없이 실행됨. 이때문에 어색하고 실수를 야기할 수 있음.

 

2. 스코프, 스코프 체인, outerEnvironmentReference

  • 스코프: 식별자에 대한 유효범위
  • 스코프 체인: ‘식별자에 대한 유효범위’를 안에서부터 바깥으로 차례로 검색해 나가는 것

 

  • ES5까지의 자바스크립트: 오직 함수에 의해서만 스코프가 생성됨(전역공간 제외).
  • ES6부터는 다른 언어와 비슷하게 블록에 의해서도 스코프 경계가 발생하게 함. var로 선언한 변수에는 작용하지 않고 오직 새로 생긴 let, const, class, strict mode에서의 함수 선언 등에 대해서만 범위로서의 역할 수행

 

[스코프 체인 예시]

  • A 함수 내부에 B 함수를 선언하고 다시 B 함수 내부에 C 함수를 선언한 경우, 함수 C의 outerEnvironmentReference는 함수 B의 LexicalEnvironment를 참조함
  • 함수 B의 LexicalEnvironment에 있는 outerEnvironmentReference는 다시 함수 B가 선언되던 때의 함수 A의 LexicalEnvironment를 참조함
  • 이처럼 outerEnvironmentReference는 연결리스트 형태를 띔

 

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

 

  • 각 outerEnvironmentReference는 오직 자신이 선언된 시점의 LexicalEnvironment만 참조하고 있으므로 가장 가까운 요소부터 차례로 접근할 수 있고 다른 순서로 접근은 불가함.
  • 따라서 여러 스코프에서 동일한 식별자를 선언한 경우, 무조건 스코프 체인 상에서 가장 먼저 발견된 식별자에만 접근 가능하게 됨

 

  • 어떤 변수에 접근하려고 하면 스코프 체인을 쭉 따라가다가 제일 먼저 발견하는 변수의 값을 반환. 전역 컨텍스트까지 탐색해도 못 찾으면 undefined 반환.

 

  • 스코프 체인 상에 있는 변수라고 모두 접근 가능한 것은 아님. 동일한 이름의 식별자 여러번 선언한 경우 가장 가까운 식별자에만 접근 가능. 더 상위의 동일한 이름의 변수에는 접근 불가능. => 변수 은닉화

 

04) this

  • 실행 컨텍스트의 thisBinding에는 this로 지정된 객체가 저장됨
  • 실행 컨텍스트 활성화 당시에 this가 지정되지 않은 경우 this에는 전역 객체가 저장됨

 

  • 전역 컨텍스트의 LexicalEnvironment에 담긴 변수를 전역변수라고 하고, 그 외는 다 지역변수
  • 안전한 코드 구성을 위해 전역변수 사용 최소화할 것.
반응형