[FE Road Map] 실행 컨텍스트

장동현·2022년 6월 7일
0

FE Roadmap

목록 보기
3/3

Notion 으로 보기

2. 실행 컨텍스트

실행 컨텍스트란?

  • 실행할 코드에 제공한 환경 정보들을 모아놓은 객체
  • JS의 동적 언어로서의 성격을 잘 파악할 수 있는 개념
    • 어떤 실행 컨텍스트가 활성화되는 시점에 선언된 변수를 위에 끌어올리기 - 호이스팅
    • 외부 환경 정보를 구성
    • this 값을 설정
  • JS에서 가장 중요한 핵심 개념 중 하나라고 할 수 있음
    • 클로저 지원 - 대부분 동일한 개념 적용

1. 실행 컨텍스트란?

  • 먼저 스택, 큐에 대해서 학습

  • 스택 : FIFO

    • 우리가 흔히 알고 있는 스택 오버플로우
    • 스택이 넘치면 에러가 발생함
  • 큐 : LIFO

  • 실행 컨텍스트

    • 실행할 코드에 제공할 환경 정보를 모아놓은 객체
    1. 동일한 환경에 있는 코드들을 실행할 때
    2. 필요한 환경 정보들을 모아 → 컨텍스트를 구성
    3. 이를 콜 스택에 쌓아올렸다가
    4. 가장 위에 쌓여있는 컨텍스트와 관련 있는 코드들을 실행하는 식
  • 동일한 환경

    • 즉 : 하나의 실행 컨텍스트를 구성할 수 있는 방법
    1. 전역 공간
    2. eval() 함수
    3. 함수
  • 자동으로 생성되는 전역공간과

  • 악마로 취급받는 eval? 을 제외하면

  • 실행 컨텍스트 구성 → 함수

// -------------------------- (1)
var a = 1;
function outer() {
	function inner() {
		console.log(a); //undefined
		var a = 3;
	}
	inner(); // --------------- (2)
	console.log(a); // 1
}
outer(); // ----------------- (3)
console.log(a); // 1
  1. 처음 JS 코드를 실행하면 (1) 전역 컨텍스트가 콜 스택에 담김

    • 전역 컨텍스트라는 개념은 - 일반적인 실행 컨텍스트와 특별히 다를 것이 없음
    • 최상단의 공간은 코드 내부에서 별도의 실행 명령이 없이도
    • 브라우저에서 자동으로 실행
    • JS 파일 열리는 순간 → 전역 컨텍스트 활성화
  2. 코드를 순차적으로 진행 하다가 (2) 구문을 만나게 되면

    • JS 엔진은 outer에 대한 환경 정보를 수집해서
    • outer 실행 컨텍스트를 생성한 후 - 콜스택에 담음
    • 콜 스택의 맨 위outer 실행 컨텍스트가 놓인 상태
    • 전역 컨텍스트 실행 → 중단
    • outer 실행에 관련된 실행 콘텍스트 → 즉 함수 내부의 코드들을 순차적으로 실행
  3. outer 실행 중 inner() 실행 구문을 만나게 되면

    • outer 컨텍스트와 관련된 코드의 실행을 중단하고
    • inner 함수 내부의 코드를 순서대로 진행
  4. inner 함수 내부에서 a 변수의 값을 추출하고 나면

    • inner 함수의 실행이 종료되면서
    • inner 실행 컨텍스트가 콜 스택에서 제거됨
  5. 그 이후 순차적으로 스택에 남겨져 있던 모든 컨텍스트 제거

스택 구조를 잘 생각해보면
한 실행 컨텍스트 → 맨 위에 쌓이게 되면
현재 실행할 코드에 관여하게 되는 시점

  • 어떤 실행 컨텍스트가 활성화될 때
  • JS엔진은 관련된 코드들을 실행하는 데 필요한
  • 환경 정보들을 수집해서 실행 컨텍스트 객체에 저장함

실행 컨텍스트 객체에 저장되는 정보

  • 개발자가 코드를 통해 확인은 불가함
  1. VariableEnvironment
    • 현재 컨텍스트 내의 식별자들에 대한 정보 + 외부 환경 정보
    • 선언 시점의 LexicalEnvironment의 스냅샷으로
    • 변경 사항은 반영되지 않음
  2. LexicalEnvironment
    • 처음에는 VariableEnvironment와 같지만
    • 변경 사항이 실시간으로 반영됨
  3. ThisBinding
    • this 식별자가 바라봐야 할 대상 객체

2. VairibleEnvironment

  • VairibleEnvironment에 담기는 내용 === LexicalEnvironment와 같지만
    • 최초 실행 시의 스냅샷을 유지한다는 점이 다름

실행 컨텍스트를 생성할 때

  1. VairibleEnvironment에 정보를 먼저 담고
  2. 이를 그대로 복사해서 LexicalEnvironment를 만들고
  3. 이후에는 LexicalEnvironment를 활용

두개의 내부에는

  1. environmentRecord
  2. outer-EnvironmentReference로 구성

초기화 과정 중에는 사실상 완전히 동일하고

이후 코드 진행에서 완전히 달라짐

3. LexicalEnvironment

  • 어휘적 환경, 정적 환경을 의미함 → 일반 적인 의미
  • 사전적인 의미로 이해하는 것이 좋음
    • 현재 컨텍스트 내부에는 a, b, c 와 같은 식별자들이 있고
    • 그 외부 정보는 d를 참조하도록 구성돼 있음

2-3-1 environmentRecord와 호이스팅

  • environmentRecord
    • 현재 컨텍스트와 관련된 코드의 식별자 정보들이 저장

    • 컨테스트를 구성하는 함수에 지정된
      1. 매개변수 식별자
      2. 선언된 함수가 있을 경우 → 그 함수 자체
      3. var로 선언된 변수의 식별자

      순서대로 수집

  • 참고 전역 실행 컨텍스트는 변수 객체를 생성하는 대신 JS 구동 환경이 별도로 제공하는 객체 즉 : 전역 객체를 활용함 브라우저 : window Node.js : global 이들은 내장 객체가 아닌 → 호스트 객체로 분류
  • 변수 정보를 수집하는 과정을 모두 마쳤더라도
    • 실행 컨텍스트가 관여할 코드 → 아직 실행 이전
    • 하지만 JS 엔진은 모든 변수명을 알고 있음
    • 이를 호이스팅이라고 함
    • 편의상 끌어올린 것으로 간주

호이스팅 규칙

function a (x) {  // 수집 대상 1 - 매개변수
	console.log(x); // (1)
	var x;          // 수집 대상 2 - 변수 선언
	console.log(x); // (2)
	var x = 2;      // 수집 대상 3 - 변수 선언
	console.log(x); // (3)
}

좀 더 사람의 입장에서 이해하기 위해서 코드를 변경
실제로는 이렇게 동작하지 않다는 것을 유의

  • 함수 인자의 경우
    • 함수 내부의 다른 코드보다 먼저 선언 및 할당이 이뤄진 것으로 간주
function a () {  
	var x = 1;      // 수집 대상 1 - 매개변수
	console.log(x); // (1)
	var x;          // 수집 대상 2 - 변수 선언
	console.log(x); // (2)
	var x = 2;      // 수집 대상 3 - 변수 선언
	console.log(x); // (3)
}

여기서 호이스팅 처리를 한다면?

  • environmentReocrd : 현재 실행될 컨텍스트의 대상 코드 내에
    • 어떤 식별자가 있는지에만 관심
    • 각 식별자에 어떤 값이 할당될 것인지는 관심이 없음
  • 따라서 변수명만 끌어올리고 → 할당 과정은 원래 그자리에 둠
    • 이는 매개변수도 마찬가지

변환 후

function a () {  
	var x;          // 수집 대상 1의 변수 선언 부분
	var x;          // 수집 대상 1의 변수 선언 부분
	var x;          // 수집 대상 1의 변수 선언 부분 

	x = 1;          // 수집 대상 1의 할당 부분
	console.log(x); // (1)
	console.log(x); // (2)
	x = 2;          // 수집 대상 3 - 변수 선언
	console.log(x); // (3)
}
  • 이제 실제 코드의 실행의 차례
    • 스코프체인 수집 및 this 할당 과정은 추후 논의
  1. 2번째 줄의 변수 x를 선언함
    • 이때 메모리에서는 → 저장할 공간을 미리 확보하고
    • 확보한 공간의 주솟값을 변수 x에 연결함
  2. 3번째 줄과 4번째 줄
    • 다시 변수 x를 선언함
    • 이미 선언된 변수 x가 있으므로 무시
  3. 6번째 줄 x에 1을 할당함
    • 우선 숫자 1을 별도의 메모리에 담고
    • x와 연결된 메모리 공간에 숫자 1을 가리키는 주솟값을 입력
  4. 7번째 줄과 8 번째 줄
    • (1), (2) 모두 1이 출력됨
  5. 9번째 줄 x에 2를 할당함
    • 숫자 2를 별도의 메모리에 담고
    • 그 주솟값을 든 채로 x와 연결된 메모리 공간으로 감
    • 이걸 2의 주솟값으로 대치
  6. 10번째 줄
    • 출력 후 실행 컨텍스트가 콜 스택에서 제거됨

호이스팅의 개념에 없으면 두번째 var x

// before 호이스팅
function a () {
	console.log(b); // b 함수
	var b = 'bbb';
	console.log(b); // 'bbb'
	function b () { }
	console.log(b); 
}
// after 호이스팅
function b() {
	var b;
	var b = function b () { }
	
	console.log(b);
	b = 'bbb';
	console.log(b);
	console.log(b);
}

함수의 선언 : 함수 전체를 끌어올림
변수 → 선언부만 끌어올리는 것과의 차이점

함수 선언문과 함수 표현식

  • 둘 모두 함수를 새롭게 정의할 때 쓰이는 방식

  • 함수 선언문 → function 정의부만 존재

    • 별도의 할당 명령이 없는 것을 의미함
  • 함수 표현식 → 정의한 function을 별도의 변수에 할당

선언문 : 반드시 함수명의 정의되어 있어야함

표현식 : 없어도 됨

  • 함수명을 정의한 함수 표현식 : 기명 함수 표현식
    • 정의하지 않은 것을 : 익명 함수 표현식
  • 일반적으로 함수 표현식은 익명 함수 표현식
function a () { } // 함수 선언문 : 함수명 a가 곧 변수명
a(); // 실행 OK

var b = function () {} // (익명) 함수 표현식 : 변수명 b가 곧 함수명
b(); // 실행 OK

var c = function d () {] // 기명 함수 표현식 변수명 : c, 함수명 : d
c(); // 실행 OK
d(); // 에러!

그럼 왜 기명 함수 표현식이 필요한가?

  • 예전에는 내부에서 익명 함수 표현식을 호출 할 수 없었음
  • undefined, unnamed → 지금은 잘 나옴 → 익명 함수 표현식의 변수명을 함수의 name 프로퍼티에 할당
  • 굳이 있어야 하나?
// before 호이스팅
console.log(sum(1, 2));
console.log(multiply(3, 4));

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

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

// after
var sum = function sum (a, b) { return a + b }; // 함수 선언문은 전체를 호이스팅

var multiply; // 변수는 선언부만 끌어올림
console.log(sum(1, 2));
console.log(multiply(3, 4));
multiply = function (a, b) { // 변수 할당부는 원래 자리에 남겨둠
	return a * b;
}

6번째 console.log(multiply)의 경우 실행이 되지 않음

  • sum 함수는 선언 전에 호출해도 아무 문제 없이 실행됨
  • 이는 양날의 검
    • 선언 후에야 호출 가능함 → 우리가 흔히 알고 있음
    • 다른 오해의 소지의 예
console.log(sum(3, 4));

// 첫 sum
function sum (x, y) {
	return x + y;
}

var a = sum(1, 2);

// 두번 째 sum
function sum (x, y) {
	return x + ' + ' + y + ' + ' = ' + (x + y);
}

var c = sum(1, 2);
console.log(c);
  • 첫번 째 sum을 선언하고
  • 후에 5천번째 줄에서 sum을 다시 선언 했다고 예상
  • 고로 전역 컨텍스트가 활성화될 때
    • 전역공간에 선언된 함수들이 모두 가장 위로 끌어올려짐 → 호이스팅
    • 동일한 변수명에 서로 다른 값을 할당 → 나중에 할당이 최신이 됨

고로 함수 표현식으로 작성하는 것이 더 좋음

또는 함수명이 겹치지 않게 선언하는 것 또한 방법

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

  • 스코프란 : 식별자에 대한 유효범위

    • 어떤 경계 A의 외부에서 선언한 변수
      • A의 외부
      • A의 내부에도 접근이 가능
    • A의 내부에서 언헌한 변수
      • A의 내부에서만 사용이 가능함
  • 이러한 스코프의 개념은 대부분의 언어에 존재함

    • JS도 예외는 아닌데
    • ES5 까지는 오직 함수에 의해서만 스코프가 생성 되었음
    • 이렇게 식별자의 유효범위를 안 → 밖으로 차례로 검색하는 것
    • 스코프 체인
    • 이를 가능케 하는 것 → LexicalEnvironment의 두 번째 수집 자료
    • outerEnvironmentReference

스코프 체인

  • outerEnvironment는 현재 호출된 함수가 선언될 당시의

    • LexicalEnvironmnet를 참조함
    • 과거 시점인 선언될 당시에 주목
  • 선언하다는 행위가 실제로 일어날 수 있는 시점이면

    • 콜 스택 상에서 어떤 실행 컨텍스트가 활성화 된 상태일 뿐
    • 어떤 함수를 선언(정의) 하는 행위 자체도 하나의 코드
    • 모든 코드는 실행 컨텍스트가 활성화 → 실행

여러 스코프에서 동일한 식별자를 선언한 경우
무조건 스코프 체인 상에서 가장 먼저 발견된 식별자에만 접근 가능

var a = 1;
var outer = function () {
	var inner = function () {
		console.log(a);
		var a = 3;
	};
	inner();
	console.log(a);
};
outer(); // 10 번째 줄
console.log(a);
  • 실행 과정 - 김

    1. 전역 컨텍스트 활성화

      • environmentRecord에 {a, outer} 식별자를 저장함
      • 전역 커넥스트는 선언 시점이 없으므로 전역 컨텍스트의
      • outerEnvironment - Reference에는 아무것도 담기지 않음 (this: 전역 객체)
    2. 1번째 줄과 2번째 줄 : 전역 스코프에 있는 변수 a에 1을 outer에 함수를 할당

    3. 10번째 줄

      • outer 함수를 호출함
      • 이에 따라 전역 컨텍스트의 코드는 10번째 줄에서 임시 중단되고
      • outer 실행 컨텍스트가 활성화되어 2번째 줄로 이동함
    4. outer 실행 컨텍스트의 environmentRecord에 { inner } 식별자를 저장함

      • outerEnvironment -Reference에는 outer 함수가 선언될 당시의 LexicalEnvironment 담김
      • outer 함수는 전역 공간에서 선언됐으므로 전역 컨텍스트의 LexicalEnvironment를 참조 복사함
      • 이를 [GLOBAL, { a, outer } ] 라고 표시
      • 첫 번째는 실행 컨텍스트의 이름, 두번째는 environment 객체 (this: 전역 객체)
    5. 3번째 줄

      • outer 스코프에 있는 변수 inner에 함수를 할당함
    6. 7번째 줄 : inner 함수를 호출함

      • 이에따라 outer 실행이 중단되고
      • inner 실행 컨텍스트가 활성화되어 3번째 줄로 이동함
    7. 7번째 줄 : inner 실행 컨텍스트의 environmentRecord에 { a } 식별자를 저장함

      • outerEnvironment에는 inner 함수가 선언될 당시의 LexicalEnvironment가 담김
      • inner 함수는 outer 함수 내부에서 선언됐으므로
      • outer 함수의 LexicalEnvironment 즉 [outer, {inner} ] 를 참조 복사함
    8. 4번째 줄 : 식별자 a에 접근하고자 함

      • 현재 활성화 상태인 inner 컨텍스트의 environmentRecord에서 a를 검색함
      • a가 발견됐는데 여기에는 아직 할당된 값이 없음 (undefined 출력)
    9. 5번째 줄 : inner 스코프에 있는 변수 a에 3을 할당함

    10. 6번째 줄

      • inner 함수 실행이 종료됨
      • inner 실행 컨텍스트가 콜 스택에서 제거되고
      • 바로 아래의 outer 실행 컨텍스트가 다시 활성화 되면서
      • 앞서 중단했던 7번째 줄의 다음으로 이동함
    11. 8번째 줄 :

      • 식별자 a에 접근하고자 함
      • JS 엔진은 활성화된 실행 컨텍스트의 LexicalEnvironment에 접근함
      • 첫 요소의 environmentRecord로 넘어가는 식으로 계속해서 검색함
      • 예제에서는 두 번째, 즉 : 전역 LexicalEnvironment에 a가 있으니 a에 저장된 값을 반환
    12. 9번째 줄 :

      • outer 실행이 종료됨
      • outer 실행 컨텍스트가 콜 스택에서 제거됨
      • 바로 아래의 전역 컨텍스트가 다시 활성화 되면서
      • 앞서 중단했던 10번째 줄의 다음으로 이동함
    13. 11번째 줄
      - 식별자 a에 접근하고자 함
      - 현재 활성화 상태인 전역 컨텍스트의 environmentRecord에서 a를 검색함
      - 바로 a를 찾을 수 잇음
      - 이로써 모든 실행이 종료됨

    • 전역 : 전역 스코프에서만 생성된 변수에 접근
    • outer : outer 및 전역
    • inner : inner, outer, 전역 모두 접근 가능

하지만 스코프 체인 상에 있는 변수라고 해서 무조건 접근 가능한 것은 아님

  • 식별자 a의 경우
    • 전역 공간
    • inner 내부에서도 선언했음
  • inner 함수 내부에서 a에 접근하려고 하면
    • inner 스코프의 LexicalEnvironment 부터 검색할 수밖에 없음
    • inner 내부에서 존재하므로 검색을 진행하지 않고
    • 즉시 반환을 하게 되는 이유임
    • 이를 변수 은닉화

전역변수와 지역변수

  • 안정성을 위해 → 코드의 안정성을 위해 가급적 사용 안하는 것이 좋음

this

  • thisBinding에는 this로 지정된 객체가 저장됨
  • 실행 컨텍스트 활성화 당시에 this 전역 객체가 저장됨
  • 그밖에는 함수를 호출하는 방법에 따라 this에 저장되는 대상이 다름

정리

실행 컨텍스트 : 실행할 코드에 제공할 환경 정보를 모아 놓은 객체

실행 컨텍스트는의 종류

  1. 전역 공간 → 전역 컨텍스트
  2. eval
  3. 함수 실행에 의한 컨텍스트
  • 실행 컨텍스트 객체는 활성화 되는 시점에

    1. VariableEnvironment
    2. LexicalEnvironment
    3. ThisBinding
    • 3가지 정보를 수집함
  • VairialbeEnvironment

    • 실행 시점에는 LexicalEnvironment와 동일한 내용으로 구성됨
    • 초기 상태를 유지함
  • LexicalEnvironment

    • environmentRecord
      • 매개변수 명
      • 변수의 식별자
      • 선언한 함수의 함수명
    • outerEnvironmentReference
      • 직전 실행 컨텍스트의 LexicalEnvironment 참조
  • 호이스팅

    • 코드 해석을 좀 더 수월하게 하기 위해
    • environmentRecord 수집 과정을 추상화한 개념
    • 변수 : 선언부만 호이스팅
    • 함수 : 문 전체를 호이스팅
      • 여기서 함수 선언문, 표현식의 차이
      • 선언문 : function name () {}
      • 표현식 : var x = function () {}
  • 스코프

    • 변수의 유효범위를 의미함
    • outerEnvironmentReference : 해당 함수가 선언된 위치의 LexicalEnvironment 참조
    • 코드 상에서 어떤 변수에 접근하려고 하면 현재 컨텍스트의 LexicalEnvironment 탐색
      • 있으면 반환
      • 없으면 스코프 체이닝 → outerEnvironmentReferenceLexicalEnvironment 탐색
      • 전역 컨텍스트의 LexicalEnvironment까지 탐색해서 찾지 못하면 → undefined

    참조

  • 코어 자바스크립트 (정재남님)

profile
FE 개발자 장동현 입니다 😃

0개의 댓글