JavaScript 실행 컨텍스트

H_Chang·2023년 10월 17일

실행 컨텍스트

  • 실행할 코드에 제공할 환경 정보들을 모아놓은 객체이다.
  1. 선언된 변수를 위로 끌어올린다. = 호이스팅(hoisting)
  2. 외부 환경 정보를 구성한다.
  3. this 값을 설정한다.
  • 실행 컨텍스트를 이해하기 위해서는, "콜 스택"에 대한 이해가 반드시 필요하다.

  • "콜 스택"을 알기 전에 “스택”이라는 개념에 대해서 밑의 이미지 자료를 통해 이해하자!

콜 스택(call stack)

  • 실행 컨텍스트란 실행할 코드에 제공할 환경 정보들을 모아놓은 객체이다.
  • 개발자가 실행할 코드를 수행할수 있는 여러가지 환경정보를 다 모아 놓은 컨텍스트(정보)를 동일한 환경에서 실행 할때 사용하는 환경정보를 콜스텍에 쌓아 올린다.
  • Javascript는 코드의 환경 및 순서를 보장한다.

실행컨텍스트 구성 예시 코드

// ---- 1번
var a = 1;
function outer() {
	function inner() {
		console.log(a); //undefined
		var a = 3;
	}
	inner(); // ---- 2번
	console.log(a);
}
outer(); // ---- 3번
console.log(a);
  • 실행컨텍스트 구성 순서
    코드실행 → 전역(in) → 전역(중단) + outer(in) → outer(중단) + inner(in) → inner(out) + outer(재개) → outer(out) + 전역(재개) → 전역(out) → 코드종료

  • 결국은 특정 실행 컨텍스트가 생성되는(또는 활성화되는) 시점이 콜 스택의 맨 위에 쌓이는(노출되는) 순간을 의미하고 곧, 현재 실행할 코드에 해당 실행 컨텍스트가 관여하게 되는 시점을 의미한다고 이해하자!

실행 컨텍스트 객체의 실체(=담기는 정보)

1. VariableEnvironment

  • 현재 이 컨텍스트 내에 식별자 정보(=record) 를 가지고 있다.
vat a = 3; // 이라고 할떄
var a // 를 의마한다.
  • 외부 환경 정보(=outer)를 가지고 있다.
  • 정보가 생겼을때의 모습을 그대로 간직하고 있다.(=snapshot)

2. LexicalEnvironment

  • VariableEnvironment와 완전 동일하지만, 변경사항을 실시간으로 반영한다.

3. ThisBinding

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

VariableEnvironment, LexicalEnvironment의 개요

VE vs LE

  1. VE : 스냅샷을 유지한다. (record, outer)를 가지고 있다.
  2. LE : 스냅샷을 유지하지 않는다. 즉, 실시간으로 변경사항을 계속해서 반영한다.
  • 결국, 실행 컨텍스트를 생성할 때, VE에 정보를 먼저 담은 다음, 이를 그대로 복사해서 LE를 만들고 이후에는 주로 LE를 활용한다.

LexicalEnvironment(1) - environmentRocord(=record)와 호이스팅

개요

  1. rocord는 현재 컨텍스트관련 코드의 식별자 정보이다. 이 식별자 정보를 컨텍스트에 기록한다 라고 이해하자!
  2. 수집 대상 정보 : 함수에 지정된 매개변수 식별자, 함수 자체, var로 선언된 변수 식별자 등
  3. 컨텍스트 내부를 처음부터 끝까지 순서대로 훑어가며 수집한다.(실행하지는 않는다!)

호이스팅

  1. 변수정보 수집을 모두 마쳤더라도 아직 실행 컨텍스트가 관여할 코드는 실행 전의 상태이다.
    (JS 엔진은 코드 실행 전 이미 모든 변수정보를 알고 있는 것)
  2. 변수 정보 수집 과정을 이해하기 쉽게 설명한 ‘가상 개념’이다.

호이스팅 규칙

  1. 호이스팅 법칙 1
  • 매개변수 및 변수는 선언부를 호이스팅(변수의 식별자 정보만 위로 끌어 올리는것!) 한다.

<적용 전 예시>

//action point 1 : 매개변수 다시 쓰기(JS 엔진은 똑같이 이해한다)
//action point 2 : 결과 예상하기
//action point 3 : hoisting 적용해본 후 결과를 다시 예상해보기

function a (x) {
	console.log(x); // 1
	var x; 
	console.log(x); // undefibed
	var x = 2;
	console.log(x); // 2  예상 출력
}
a(1);

<매개변수 적용 예시>

//action point 1 : 매개변수 다시 쓰기(JS 엔진은 똑같이 이해한다)
//action point 2 : 결과 예상하기
//action point 3 : hoisting 적용해본 후 결과를 다시 예상해보기

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

<호이스팅 적용 예시>

//action point 1 : 매개변수 다시 쓰기(JS 엔진은 똑같이 이해한다)
//action point 2 : 결과 예상하기
//action point 3 : hoisting 적용해본 후 결과를 다시 예상해보기

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

	x = 1;
	console.log(x);
	console.log(x);
	x = 2;
	console.log(x);
}
a(1);
  • 호이스팅이라는 개념을 모르면 예측이 불가능한 어려운 결과이다!
  1. 호이스팅 법칙 2
  • 함수 선언은 전체를 호이스팅한다.

<적용 전 예시>

//action point 1 : 결과 값 예상해보기
//action point 2 : hoisting 적용해본 후 결과를 다시 예상해보기

function a () {
	console.log(b); // 오류
	var b = 'bbb';
	console.log(b); // bbb
	function b() { }
	console.log(b); // function  예상 출력
}
a();

<호이스팅 적용 예시>

//action point 1 : 결과 값 예상해보기
//action point 2 : hoisting 적용해본 후 결과를 다시 예상해보기

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

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

	console.log(b); // bbb
	console.log(b); // bbb  출력
}
a();

<함수선언문을 함수 표현식으로 봐꾼 예시>

//action point 1 : 결과 값 예상해보기
//action point 2 : hoisting 적용해본 후 결과를 다시 예상해보기

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

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

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

함수 선언문, 함수 표현식

<함수 정의의 2가지 방식 예시>

  • 총 3가지가 있지만 예시의 2가지만 기억하자!
// 함수 선언문. 함수명 a가 곧 변수명
// function 정의부만 존재, 할당 명령이 없는 경우
function a () { /* ... */ }
a(); // 실행 ok

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

내용 정리

  • 실행 컨텍스트는 실행할 코드에 제공할 환경 정보들을 모아놓은 객체이다.
  • 그 객체 안에는 3가지가 존재한다.
    ✓ VariableEnvironment
    ✓ LexicalEnvironment
    ✓ ThisBindings
  • VE와 LE는 실행컨텍스트 생성 시점에 내용이 완전히 같고, 이후 스냅샷 유지 여부가 다르다.
  • LE는 다음 2가지 정보를 가지고 있다.
    ✓ record(=environmentRecord) ← 이 record의 수집과정이 hoisting
    ✓ outer(=outerEnvironmentReference)

<함수 선언문과 표현식의 실질적인 차이 예시>

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;
}
  • 위의 예시대로, LE는 record와 outer를 수집한다. 그 중, record를 수집하는 과정에서 hoisting이 일어난다.

<위로 쭉 끌어올려본 결과 예시>

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

<함수 표현식을 사용한 예시>

...

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

LexicalEnvironment(2) - 스코프, 스코프 체인, outerEnvironmentReference(=outer)

1. 스코프

  • 식별자에 대한 유효범위를 의미한다.
  • 대부분 언어에 존재하며, JS에도 존재한다.

2. 스코프 체인

  • 별자의 유효범위를 안에서부터 바깥으로 차례로 검색해나가는 것

2-1. outer는 현재 호출된 함수가 선언될 당시(☆☆☆)의 LexicalEnvironment를 참조(당시의 환경 정보를 저장)한다.

2-2. 예를 들어, A함수 내부에 B함수 선언 → B함수 내부에 C함수 선언(Linked List)한 경우 -> 결국 타고, 타고 올라가다 보면 전역 컨텍스트의 LexicalEnvironment를 참조하게 된다.

2-3. 항상 outer는 오직 자신이 선언된 시점의 LexicalEnvironment를 참조하고 있으므로, 가장 가까운 요소부터 차례대로 접근 가능하다.

2-4. 결론 : 무조건 스코프 체인 상에서 가장 먼저 발견된 식별자에게만 접근이 가능하다!

<예시>

// 아래 코드를 여러분이 직접 call stack을 그려가며 scope 관점에서 변수에 접근해보세요!
// 어려우신 분들은 강의를 한번 더 돌려보시기를 권장드려요 :)
var a = 1;
var outer = function() {
	var inner = function() {
		console.log(a); // 이 값은 뭐가 나올지 예상해보세요! 이유는 뭐죠? scope 관점에서!
		var a = 3;
	};
	inner();
	console.log(a); // 이 값은 또 뭐가 나올까요? 이유는요? scope 관점에서!
};
outer();
console.log(a); // 이 값은 뭐가 나올까요? 마찬가지로 이유도!

  • 각각의 실행 컨텍스트는 LE 안에 record와 outer를 가지고 있고, outer 안에는 그 실행 컨텍스트가 선언될 당시의 LE정보가 다 들어있으니 scope chain에 의해 상위 컨텍스트의 record를 읽어올 수 있다.

3. outerEnvironmentReference(이하 outer)

  • 스코프 체인이 가능토록 하는 것(외부 환경의 참조정보)라고 할 수 있다.
profile
프론트 엔드 시작하는 뉴비!

0개의 댓글