실행 컨텍스트 - VE, LE, 호이스팅

최재홍·2023년 4월 5일
0

실행 컨텍스트 객체

실행 컨텍스트 객체는 다음 세가지의 정보를 가지고 있다.

  1. VariableEnvironment
  2. LexicalEnvironment
  3. thisBinding

1. VariableEnvironment

    a. 현재 컨텍스트 내의 식별자 정보(=record)를 갖고 있다.
       ex) var a = 3의 경우 var a를 의미

    b. 외부 환경 정보(=outer)를 갖고 있다.

    c. 선언 시점 LexicalEnvironment의 snapshot

2. LexicalEnvironment

    a. VE와 동일하지만, 변경사항을 실시간으로 반영한다.

3. thisBinding

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


VE vs LE

  1. 이 두가지는 담는 항목이 완벽하게 동일하다. 그러나 스냅샷 유지여부가 다르다.
  • VE : 스냅샷 유지
  • LE : 스냅샷을 유지하지 않고, 실시간으로 변경사항을 반영

둘 다 ‘environmentRecord’와 ‘outerEnvironmentReference’로 구성되어 있다. 그리고 environmentRecord가 곧 'record', outerEnvironmentReference가 곧 'outer'라고 할 수 있을 것이다.

결국 실행 컨텍스트를 생성할 때, VE에 정보를 먼저 담은 다음, 이를 그대로 복사해서 LE를 만들고 이후에는 주로 LE를 활용.


LE(1) - environmentRecord(=record)와 호이스팅

LE에 environmentRecord는

  1. 현재 컨텍스트와 관련된 코드의 식별자 정보들이 저장(수집 및 기록)된다. (record)
  2. 수집 대상이 되는 정보는 함수에 지정된 매개변수 식별자, 함수 자체, var로 선언된 변수 식별자 등이다.(기록되는건 식별자이지 변수가 아니다.)
  3. 컨텍스트 내부를 처음부터 끝까지 순서대로 훑어가며 수집한다.(수집하는거지, 실행되는 것이 아니다.)

호이스팅

  1. 변수정보 수집을 모두 마쳤더라도 아직 실행 컨텍스트가 관여할 코드는 실행 전의 상태이다.(JS 엔진은 코드 실행 전 이미 모든 변수정보를 알고 있는 것)

호이스팅 규칙

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

<적용 전ex>

<script>
function a (x) {
	console.log(x);
	var x;
	console.log(x);
	var x = 2;
	console.log(x);
}
a(1);
</script>

<매개변수 적용ex>

<script>
function a () {
	var x = 1;
	console.log(x);
	var x;
	console.log(x);
	var x = 2;
	console.log(x);
}
a(1);
</script>

언뜻 보면, 결과값이 각각 1, undefined, 2로 나올 것 같지만 실제로는 1, 1, 2라는 결과가 나온다. 호이스팅 과정을 거치면 JS엔진은 다음과 같은 순서로 읽기 때문이다.

<호이스팅 적용ex>

<script>
function a () {
	var x;
	var x;
	var x;

	x = 1;
	console.log(x);
	console.log(x);
	x = 2;
	console.log(x);
}
a(1);
</script>

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

<적용전ex>

<script>
function a () {
	console.log(b);
	var b = 'bbb';
	console.log(b);
	function b() { }
	console.log(b);
}
a();
</script>

결과는 undefined, 'bbb', function일 것 같지만 사실은 그렇지 않다. 다음과 같은 호이스팅 결과를 기반으로 읽기 때문이다.

<호이스팅 적용ex>

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

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

	console.log(b);
	console.log(b);
}
a();
</script>

그래서 결과적으로 function, 'bbb', 'bbb'라는 결과가 도출되게 된다.

3. 호이스팅 법칙3 : 2번 법칙으로 부터 비롯된 함수 선언문, 함수 표현식

함수를 정의하는데는 세가지 방식이 있다.

<script>
// 함수 선언문. 함수명 a가 곧 변수명
// function 정의부만 존재, 할당 명령이 없는 경우
function a () { /* ... */ }
a(); // 실행 ok

// 함수 표현식. 정의한 function을 별도 변수에 할당하는 경우
// (1) 익명함수표현식 : 변수명 b가 곧 변수명(일반적 case에요)
var b = function () { /* ... */ }
b(); // 실행 ok

// (2) 기명 함수 표현식 : 변수명은 c, 함수명은 d
// d()는 c() 안에서 재귀적으로 호출될 때만 사용 가능하므로 사용성에 대한 의문
var c = function d () { /* ... */ } 
c(); // 실행 ok
d(); // 에러!
</script>

이런 차이 때문에 다음과 같은 문제가 발생할 수 있다.

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

라는 함수들을 호이스팅하는 과정에 있어서 다음과 같이 호이스팅된다.

<script>
// 함수 선언문은 전체를 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;
};
</script>

console.log(sum(1, 2));는 정상작동하게 되지만, console.log(multiply(3, 4));는 정상적으로 작동하지 않게 된다.


결론적으로 이름이 같은 두 함수가 같은 코드에 있을 때, 함수선언문으로 선언한 함수는 실행 컨텍스트에서 호이스팅 결과에 의해 코드 상 보다 윗줄에 위치한 코드에도 원치 않게 영향을 미치게 된다.

하지만 함수표현식으로 선언한 함수는 실행컨텍스트에서 불필요하게 호이스팅되지 않기 때문에 함수 코드 자기자신 이후의 코드에만 영향을 미치게 된다.

그러므로 협업을 많이 하고, 복잡한 코드일수록, 그리고 전역 공간에서 이루어지는 코드 협업일수록 함수표현식을 활용하는 습관을 들여야 한다.

0개의 댓글