[코어 자바스크립트 요약] 02. 실행 컨텍스트

·2024년 1월 30일
0

FE

목록 보기
3/3

02. 실행 컨텍스트

⚠️ 실행 컨텍스트를 정확히 이해하자.

01. 실행 컨텍스트란?

  • 의미: 실행할 코드에 제공할 환경 정보들을 모아놓은 객체.
  • JS의 동작 [1] 어떤 실행 컨텍스트가 활성화되는 시점에 선언된 변수를 위로 끌어올림(hoisting)
    [2] 외부 환경 정보를 구성
    [3] this 값을 설정
  • 역할: 전체 코드의 환경과 순서를 보장 동일한 환경에 있는 코드들을 실행할 때 필요 환경 정보들을 모아 컨텍스트를 구성, 콜 스택 call stack에 쌓아올렸다가, 가장 위에 쌓여있는 컨텍스와 관련 있는 코드들을 실행하는 식 *동일한 환경**: 하나의 실행 컨텍스트를 구성할 수 있는 방법 - 전역공간, eval 함수, 함수
  • 실행 컨텍스트 객체: JS 엔진이 어떤 실행 컨텍스트가 활성화될 때 그 관련된 코드들을 실행하는 데 필요한 환경 정보들을 수집
    • VariableEnvironment: 현재 컨텍스트 내의 식별자들에 대한 정보 + 외부 환경 정보. 선언 시점의 LexicalEnvironment의 스냅샷 snapshot, 변경 사항은 반영되지 않음
    • LexicalEnvironment: 처음에는 VariableEnvironment와 같지만 변경 사항이 실시간으로 반영
    • ThisBinding: this 식별자가 바라봐야 할 대상 객체

Untitled

02. VariableEnvironment

  • 차이: LexicalEnvironment와 같지만 최초 실행 시의 스냅샷을 유지한다는 점이 다름.
  • 00실행 컨텍스트를 생성할 때 VariableEnvironment에 정보를 먼저 담고, LexicalEnvironment를 만들고, 이후에는 LexicalEnvironment를 주로 활용.
  • 내부 구성(공통): VariableEnvironment와 LexicalEnvironment 둘 모두 내부 구성은 아래와 같이 동일하다.
    • environmentRecord
    • outer-EnvironmentReference

03. LexicalEnvironment

2-3-1 environmentRecord와 호이스팅

  • environmentRecord: 현재 컨텍스트와 관련된 코드의 식별자 정보들이 저장.
    • 식별자의 종류 [1] 컨텍스트를 구성하는 함수에 지정된 매개변수 식별자
      [2] 선언한 함수 자체
      [3] var로 선언된 변수의 식별자
    • 참고 전역 실행 컨텍스트는 변수 객체가 아닌, JS 구동 환경이 별도로 제공하는 전역 객체를 활용 *전역 객체**: 브라우저의 window, Node.js의 global 객체 등이 있음
      이들은 JS 내장 객체(native object)가 아닌 호스트 객체(host object)로 분류됨
  • hoisting: 컨텍스트 내부 전체를 처음부터 끝까지 훑어 나가며 순서대로 수집
    • BUT 코드 실행 전에 수집
    • 실제로 끌어올리지는 않지만, 편의상 JS 엔진은 식별자들을 최상단으로 끌어올린 후 실제 코드를 실행한다고 생각 가능

hoisting 규칙

  • 변수명과 함수 선언의 정보를 수집
    • 변수: 선언부와 할당부를 나누어 선언부만 수집
    • 함수 선언: 함수 전체를 수집
    • 함수 표현식: 변수 선언부만 호이스팅
  • hoisting 규칙에 의해 전역에서 호출 가능한 함수 선언보다는 선언 이후 실행 가능한 함수 표현식 사용하는 게 best!
    • EX) 전역공간에 동명의 함수가 여럿 존재하는 상황에서의 디버깅!

@매개변수와 변수에 대한 호이스팅

// 매개변수와 변수에 대한 호이스팅 - 원본 코드
function a (x) {  // 수집 대상 1 - 매개 변수 선언
  console.log(x); 
	var x;          // 수집 대상 2 - 변수 선언
	console.log(x);
	var x = 2;      // 수집 대상 3 - 변수 선언
	console.log(x);
}
a(1);

// 매개변수와 변수에 대한 호이스팅 - 호이스팅을 마친 상태
⚠️ 주의: 실제 JS 엔진은 이러한 변환 과정을 거치지 않음. 사람 입장에서의 코드로 변환한 것!

function a (){
	var x;         // 수집 대상 1 - 매개 변수 선언
	~~var x;         // 수집 대상 2 - 변수 선언
	var x;         // 수집 대상 3 - 변수 선언~~

	x = 1;         // 수집 대상 1의 할당 부분
	console.log(x);
	console.log(x);
	x = 2;         // 수집 대상 3의 할당 부분
	console.log(x);
}
a(1);

실제 코드 실행

[1] 변수 x 선언(메모리 공간 미리 확보, 주솟값 변수 x에 연결)
[2] 다시 변수 x 선언, 이미 선언된 변수 x가 있으므로 무시
[3] x에 1 할당
[4] 각 x 출력, 모두 1 출력됨
[5] x에 2 할당(원래 1을 가리키는 주솟값 → 2의 주솟값으로 대치)
[6] x 출력, 2 출력됨
[7] 함수 내부의 모든 코드가 실행됐으므로 실행 컨텍스트가 콜 스택에서 제거됨

@함수 선언의 호이스팅

// 원본 코드
function a () {
	console.log(b);
	var b = 'bb';
	console.log(b);
	function b(){}
	console.log(b);
}
a();

// 호이스팅을 마친 상태
function a(){
	var b;
	function b (){}
	
	console.log(b);
	b = "bbb";
	console.log(b);
	console.log(b);
}
a();

// 함수 선언문을 함수 표현식으로 바꾼 코드
function a(){
	var b;                        // [1] 
	var b = function b (){};      // [2]
	
	console.log(b);               // [3]  함수 b 출력
	b = "bbb";                    // [4]
	console.log(b);               // [5]  "bbb" 출력
	console.log(b);
}
a();

실행 시

[1] 변수 b 선언
[2] 함수는 별도의 메모리, 그 함수가 저장된 주솟값을 b와 연결된 공간에 저장. 이제 변수 b는 함수를 가리킴
[3] 변수 b에 할당된 함수 b가 출력
[4] 변수 b에 “bbb”를 할당
[5] “bbb” 출력 후 실행 컨텍스트는 콜 스택에서 제거(모든 코드 실행됐으므로)

함수 선언문과 함수 표현식

  • 함수 선언문: function 정의부만 존재하고 별도의 할당 명령이 없는 경우
  • 함수 표현식: 정의한 function을 별도의 변수에 할당하는 것을 말함
    • 기명 함수 표현식: 함수명을 정의한 함수 표현식
    • 익명 함수 표현식: 함수명을 정의하지 않은 함수 표현식
  • 함수 정의하는 3가지 방식
    // 함수를 정의하는 3가지 방식
    function a(){ ... }
    a();  // 실행 OK!
    
    var b = function(){ ... }
    b();  // 실행 OK!
    
    var c = function d(){ ... }
    c();  // 실행 OK!
    d();  // 에러!
    • ⚠️ 기명 함수 표현식 주의 외부에서는 함수명으로 함수를 호출할 수 없음.
      오직 내부에서 함수명으로 함수 호출 가능!
      과거) 기명 함수 표현식 - 디버깅 시 어떤 함수인 지 추적하기에 익명 함수 표현식보다 유리한 측면이 있었음
      현재) 모든 브라우저 - 익명 함수 표현식의 변수명을 함수의 name 프로퍼티에 할당

@함수 선언문과 함수 표현식

// 원본 코드
console.log(sum(1,2));
console.log(multipy(3,4));

function sum(a,b){  // 함수 선언문 sum
	return a+b;
}

var multiply = function(a,b){   // 함수 표현식 multiply
	return a*b;
}

// 호이스팅을 마친 상태
var sum = function sum(a,b){  // 함수 선언문 전체를 호이스팅
	return a+b;
}
var multiply;                 // 변수는 선언부만 수집

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

multifly = function(a,b){     // 변수의 할당부는 원래 자리에 남겨둠
	return a*b;
}

실행 시

[1] sum ← 메모리 공간 확보 후 연결
[2] multiply ← 메모리 공간 확보 후 연결
[3] sum 함수 → 또 다른 메모리 공간에 저장, 그 주솟값을 변수 sum의 공간에 할당.
[4] sum 실행
[5] multiply is not a function 이라는 에러 메시지가 출력됨
← 현재 multiply에는 값이 할당되어 있지 않음. 비어있는 대상을 함수로 여겨 출력하라는 명령이었음.

2-3-2 스코프, 스코프 체인, outerEnvironmentReference

  • scope: 식별자에 대한 유효범위
    • ES5까지의 JS는 전역공간을 제외하면 오직 함수에 의해서만 스코프가 생성
  • 스코프 체인 scope chain: 식별자의 유효범위를 안에서부터 바깥으로 차례로 검색해나가는 것
    • LexicalEnvironment의 outerEnvironmentReference 활용

스코프 체인

  • outerEnvironmentReference: 현재 호출된 함수가 선언될 당시*의 LexicalEnvironment를 참조 선언될 당시* : A 함수 내부에 B 함수를 선언, B 함수 내부에 C 함수를 선언
    함수 C의 outerEnvironmentReference → 함수 B의 LexicalEnvironment를 참조
    (함수 C가 선언될 당시)
    함수 B의 outerEnvironmentReference → 함수 A의 LexicalEnvironment를 참조
    (함수 B가 선언될 당시)
  • outerEnvironmentReference는 연결 리스트의 형태
    • 선언 시점의 LexicalEnv를 계속 찾아 올라가면 마지막엔 전역 컨텍스트의 LexicalEnv가 있을 것
  • 여러 스코프에서 동일한 식별자를 선언한 경우 - 무조건 스코프 체인 상에서 가장 먼저 발견된 식별자에만 접근이 가능
    • 가장 먼저* : 찾아 ^올라가는^ 형태

@예제

var a = 1;
var outer = function() {
	var inner = function () {
		console.log(a);
		var a = 3;
	};

	inner();
	console.log(a);
};
outer();
console.log(a);
  • 설명 [1] 가장 처음
    전역 컨텍스트 활성화
    전역 컨텍스트 → environmentRecord = {a, outer} 식별자를 저장
    전역 컨텍스트 → outerEnv-Ref = 아무것도 안 담김 [2] 전역 스코프에서 호이스팅
    전역 스코프에 있는 변수 a ← 1,
    outer ← 함수 할당 [3] outer();
    outer 함수 호출 전역 컨텍스트의 코드는 임시중단,
    outer 실행 컨텍스트가 활성화,
    2번째 줄로 이동 [4] var outer = function () {
    outer 실행 컨텍스트 → environmentRecord = {inner} 식별자 저장
    outer 실행 컨텍스트 → outerEnv-Ref = [GLOBAL, {a, outer}][GLOBAL, {a, outer}]* : 전역 컨텍스트 → LexicalEnvironment를 참조복사한 것을 편의상 표기.
    1번째 인자 = 실행 컨텍스트의 이름, 2번째 인자 = environmentRecord 객체 [5] var inner = function () {
    outer 스코프에 있는 변수 inner에 함수 할당 [6] inner(); inner 함수 호출. outer 실행 컨텍스트 임시중단, inner 실행 컨텍스트 활성화 [7] var inner = function () {
    inner 실행 컨텍스트 → environmentRecord = {a} 식별자 저장
    inner 실행 컨텍스트 → outerEnv-Ref = [outer, {inner}] [8] console.log(a);
    식별자 a에 접근, inner 컨텍스트의 envRec에서 a를 검색
    undefined 출력 [9] var a = 3;
    inner 스코프에 변수 a에 3 할당 [10] };
    inner 함수 실행이 종료, inner 실행 컨텍스트가 콜 스택에서 제거, outer 실행 컨텍스트가 다시 활성화 [11] console.log(a);
    식별자 a 에 접근.
    활성화된 실행 컨텍스트의 LexicalEnv에 접근, 매 요소마다 envRec에 a 있는 지 찾아보고, 없으면 outerEnvRef에서 environmentRecord에서 a 찾는 식으로 계속해서 검색 [12] };
    outer 함수 실행 종료
    outer 실행 컨텍스트가 콜 스택에서 제거, 바로 아래의 전역 컨텍스트가 다시 활성화 [13] console.log(a);
    식별자 a에 접근
    현 활성화된 전역 컨텍스트의 environmentRecord에서 a를 검색
    전역 컨텍스트가 콜 스택에서 제거되고 종료

@ 간략 요약 표

  • LE = LexicalEnvironment, e = environmentRecord, o = outerEnvironmentReference
  • 변수 은닉화
    • 스코프 체인 상에 있는 변수라고 해서 무조건 접근 가능한 것은 아니며, 가장 가까운 실행 컨텍스트에서 어떤 변수가 이미 검색되는 경우, 스코프 체인 검색을 더 이상 진행하지 않음. 즉, 접근할 수 없는 것과 마찬가지.

Untitled

전역변수와 지역변수

  • 전역변수: 전역 공간에서 선언한 변수
  • 지역변수: 함수 내부에서 선언한 변수
  • 코드의 안전성을 위해 가급적 전역변수 사용을 최소화하고자 노력하는 것이 좋음.

04. this

실행 컨텍스트의 thisBinding에는 this로 지정된 객체가 저장

실행 컨텍스트 활성화 당시, this가 지정되지 않는 경우: this ← 전역 객체가 저장

05. 정리

  • 실행 컨텍스트
    • 실행할 코드에 제공할 환경 정보들을 모아놓은 객체
    • 전역 공간에서 자동으로 생성되는 전역 컨텍스트와 eval 및 함수 실행에 의한 컨텍스트 등
    • 활성화 시점에 VariableEnv, LexicalEnv, ThisBinding 정보를 수집
  • VariableEnv, LexicalEnv
    • 공통: 실행 컨텍스트 생성 당시 동일 내용
    • 차이
      • LexicalEnv: 함수 실행 도중에 변경되는 사항이 즉시 반영
      • VariableEnv: 초기 상태를 유지
    • 구성
      • environmentRecord: 매개변수명, 변수의 식별자, 선언한 함수의 함수명 수집
      • outerEnvironment-Reference: 바로 직전 컨텍스트의 LexicalEnvironment 정보를 참조하는 outerEnvironmentReference로 구성
  • 호이스팅
    • 코드 해석을 수월하게 해 environmentRecord의 수집 과정을 추상화
      • 실행 컨텍스트가 관여하는 코드 집단의 최상단으로 이들을 끌어올린다고 해석하는 것
    • 변수 선언과 값 할당이 동시에 이뤄진 문장: 선언부만 호이스팅, 할당 과정(호출부)는 원래 자리에.
      • 함수 선언문 vs 함수 표현식의 차이 발생
  • 스코프
    • 변수의 유효범위
    • outerEnvironmentReference: 해당 함수가 선언된 위치의 LexicalEnvironment를 참조
    • 코드 상에서 어떤 변수에 접근하는 경우 현재 컨텍스트의 LexicalEnvironment를 탐색해서
      • 발견 : 그 값을 반환
      • 발견 못하면 다시 outerEnvRef에 담긴 LexicalEnv를 탐색하는 과정을 거침
      • 전역 컨텍스트까지 탐색해도 해당 변수를 찾지 못하면 undefined를 반환
  • 전역변수와 지역변수
    • 전역변수: 전역 컨텍스트의 LexicalEnv에 담긴 모든 변수
    • 그 밖의 함수에 의해 생성된 실행 컨텍스트의 변수들
    • 안전한 코드 구성을 위해 전역변수 사용 최소화
profile
이것저것 개발하는 것 좋아하지만 서버 개발이 제일 좋더라구요..

0개의 댓글