실행 컨텍스트(JS Execution Context )

hanbyul.choi·2023년 5월 22일
0

JavaScript 기본

목록 보기
2/3

> 실행 컨텍스트(JS Execution Context)란?

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

어떤 실행 컨텍스트가 활성화되는 시점에 다음과 같은 일을 한다.

  1. 선언된 변수를 위로 끌어올린다. = 호이스팅(hoisting)
  2. 외부 환경 정보를 구성한다.
  3. this 값을 설정한다.

코드를 실행하면서 생성되는 실행컨텍스트를 저장하는 구조가 콜스택(Call Stack)이다.
이름에서 알 수 있듯이 LIFO(Last In First Out)형태.

  1. 함수를 호출하면 실행 컨텍스트가 생성되고, 이를 콜 스택에 추가한 다음 함수를 수행하기 시작한다.
  2. 함수에 의해 호출되는 모든 함수(내부 함수들)는 콜 스택에 추가되고 해당 위치에서 실행한다.
  3. 함수의 실행이 종료되면 해당 실행 컨텍스트를 콜 스택에서 제거한 후 중단 된 시점부터 다시 시작한다.

이해가 되지 않는다면 끝까지 읽고 처음부터 다시 읽어보자.

> 실행 컨텍스트의 구성 및 실행순서

  1. 전역공간
  2. eval()함수
  3. 함수(우리가 흔히 실행컨텍스트를 구성하는 방법)

아래 코드 실행 결과를 예상해보자.

1                           // (1) 전역(in)
2  var a = 1;
3  function outer() {
4 	 function inner() {
5	   console.log(a); //undefined
6	   var a = 3;
7	 }
8	 inner();                // (3) outer(중단) + inner(in)
9	 console.log(a);         // (4) inner(out) + outer(재개)
10  }
11  outer();                 // (2) 전역(중단) + outer(in)
12  console.log(a);          // (5) outer(out) + 전역(재개)
13                           // (6)전역(out)

위 코드는 아래 순서로 진행.
(콜 스택에 쌓이는 실행컨텍스트 순서)

코드실행 → 전역(in) → 전역(중단) + outer(in) → outer(중단) + inner(in) 
→ inner(out) + outer(재개) → outer(out) + 전역(재개) → 전역(out) → 코드종료

결과는 Undefined, 1 ,1 이 순서대로 출력 된다.


> 실행 컨텍스트에 담기는 정보

  1. VariableEnvironment

    1) 현재 컨텍스트 내의 식별자 정보(=record)

    • var a = 3
      위의 경우, var a를 의미

    2) 외부 환경 정보(=outer)

    • 선언 시점 LexicalEnvironment의 snapshot
      즉, 선언 시점의 상태 그대로 유지됨
  2. LexicalEnvironment

    • VariableEnvironment와 구성이 동일하지만, 변경사항을 실시간으로 반영
  3. ThisBinding

    • this 식별자가 바라봐야할 객체

VariableEnvironment, LexicalEnvironment의 비교

이 두가지는 담기는 항목은 완벽하게 동일하다. 그러나, 스냅샷 유지여부가 다르다.

  1. VE : 스냅샷을 유지.
  2. LE : 스냅샷을 유지하지 않는다. 즉, 실시간으로 변경사항을 계속해서 반영

결론적으로 실행컨텍스트에 실행시점에만 두가지가 같고 이후 스냅샷 유지여부가 다르다.


LE의 구성을 다시보면,

  1. environmentRecord(=Record)

    • 현재 컨텍스트와 관련된 코드의 식별자 정보들이 저장.
    • 함수에 지정된 매개변수 식별자, 함수자체, var로 선언된 변수 식별자 등
    • 이 레코드의 수집 과정이 호이스팅(Hoisting)
  2. outerEnvironmentReference(=Outer)

  3. this Binding

    2, 3번 항목은 아래에서 설명 예정


> 호이스팅(Hoisting)의 개념과 법칙

호이스팅은 "변수정보 수집을 모두 마친 상태" + "아직 실행 컨텍스트가 관여할 코드는 실행 전의 상태" 의 가상 개념.
(JS 엔진은 코드 실행 전 이미 모든 변수정보를 알고 있는 것)

호이스팅 법칙 1 : 매개변수 및 변수는 선언부를 호이스팅

적용 전

function a (x) {
	console.log(x);
	var x;
	console.log(x);
	var x = 2;
	console.log(x);
}
a(1);

매개변수 적용

function a () {
	var x = 1;
	console.log(x);
	var x;
	console.log(x);
	var x = 2;
	console.log(x);
}
a(1);

// 예상 결과
// 1 → undefined → 2

호이스팅 적용

function a () {
	var x;
	var x;
	var x;

	x = 1;
	console.log(x);
	console.log(x);
	x = 2;
	console.log(x);
}
a(1);

// 실행 결과
// 1
// 1
// 2 

호이스팅 법칙 2 : 함수 선언은 전체를 호이스팅

적용 전

function a () {
	console.log(b);
	var b = 'bbb';
	console.log(b);
	function b() { }
	console.log(b);
}
a();

// 예상 결과
// undefined → 'bbb' → b함수

호이스팅 적용

function a () {
	var b; // 변수 선언부 호이스팅
	function b() { } // 함수 선언은 전체를 호이스팅

	console.log(b);
	b = 'bbb'; // 변수의 할당부는 원래 자리에

	console.log(b);
	console.log(b);
}
a();

// 실행 결과
// b함수 → 'bbb' → 'bbb'

주의할 점

var로 선언된 식별자는 호이스팅 시 undefined로 초기화 된다.
따라서 값이 할당되지 않아도 사용가능하며 에러가 나지 않는다.

그러나 let, const의 경우는 호이스팅 시 따로 초기화되지 않는다.
즉, 어떠한 값도 할당하지 않기 때문에 데이터 할당 전에 식별자를 사용하게 된다면 에러가 발생한다.

이 내용을 보면 알 수 있듯이 var를 사용하면 안전한 코드작성이 되기 어렵다.


함수 선언문과 함수 표현식의 차이

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;
}

위의 코드를 호이스팅하면,

// 함수 선언문은 전체를 hoisting
function sum (a, b) { // 함수 선언문 sum
	return a + b;
}

// 변수는 선언부만 hoisting

var multiply; 

console.log(sum(1, 2));
console.log(multiply(3, 4));

multiply = function (a, b) { // 이 지점에서 변수에 함수가 할당됨.
	return a + b;
};

이렇게 함수 선언문함수 표현식hoisting 과정에서 극명한 차이가 생긴다.

여기서 함수 선언문을 주의해야 하는데, 그 이유는 아래코드에서 볼 수 있다.

...

console.log(sum(3, 4));

// 함수 선언문으로 짠 코드
// 100번째 줄 : 시니어 개발자 코드(활용하는 곳 -> 200군데)
// hoisting에 의해 함수 전체가 위로 호이스팅
function sum (x, y) {
	return x + y;
}

...
...

var a = sum(1, 2);

...

// 함수 선언문으로 짠 코드
// 5000번째 줄 : 신입이 개발자 코드(활용하는 곳 -> 10군데)
// hoisting에 의해 함수 전체가 위로 호이스팅
function sum (x, y) {
	return x + ' + ' + y + ' = ' + (x + y);
}

...

var c = sum(1, 2);

console.log(c);

// 즉 시니어가 만든 sum함수가 신입 개발자가 만든 함수로 변경됨.

이걸 함수표현식으로 쓰게 된다면,

...

console.log(sum(3, 4));

// 함수 표현식으로 짠 코드
// 함수 선언부만 위로 호이스팅
// 이 이후부터의 코드만 영향을 받아요!
var sum = function (x, y) {
	return x + y;
}

...
...

var a = sum(1, 2);

...

// 함수 표현식으로 짠 코드
// 함수 선언부만 위로 호이스팅
// 이 이후부터의 코드만 영향을 받아요!
var sum = function (x, y) {
	return x + ' + ' + y + ' = ' + (x + y);
}

...

var c = sum(1, 2);

console.log(c);

// 즉, 함수가 변경된 시점부터 해당 함수가 적용된다.

복잡한 코드일수록, 전역 공간에서 이루어지는 코드 협업일수록 함수 표현식을 활용하는 습관이 중요함.

여기까지가 LE의 구성 중 environmentRecord(=Record)해당하는 부분이다.

이제 outerEnvironmentReference(=Outer)를 알아보자.


> outerEnvironmentReference(=Outer)

먼저 스코프(scope)란 식별자에 대한 유효범위를 의미한다.

[스코프 체인(Scope Chain)]

스코프 체인은 변수 및 함수의 유효 범위를 나타내는 개념이다. 간단히 말하면, 식별자의 유효범위를 안에서부터 바깥으로 차례로 검색해나가는 것.

function outer() {
  var x = 10;

  function inner() {
    var y = 20;

    function nested() {
      var z = 30;

      function deeplyNested() {
        var w = 40;
        console.log(x + y + z + w);
      }

      deeplyNested();
    }

    nested();
  }

  inner();
}

outer(); // 100 출력

위의 코드를 예시로 살펴보면,
deeplyNested() 함수 내에서 변수인 x,y,z,w 모두 사용이 가능하다. deeplyNested()가 불린 곳 즉, nested()에 외부에서 변수를 참조할 수 있기 때문에 이런 형식으로 스코프체인에 의해서 전부 참조가 가능하게 되는 것이다.

Outer스코프 체인이 가능토록 '외부 환경의 참조정보'라고 할 수 있다.
스코프 체인을 통해 사용할 수 있는 변수나 함수를 담고 있다고 보면 된다.

다시 말해, 현재 실행 컨텍스트에서 바로 외부 스코프에 있는 변수나 함수 혹은 스코프 체인을 통해 넘어온 변수나 함수를 사용할 수 있다는 것이다.


> this

this는 실행 컨텍스트가 생성될 때 결정된다. 이 말을 thisbinding한다 라고 함.

this에 관한 내용은 아래링크를 참고하자.
https://velog.io/@hanbyul1025/this-%ED%8C%8C%ED%97%A4%EC%B9%98%EA%B8%B0


여기까지 실행컨텍스트에 관한 내용 끝.

0개의 댓글