TIL 26 JavaScript - 실행 컨텍스트

Leo·2021년 5월 11일
0

Javascript

목록 보기
10/17
post-thumbnail

실행 컨텍스트

실행 컨텍스트는 실행할 코드에 제공할 환경 정보들을 모아놓은 객체이다. 자바스크립트는 어떤 실행 컨텍스트가 활성화되는 시점에 선언된 변수를 위로 끌어올리고(호이스팅), 외부 환경 정보를 구성하고 this 값을 설정하는 등의 동작을 수행한다. 이로 인해 특이한 현상들이 발생한다.

그리고 이 객체는 자바스크립트 엔진이 활용할 목적으로 생성할 뿐 개발자가 코드를 통해 확인할 수는 없다.

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

여기서 동일한 환경 즉 하나의 실행 컨텍스트를 구성할 수 있는 방법으로 전역공간, eval()함수, 함수 등이 있다.

자동으로 생성되는 전역공간과 eval을 제외하면 우리가 흔히 실행 컨텍스트를 구성하는 방법은 함수를 실행하는 것 뿐이다.

실행컨텍스트 객체에 담기는 정보들은 아래와 같다.

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

VariableEnvironment

VariableEnvironment에 담기는 내용은 LexicalEnvironment와 같지만 최초 실행 시의 스냅샷을 유지한다는 점이 다르다. 실행 컨텍스트를 생성할 때 VariableEnvironment에 정보를 먼저 담은 다음, 이를 그대로 복사해서 LexicalEnvironment를 만들고 이 후에는 LexicalEnvironment를 주로 사용한다.

VariableEnvironment와 LexicalEnvironment의 내부는 environmentRecordouterEnvironmentReference로 구성돼 있다.

LexicalEnvironment

LexicalEnvironment의 한국어 번역은 문서마다 다르다. ‘어휘적 환경’, ‘정적 환경’이라는 단어가 가장 많이 쓰인다. 하지만 ‘사전적인’이라는 표현으로 ‘생각’만하자. 예를들어 “현재 컨텍스트의 내부에는 a, b, c와 같은 식별자들이 있고 그 외부 정보는 D를 참조하도록 구성돼 있다”처럼 컨텍스트를 구성하는 환경 정보들을 사전에서 접하는 느낌으로 모아놓은 것 처럼.

environmentRecord와 호이스팅

environmentRecord에는 현재 컨텍스트와 관련된 코드의 식별자 정보들이 저장된다. 여기서 식별자는 컨텍스트를 구성하는 함수에 지정된 매개변수 식별자, 선언한 함수가 있을 경우 그 함수 자체, var로 선언된 변수의 식별자 등이 식별자에 해당한다. 컨텍스트 내부 전체를 처음부터 끝까지 쭉 훑어나가며 순서대로 수집한다.

변수 정보를 수집하는 과정을 마치고 난 뒤에도 실행 컨텍스트가 관여할 코드들은 실행 전이다. 코드가 실행 전임에도 자바스크립트 엔진은 해당 환경에 대한 변수정보들을 알고 있다. 그러면 자바스크립트 엔진은 식별자들을 최상단으로 끌어올려논 후 다음 실제 코드를 실행한다. 여기서 호이스팅이라는 개념이 나온다. 자바스크립트 엔진이 실제로 끌어올리지는 않지만 그렇게 간주하자는 것이다.

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 정의부만 존재하고 별도의 할당 명령이 없는 것을 의미한다. 함수 선언문은 반드시 함수명이 정의돼 있어야 한다. 함수 선언문은 호이스팅이 발생하면 선언문 자체를 끌어올린다.

//원본코드
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을 별도의 변수에 할당하는 것을 말한다. 함수 표현식은 함수명을 정의하지 않아도 된다. 함수명을 정의하면 기명 함수 표현식 정의하지 않으면 익명 함수 표현식이라고도 한다. 일반적으로는 익명 함수 표현식이라고 부른다.

주의 ☠️
기명 함수 표현식은 외부에서 함수명으로 바로 호출할 수 없다. 기명 함수 표현식이 디버깅 시 어떤 함수인지를 추적하기에 익명 함수 표현식보다 유리했었다.
하지만 이제는 모든 브라우저들이 익명 함수 표현식의 변수명을 함수의 name 프로퍼티에 할당하고있다.

스코프, 스코프 체인, outerEnvironmentReference

스코프란 식별자에 대한 유효범위이다. 어떤 경계 A의 외부에서 선언한 변수는 A의 외부 뿐 아니라 A의 내부에서도 접근이 가능하지만 A의 내부에서 선언한 변수는 오직 A의 내부에서만 접근할 수 있다.

스코프체인이란 식별자의 유효범위를 안에서부터 바깥으로 차례로 검색해나가는 것이다. 그리고 이를 가능하게 해주는게 outerEnvironmentReference이다.

한 가지 예시를 보자.

inner함수 내부에서 a에 접근하려고 하면 무조건 스코프 체인 상의 첫 번째 인자, inner스코프의 a를 찾으려고 한다. 여기서 a식별자가 존재하므로 스코프 체인 검색을 더 진행하지 않고 즉시 inner안의 a 를 반환한다.

즉, inner함수 내부에서 a 변수를 선언했기 때문에 전역 공간에서 선언한 동일한 이름의 a 변수에는 접근할 수 없고 이를 변수 은닉화(variable shadowing)라고 한다.

변수 은닉화를 조금 더 자세히 보기 위에 위 코드 중 일부를 수정했다.

inner함수 내부에 a식별자가 없었다면 1이 출력된다.

변수 은닉화가 되어 inner 함수 내부에 a가 있기 때문에 undefined가 출력된다.

What I Learned?

호이스팅과 스코프에 대해서 공부를 한 번 했었는데 그 때 잘 이해가 가지 않았던 게 다시 보니 이해가 잘 되었다. LexicalEnvironment VariableEnvironment 같이 눈으로 확인할 수 없어서 100% 이해를 했다기 보다는 70~80%정도 이해가 됐다. 나중에 다시 볼 때는 99%이해 했으면 좋겠다.

Reference

  • 코어자바스크립트(정재남 지음)
profile
느리지만 확실하게

0개의 댓글