[JavaScript] 실행 컨텍스트

HyungJin Han·2023년 2월 23일
0

JavaScript

목록 보기
6/7
post-thumbnail

1. Execution Context(실행 컨텍스트)란?

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

JavaScript는 동일한 환경에 있는 환경 정보들을 모은 실행 컨텍스트를 콜스택에 쌓아올린 후 실행하여 코드의 환경과 순서를 보장할 수 있게 된다.

즉, 스택(Stack)의 경우, FILO(First In, Last Out)의 구조이기에 순서를 보장하며, 콜스택 내부에 쌓인 실행 컨텍스트의 정보를 통해 환경을 보장할 수 있는 것이다.

위에서 말한 환경이란 전역 공간이 될 수 있고, 함수 내부의 환경이 될 수도 있다.

var temp = "temp";

function b() {
  console.log("Hello, World");
}

function a() {
  b();
}

a(); // Hello, World

위의 예시와 그림을 참고하면,

  1. 콜스택엔 전역 컨텍스트를 제외하고 다른 컨텍스트가 없기에 전역 컨텍스트와 관련된 코드를 진행한다.

  2. 전역 컨텍스트와 관련된 코드를 진행 중, a 함수를 실행했기 때문에 a 함수의 환경 정보들을 수집하여 a 실행 컨텍스트를 생성하고 콜스택에 담으며, 콜스택 최상단에 a 실행 컨텍스트가 있기 때문에 기존의 전역 컨텍스트와 관련된 코드의 실행을 일시적으로 중단한 후, a 실행 컨텍스트의 코드를 실행한다.

  3. a 함수 내부에서 b 함수를 실행하였기 때문에 b 함수의 환경 정보들을 수집하여 실행 컨텍스트를 생성하고, 콜스택에 담으며, 이전과 똑같이 콜스택 최상단에 b 실행 컨텍스트가 있기 때문에 기존 a 실행 컨텍스트와 관련된 코드의 실행을 일시적으로 중단한다.

  4. b 함수가 종료된 후, b 실행 컨텍스트가 콜스택에서 제거되고, 그 후 콜스택 최상단에는 a 실행 컨텍스트가 있기 때문에 이전에 중단된 지점부터 코드 진행이 재개된다.

  5. a 함수 또한 종료된 후 실행 컨텍스트가 콜스택에서 제거되며, 이후에는 전역 공간에 실행할 코드가 남아있지 않다면 콜스택에서 전역 컨텍스트 또한 제거되며 콜스택에 아무것도 남지 않은 상태로 종료된다.

위의 단계로 JavaScript 코드가 진행된다.


2. 실행 컨텍스트 내의 정보

실행 컨텍스트 내부에는 variable environment, lexical environment, this binding이 있다.

2-1. VariableEnvironment

VariableEnvironment란 현재 컨텍스트 내부의 식별자 정보 environmentRecord, 외부 환경 정보 outerEnvironmentReference가 포함되어 있다.

VariableEnvironment에 먼저 정보를 담고, 그대로 LexicalEnvironment에 복사해 사용한다고 한다.

2-2. LexicalEnvironment

LexicalEnvironment는 초기에는 VariableEnvironment와 같지만 변경 사항이 실시간으로 적용된다.

즉, VariableEnvironment 초기 상태를 기억하고 있으며, LexicalEnvironment 최신 상태를 저장하고 있다.

2-2-1. environmentRecord

environmentRecord란 현재 컨텍스트와 관련된 식별자와 식별자에 바인딩된 값이 기록되는 공간이다.

더불어 실행 컨텍스트 내부 전체를 처음부터 끝까지 확인하며 순서대로 수집한다.

var hhj = "HyungJin Han";

console.log(hhj); // HyungJin Han
console.log(hhj); // undefined

var hhj = "HyungJin Han";

위의 예시를 보면, 첫 코드 예시의 경우 문제없이 작동되나, 두 번째 예시의 경우 선언하기도 전에 값을 호출했지만, Reference error가 발생하지 않고, undefined가 출력된다.

위의 현상은 JavaScript의 호이스팅이라는 현상과 관련있다.

호이스팅에 관련된 포스팅은 이전에 올렸던 포스팅을 보며 재차 공부할 예정이다.

LexicalEnvironmentenvironmentRecord의 경우 해당 컨텍스트 환경에 필요한 식별자와 식별자의 값이 기록되며, 함수 실행 시 실행 컨텍스트가 생성되므로(함수 실행보다 environmentRecord 수집이 먼저 되므로) 변수와 같은 식별자를 끌어올리는 것과 같다는 개념의 호이스팅이 생겨났다.

2-2-2. outerEnvironmentReference

const greeding = "Hello, World";

const info = () => {
  const hhj = {
    age: 28,
    name: "HyungJin Han",
  };
  const jhh = {
    age: 23,
    breed: "HyungHan Jin",
  };
  console.log(greeding);
  console.log(hhj);
  console.log(jhh);
};
info(); // Hello, World { age: 28, name: 'HyungJin Han' } { age: 23, breed: 'HyungHan Jin' }
console.log(hhj); // ReferenceError: hhj is not defined
console.log(jhh); // ReferenceError: jhh is not defined

위의 예시를 보면, 해당 코드의 함수 내부에서는 외부의 greeding이 접근 가능하며, 내부에서 선언한 hhjjhh 또한 접근이 가능하다.

하지만 외부에서는 내부에 선언된 hhjjhh에 접근할 수 없다.

그 이유는 outerEnvironmentReference 때문이다.

outerEnvironmentReference란 현재 호출된 함수가 선언될 당시의 LexicalEnvironment를 참조하게 된다.

여기서 선언될 당시를 집중해야 하는데, info 함수가 선언될 당시의 outerEnvironmentReference는 글로벌 실행 컨텍스트의 LexicalEnvironment를 참조하고 있으며, 해당 환경의 environmentRecordgreeding과 같은 변수의 정보들이 기록되어 있게 된다.

그렇기에 함수 내부에서는 outerEnvironmentReference를 통해 상위 컨텍스트의 LexicalEnvironment에 접근하여 environmentRecord에서 변수인 greeding을 사용할 수 있게 되는 것이다.

또한 outerEnvironmentReference는 오직 자신이 선언될 당시의 LexicalEnvironment를 참조하기 때문에 순차적으로만 접근이 가능하며, 여러 스코프에서 동일한 식별자를 생성하였다고 하더라도 가장 먼저 발견된 식별자만 접근이 가능하다.

const a = "a";
const b = "b";

const sayHi = () => {
  const a = "aa";
  console.log(a); // aa
  console.log(b); // b
};

sayHi();

위의 코드 예제를 보면, a라는 변수는 현재 컨텍스트의 LexicalEnvironment => environmentRecorda라는 식별자가 있고, outerEnvironmentReference => LexicalEnvironment => environmentRecord에도 a 식별자가 있지만, 가장 먼저 발견된(가까운) 식별자에 바인딩 된 값인 aa를 출력한다.

결국 전역 컨텍스트에서 선언한 a의 경우 변수 은닉화가 된다.

b 변수는 현재 컨텍스트의 LexicalEnvironment => environmentRecordb 라는 식별자가 있으므로, outerEnvironmentReference 참조하여 전역 컨텍스트의 LexicalEnvironment 참조, environmentRecordb 식별자에 접근하여 b를 출력한다.

결론적으로 outerEnvironmentReference란 해당 함수가 선언된 위치의 LexicalEnvironment를 참조하며, 변수에 접근을 한다면 해당 LexicalEnvironment에서 발견된다면 사용, 찾지 못할 경우 다시 outerEnvironmentReference를 참조하여 탐색하는 과정을 반복한다.

이러한 과정을 스코프 체인 이라고 하며 outerEnvironmentReference 는 스코프 체인을 가능하게 하는 역할이다.

2-3. ThisBinding

우선 this 는 컨텍스트를 가르킨다.

method에서 사용시 해당 method가 담겨있는 instance or object를 가르키며, 함수표현식에서 사용시 this 를 바인딩하지 않는 이상 전역 객체를 가르킨다.

더불어 전역 공간에서 this는 함수 표현식과 같이 전역 객체를 가르킨다.

ES6의 화살표 함수를 사용하면 this 바인딩, 우회법으로 변수에 this를 담아 실행할 필요 없이 함수를 실행한 컨텍스트를 바라본다.

const obj = {
  outer: function () {
    console.log(this); // { outer: [Function: outer] }
    // 메소드에서 사용되었기에 obj 출력
    function inner() {
      console.log(this); // Object [global] {...}
      // 함수 표현식에서 사용되었기에 전역 객체 출력
    }
    inner();

    const self = this;
    function inner2() {
      console.log(self); // { outer: [Function: outer] }
      // 우회하기 위해 this를 self에 할당, 출력했기에 obj 출력
    }
    inner2();

    const inner3 = () => {
      console.log(this); // { outer: [Function: outer] }
      // 화살표함수 사용으로 this는 상위 스코프의 컨텍스트를 가르킴, obj 출력
    };
    inner3();
  },
};

obj.outer();

요약하자면, this는 실행 컨텍스트 생성 시, 즉, 함수를 실행할 때 할당해주는 것으로 상황에 따라 전역 객체 또는 instance를 카르킨다.


3. 결론

위에서 설명했듯이, 실행 컨텍스트는 VariableEnvironment, LexicalEnvironment, environmentRecord, outerEnvironmentReference, hisBinding 등의 여러 정보들이 합쳐져 실행 컨텍스트가 되고, 이것이 콜스택에 쌓여 JavaScript의 코드가 실행된다.


참고 사이트

감구마- JS Execution Context (실행 컨텍스트) 란?
천천히 꾸준하게 - [javascript 자바스크립트] 실행 컨텍스트(Execution Context) 란?

profile
토끼보다는 거북이처럼 꾸준하게

0개의 댓글