[Javascript] 실행 컨텍스트

Teasan·2022년 7월 14일
0

JavaScript

목록 보기
14/15
post-thumbnail

실행 컨텍스트


실행 컨텍스트(Execution Context)란, 코드를 실행할 때 필요한 배경이 되는 조건, 환경을 의미한다. 즉, 어떤 동일한 조건/환경 정보를 지니는 코드 뭉치가 있을 때 그 조건과 환경정보를 일컬어 바로 실행 컨텍스트라고 하는 것이다.

그러면 동일한 조건이나 환경을 지니는 코드 뭉치란 어떤 걸 의미하는 것일까? 자바스크립트에서 동일한 조건을 지닐 수 있는 경우는 몇 가지가 있다.

동일한 조건이나 환경을 지니는 코드 뭉치

  • 전역 공간
  • 함수
  • module

eval 도 있지만 여러 문제를 야기하는 위험한 명령어이기에 논외로 한다.

전역 공간
전역 공간은 자바스크립트 코드가 실행되는 순간에 바로 전역 컨텍스트가 생성되고 전체 코드가 끝날 때에 비로소 전역 컨텍스트가 종료된다. 그러니 이를 하나의 거대한 함수 공간이라도 봐도 될 것이다.

module
모듈 역시 어딘가에서 import 되는 순간에 그 모듈 내부에 있는 컨텍스트가 생성 되고, 모듈 코드가 전부 끝났을 때 컨텍스트가 종료된다. 그러니 이 역시 하나의 함수 공간이라고 간주를 해도 될 것이다.

함수
전역공간과 module을 하나의 함수 공간이라고 간주를 한다면, 결국 자바스크립트의 독립된 코드 뭉치라고 할 수 있는 것은 곧 함수 라고 볼 수 있을 것이다. 전역 공간, 모듈, 또는 함수로 묶인 내부에서는 결국 '같은 환경 안에 있다' 라는 것이 성립되기 때문이다.

if문, for문, switch문, while문 같은 조건문 혹은 반복문이라면 어떨까?

ES6에서 '블록 스코프' 개념이 추가된 이후 이 '블록 스코프'는 let과 const를 대상으로 별개의 독립된 공간으로서의 역할을 하고 있지만 별개의 실행 컨텍스트를 생성하지는 않는다.

자바스크립트는 오직 함수에 의해서만 컨텍스트를 구분한다.

결국 실행 컨텍스트는 '함수를 실행할 때 필요한 조건, 환경 정보'를 담은 '객체'라고 볼 수 있다. 실제 예시를 통해 실행 컨텍스트를 알아가보자.

실행 순서 예시

let a = 1;
function outer() {
	console.log(a); // 1 => 1이 출력

	function inner() {
		console.log(a) // 2 => undefined 가 출력
		let a = 3;
	};

	inner();
	console.log(a); // 3 => 1이 출력
};

outer(); 
console.log(a); // 4 => 1이 출력

이 코드가 실행됐을 때 제일 먼저 열리는 건 무엇일까? 바로 전역 컨텍스트다. 전역 공간을 차례대로 실행한다.

전역 컨텍스트의 outer 함수는 일단 함수의 선언일 뿐 실행 명령이 아니기 때문에 일단 넘어간다. 그리고 이후에 해당 함수를 호출하는 outer() 명령을 만나게 된다.

outer 함수를 호출하면 outer 함수의 실행 컨텍스트가 열리고 해당 함수에 대해서도 차례대로 실행을 하게 되며, 위의 그림의 console.log(a) 가 첫 번째로 실행된다.

outer 함수 내부의 코드를 차례대로 실행하며, inner 함수가 호출 될 때까지는 inner 는 일단 넘어간다. 그리고 이후에 inner() 명령어를 통해 또 다시 inner 함수의 실행 컨텍스트가 열리며,inner 함수의 내부를 차례대로 모두 실행한 뒤 위의 그림의 2번인 console.log(a) 가 두 번째로 실행된다.

inner 에 대한 호출이 모두 종료되면, 그 다음 줄 3번의 console.log(a)가 세 번째로 실행되고

outer 함수의 실행컨텍스트가 종료되면 그 다음 줄 4번인console.log(a)가 네 번째로 실행된다.

위의 자바스크립트 코드는 이런 실행 순서로 돌아가는 걸 알 수 있다.

전역 컨텍스트 → outer 함수 컨텍스트 → inner 함수 컨텍스트 → inner 함수 컨텍스트 제거 → outer 함수 컨텍스트 제거 → 전역 컨텍스트 제거

처음에는 가장 바깥의 전역 컨텍스트가 있다가, outer 함수의 컨텍스트로 이동한다. 이후 inner 함수로 이동한 뒤 inner 함수 내부의 코드 모두가 실행이 끝나면 제거된 뒤에 다시 outer 함수를 내부를 실행하고 제거 된다. 그리고 마지막으로 다시 바깥의 전역 컨텍스트가 더 이상 실행될 것이 없다고 여겨졌을 때 비로소 종료된다.

이렇듯, 제일 마지막에 들어온 게 먼저 빠지고 제일 먼저 들어온 게 제일 나중에 빠지는 개념을 '스택'이라고 하며, 코드 실행에 관여하는 스택을 바로 '콜 스택' 이라 한다.

콜 스택

콜 스택은 현재 어떤 함수가 동작 중인지, 다음에 어떤 함수가 호출될 예정인지 등을 제어하는 자료구조를 의미한다. 이 콜스택이 어떤 식으로 동작하는지 동일한 코드를 바탕으로 살펴보자.

아래 그림처럼 우물처럼 생긴 것이 바로 콜스택이다. 처음에는 비어있다가, 최초의 전역 공간에 대한 컨텍스트가 처음으로 콜스택에 쌓인다.

그리고 차례대로 outer 호출을 만나서 outer 컨텍스트가 콜스택에 쌓이면, inner 호출을 만나서 inner 컨텍스트가 콜스택에 쌓인다. 콜스택의 우물에 가장 맨 위에 쌓여있는 것이 현재 실행 중인 컨텍스트인 것이다.

inner 실행 컨텍스트가 제거되고, outer 실행 컨텍스트가 실행-제거되고, 마지막으로 전역 컨텍스트가 실행되어 제거된다면 콜 스택에는 이제 아무 것도 남지 않게 된다. 콜 스택 안에 아무 것도 남아있지 않다면 더이상 실행할 내용이 없기에 실행 컨텍스트는 종료하게 된다.

실행 컨텍스트의 내부

실행 컨텍스트 내부를 좀 더 자세히 살펴보자.
inner 에는 세 가지의 컨텍스트 환경 정보가 담겨있다.

  1. VariableEnvironment
  2. LexicalEnvirionment
  3. ThisBinding

이 중에서 1번의 VariableEnvironment 와 2번의 LexicalEnvirionment는 현재 환경과 관련된 '식별자 정보들'이 담긴다.

VariableEnvironmentLexicalEnvirionment의 차이

VariableEnvironment 는 오직 '식별자 정보를 수집'하는 용도로만 사용되고,
LexicalEnvirionment 는 '각 식별자에 담긴 데이터를 추적'하는 용도로 사용된다.

만약 변수의 값들에 변화가 생기면, 그 값은 LexicalEnvirionment 에만 실시간으로 반영된다. 그렇기에 VariableEnvironmentLexicalEnvirionment 의 차이는 값의 변화가 실시간으로 반영되느냐 그렇지 않느냐에 달려있는 것이나 다름 없다. 이 두가지의 차이를 알게 됐으니 Variable EnvironmentLexical Environment 의 특징을 정리해보자.

  • Variable Environment
    최초에는 식별자 정보를 가지고 있지만 값은 변하지 않는다.

  • Lexical Environment(어휘적/사전적 환경)
    어떤 실행 컨텍스트 a 에 대한 환경 정보가 담겨 있는 '사전' 이라고 보면 된다. 즉, 실행컨텍스트를 구성하는 환경 정보들을 모아 '사전'처럼 구성한 객체를 의미한다.

    1. environmentRecord : 현재 문맥의 식별자(hoisting)
      → 현재 컨텍스트 내부의 식별자 정보를 수집
    2. outerEnvironmentReference : 외부 식별자(scope chain)
      → 외부 환경을 참조

Lexical Environment 에는 두 가지 종류의 환경 정보가 담긴다. 바로 environmentRecordouterEnvironmentReference 이다. 이 두가지는 각각 다른 역할을 수행한다.

1. environmentRecord(현재 컨텍스트 내부의 식별자 정보 수집)

environmentRecord 는 현재 문맥의 식별자 정보를 수집한다. 실행 컨텍스트가 최초 실행할 때 제일 먼저 하는 일이 바로 이 식별자 정보를 수집하는 일이다. 그리고 이 현재 컨텍스트 식별자 정보들을 수집해서 environmentRecord 에 담는 과정에서 발생하는 현상을 바로 호이스팅(hoisting) 이라고 한다.

→ 호이스팅(hoisting)

호이스팅은 실제하는 현상이 아니라, 우리가 environmentRecord의 정보 수집 과정을 좀 더 쉽게 이해하기 위해 만든 허구의 개념이라 할 수 있다.

호이스팅(hoisting)의 뜻은 "끌어 올리다" 이다. 그럼 무엇을 "끌어 올리는" 것일까? 바로 '식별자 정보'를 실행 컨텍스트의 최상단으로 끌어올린다.

아래의 예시를 통해 '호이스팅'의 방식을 살펴보자.

왼쪽의 코드를 바탕으로 현재 컨텍스트 식별자 정보들을 수집해 environmentRecord 담는 과정에서 오른쪽 그림의 순서로 hoisting 된다. 끌어올려(hoisting)보면, ⚡️ 함수 선언문 ⚡️같은 경우(a 함수) 다른 식별자와 다르게 함수 선언문 전체가 가장 최상단으로 끌어올려지는 걸 알 수 있다. 그리고 차례대로 b와 c 가 hoisting 된다. 그렇게 최종적으로 호이스팅이 완료된 결과를 확인해보면,

오른쪽 그림에서 위에 끌어올려진 내용 전체가 바로 environmentRecord 이다. 실행 컨텍스트가 처음 생성되는 순간에 가장 먼저 environmentRecord 에 이 정보들을 수집한다. 현재 컨텍스트에서 선언되어 있는 식별자 정보를 코드 순서대로 수집을 하다보니, hoisting을 한 것과 똑같은 개념이 되버린 것이다. 오른쪽의 순서를 바탕(이것은 임의로 이해하기 쉽게 만들어놓은 허구의 개념일 뿐이라는 걸 기억하자)으로 environmentRecord 로 수집한 정보들(왼쪽)을 확인해보자.

2. outerEnvironmentReference (외부 환경 참조)

outerEnvironmentReference 는 "외부 환경에 대한 참조"를 한다. 그리고 여기서 말하는 환경이란 바로 'LexicalEnvironment' 이다. 그러니까 '외부의 환경' 이라는 것은 '외부의 LexicalEnvironment 에 대한 참조'라는 의미가 된다. 즉, 현재 문맥과 관련있는 '외부 컨텍스트'의 식별자 정보를 참조하는 것이다.

현재 실행중인 컨텍스트가 inner인 경우, inner 의 LexicalEnvironment 에는 environmentRecordouterEnvironmentReference 가 있다. 이 outerEnvironmentReference 는 바로 그 밑에 있는 outer 라는 실행 컨텍스트의 LexicalEnvironment 전체를 '참조' 한다. 그리고 outer 실행 컨텍스트에 있는 outerEnvironmentReference는 또 바로 그 아래에 있는 전역 컨텍스트의 LexicalEnvironment 전체를 '참조' 한다.

이렇게 outerEnvironmentReference 가 관여하는 동작 방식을 바로 scope chain 이라고 하는 것이다.

스코프 체인(scope chain)

스코프 체인 현상은 outerEnvironmentReference 에 의해서 만들어진다.

스코프(scope)
스코프는 변수의 유효범위를 의미한다. 그리고 이 변수의 유효범위는 실행 컨텍스트가 만든다. 실행 컨텍스트가 수집해놓은 정보에만 접근할 수 있고, 그 변수는 실행 컨텍스트 내부에서만 존재하기 때문이다. 그러니 스코프, 즉 변수의 유효범위라고 하는 것은 결국 실행 컨텍스트에 의해서 결정되는 것이다.

inner 컨텍스트에서 선언한 변수는 environmentRecord 에 의해서 접근할 수 있다. 그리고 outerEnvironmentReference 를 통해 outer 컨텍스트의 LexicalEnvironment 정보에도 접근할 수 있다.
outer 컨텍스트에서는 outer 내부에서 선언한 식별자(environmentRecord)들과 전역 컨텍스트에서 선언한 변수에 접근이 가능하다.
하지만, inner 에서 선언한 변수들은 outer 컨텍스트에서는 접근이 불가능하다. 왜냐하면, outer 에는 inner의 LexicalEnvironment 에 대한 접근할 수 있는 '수단'이 없기 때문이다. (즉, 참조하고 있는 대상이 없기 때문이다.) 그러니까 outer 컨텍스트의 LexicalEnvironment 에 inner 에 대해서 수집해놓은 정보가 없기 때문에 inner 에서 선언한 변수에는 outer가 접근을 하지 못하는 것이다. 그리고 이것을 바로 스코프(scope)라고 한다.

스코프는 외부에서 내부로 접근할 수 없다.

스코프는 내부에서 외부로 나갈 수 있지만, 외부에서 내부로는 접근할 수 없다. 이는 곧 변수의 유효 범위를 결정하게 되는 것이다. 위의 사례를 다시 한 번 살펴보자.

  1. inner 함수에서 선언한 변수의 유효범위는 inner 함수 내부에서만 국한된다. inner 함수의
    environmentRecord 는 오직 inner 안에서만 존재하는 것이다.
  2. outer 함수에서 선언한 변수는 전역에서는 접근할 수 없지만, outerinner에서 모두 접근이 가능하다. innerouterEnvironmentReference 를 통해서 outer의 LexicalEnvironment 에 접근할 수 있기 때문이다.
  3. 반면, 전역 공간 에서 선언한 변수는 outerinner 모두에서 접근이 가능하다.

만약 inner 에서 어떤 변수를 찾으라면서 명령을 하게 되면, 일단 innerinner 내부의 environmentRecord 에서 그 변수를 찾게 된다. 그러나 만약 inner 에 찾는 변수가 없다면, outerEnvironmentReference 를 타고 outer 에 있는 LexicalEnvironment → environmentRecord 에서 해당 변수를 찾게 된다. 또 outer 에도 찾는 변수가 없다면, outer의 마지막으로 outerEnvironmentReference 를 타고 전역 컨텍스트 에 있는 LexicalEnvironment → environmentRecord 에서 해당 변수를 찾게 된다.

그리고 이러한 동작 방식이 바로 '스코프 체인'이다. 간단하게 이야기하자면, 스코프 체인이란 가장 가까운 자기 자신부터 점점 멀리 있는 스코프로 찾아 나가는 것 을 의미한다.

쉐도잉 Shadowing
가장 먼저 찾아진 것만 접근할 수 있는 개념을 바로 Shadowing 이라고 한다.


실행 컨텍스트의 흐름

처음에 살펴봤던 코드를 토대로 우리가 지금까지 배운 것들을 이용해서 실행 컨텍스트의 흐름을 다시 한 번 따라가보자.

  1. 전역 변수 컨텍스트가 활성화 된다.

  1. 전역 컨텍스트가 실행 된다.
    1. var a; 변수 a 선언 → 식별자 정보를 수집(environmentRecord)
    2. function outer(){}; 함수 outer 선언 → 식별자 정보를 수집(environmentRecord)
    3. a = 1; 변수 a에 1 을 할당
    4. outer(); outer 함수를 호출 → outer 의 실행 컨텍스트 활성화

  1. outer 의 실행 컨텍스트가 실행 된다.
    1. function inner(){}; 함수 inner 선언 → 식별자 정보를 수집(environmentRecord)
    2. console.log("outer 1", a);outer 컨텍스트에서 a를 탐색(없음) → 전역 컨텍스트에서 a를 탐색(있음) → 1을 출력
    3. inner();inner 함수 호출 → inner 실행 컨텍스트 활성화

  1. inner 실행 컨텍스트가 실행 된다.
    1. var a; 변수 a 선언 → 식별자 정보를 수집(environmentRecord)
    2. console.log("inner", a);inner 컨텍스트에서 a를 탐색(inner 컨텍스트 내부의 변수 a의 값을 할당하기 전) → undefined 출력
    3. a = 3; → 변수 a에 3 을 할당
    4. inner 컨텍스트 종료되고, 스택이 사라진다.

  1. outer 컨텍스트가 다시 실행 된다.
    1. console.log("outer2", a);outer 컨텍스트에서 a를 탐색(없음) → 전역 컨텍스트에서 a를 탐색(있음) → 1을 출력
    2. outer 컨텍스트 종료되고, 스택이 사라진다.

  1. 전역 컨텍스트가 다시 실행 된다.
    1. console.log("전역", a) → 전역 컨텍스트에서 a를 탐색 → 전역 컨텍스트에서 a를 탐색(있음) → 1을 출력
    2. 전역 컨텍스트 종료되고, 스택이 사라진다.
  2. 스택이 사라짐으로써 자바스크립트 코드 전체가 사라진다.

정리


실행 컨텍스트 Execution Context
Execution Context는 함수를 실행할 때 필요한 환경 정보를 담은 객체를 의미한다. 그리고 이 객체 안에는 Variable Environment, Lexical Environment, this 가 있다.

  1. this

  2. Variable Environment
    최초에는 식별자 정보를 들고 있지만 그 값이 계속 변하지 않는다.

  3. Lexical Environment
    실행 컨텍스트의 실행 내용에 따라서 변수 값이 바뀔 때 그 변경 사항을 계속 트랙킹을 하는 정보를 의미.
    environmentRecord : 현재 문맥의 식별자(hoisting 개념)
    : 현재 문맥의 식별자를 수집하는데, hoisting 이라는 개념과 일치.
    outerEnvironmentReference : 외부 식별자(scope chain 개념)
    : 외부 식별자 정보를 참조하는데 이 참조에 의해서 스코프 체인이라는 개념이 등장.


✦ 출처


🚨 해당 포스팅은 정재남의 ⌜코어 자바스크립트⌟ 강의와 동명의 책을 베이스로 한 기록입니다.
🔖 표와 그림은 전부 제가 한땀한땀 그린 것입니다.. 불펌 자제 🥸

profile
일단 공부가 '적성'에 맞는 개발자. 근성있습니다.

0개의 댓글