[JS] JS는 왜 그렇게 동작할까?-실행컨텍스트,호이스팅,스코프

정재남,『코어자바스크립트』를 읽고 정리한 내용입니다. 이해가 부족한 부분은 책과 동일하게 작성하였습니다.

1. 실행컨텍스트(Execution Context)

  • 실행 컨텍스트는 실행할 코드에 제공하 환경 정보들을 모아놓은 객체이다.
  • JS는 어떤 실행 컨텍스트가 활성화되는 시점에 선언된 변수를 위로 끌어올리고(호이스팅), 외부 환경 정보를 구성하고, this값을 설정하는 등의 동작을 수행한다. 이로 인해 다른 언어에서는 발견할 수 없는 특이한 현상들이 발생한다.

1.1 스택과 큐


[이미지출처]

  • 스택(Stack) : 출입구가 하나뿐인 데이터 구조, Last In-First Out
  • 큐(Queue) : 양쪽이 모두 열려있는데이터 구조, First In-First Out

즉, 실행컨텍스트는
동일한 환경에 있는 코드들을 실행할 때 필요한 환경정보를 모아 컨텍스트를 구성하고 이를 콜 스택에 쌓아올렸다가, 가장 위에 쌓여있는 컨텍스트와 관련있는 코드들을 실행함으로써 전체 코드의 환경과 순서를 보장한다.
↳ 동일한 환경이란, 하나의 실행 컨텍스트를 구성할 수 있는 방법으로 전역공간, eval()함수, 함수 등이 있다.

우리가 흔히 실행컨텍스트를 구성하는 방법은 함수를 실행하는 것 뿐이다.
(∘ ES6에서는 {블록}에 의해서도 새로운 실행 컨텍스트가 생성된다)

스택구조로 보면 결국 스택에서 맨 위에 있는 컨텍스트가 현재 실행 할 코드에 관여하게 되는 시점이다.

1.2 활성화된 실행컨텍스트 구조

VariablEnvironment
현재 컨텍스트 내의 식별자들에 대한 정보와 외부 환경 정보, 선언 시점의 LexicalEnvironment의 스냅샷으로, 변경사항은 없음

LexicalEnvironment
처음 실행 당시에는 VariableEnvironment와 같지만 변경 사항이 실시간으로 반영된다.

ThisBinding
식별자가 바라봐야 할 대상 객체

2. VariableEnvironment

VariableEnvironment에 담기는 내용은 LexicalEnvironment와 같지만 스냅샷을 유지한다는 차이점이 있다.
실행 컨텍스트를 생성하게되면 VariableEnvironment에 정보를 담게 되고 그 정보를 그대로 복사해서 LexicalEnvironment 만들고 이후에는 LexicalEnvironment를 주로 활용한다.

3. LexicalEnvironment

LexicalEnvironment는 "현재 이 컨텍스트 내부에는 이러이러한 식별자들이 있고, 그 외부 정보는 뭔가를 참조하도록 구성되어 있다" 라는 느낌으로 알고 있으면 좋은 것 같다.

3.1 environmentRecord / Hoisting(호이스팅)

예를 들어 A라는 컨텍스트에서 내부를 순서대로 훑다가 B라는 함수를 만나면 그 자리에서 중지하고 B라는 컨텍스트를 생성하게 됩니다.

📍 호이스팅이란?
environmentRecord는 정보 수집 과정에서 현재 실행될 컨텍스트의 대상 코드내에 어떤 식별자들이 있는지에만 관심이 있다. 그렇다는 것은 호이스팅으로 끌어올리는데 있어 변수는 선언부와 할당부를 나누어 선언부만 끌어올린다. → 실제 할당된 부분은 놔두고 변수명만 끌어올린다.
반면, 함수 선언은 함수 전체를 끌어올린다.

// 호이스팅 전
console.log(x);
var x = 10;
// 호이스팅 후
var x;
console.log(x);
var x = 10;

3.2 함수 선언문과 함수 표현식에서의 호이스팅

  • 함수선언문 : function 정의부만 존재하고 별도의 할당 명령이 없음
  • 함수표현식 : 정의한 함수를 별도의 변수에 할당하는 것
    → 기명함수표현식 : 함수명을 정의
    → 익명함수표현식 : 함수명 따로 정희하지 않음
// 함수 선언문
function a () {
  어찌고저찌고
};

// 기명 함수 표현식
let b = function a() {
  어쩌고 저쩌고
};

// 함수 표현식 (익명 함수)
let b = function () {
  어찌고저찌고
};

environmentRecord에서 정보 수집 과정을 할때 호이스팅이 발생하는데 변수명만 끌어 올린다고 했다.
그렇다면 여기서 함수 선언문과 함수 표현식은 호이스팅을 할 때 어떤 차이가 발생할까?!

//📍함수선언문의 호이스팅
console.log(sum(1,2)) //3
//📍함수표현식의 호이스팅
console.log(divide(10,3)) //Uncaught TypeError: divide is not a function

function sum(a,b) {
   return a+b;
}

var divide = function (a,b) {
   return a/b;
}

이와 같이 함수 선언문은 함수 전체가 호이스팅 되는 반면에
함수 표현식은 에러가 발생했습니다 이러한 이유는 아래와 같이 호이스팅이 되기 때문이다.

//📍함수표현식의 호이스팅
function sum(a,b) {
   return a+b;
}
var divide;
console.log(sum(1,2))
console.log(divide(10,3))

var divide = function (a,b) {
   return a/b;
}

이와 같이 함수도 하나의 값으로 취급할 수 있습니다.

"함수 선언문"은 함수 전체가 호이스팅이 발생하게 되고,

"함수 표현식"은 기존과 같이 변수 선언부만 호이스팅 발생

4. Scope(스코프)

스코프란 식별자에 대한 유효범위입니다. 쉽게 말하면 변수를 사용할 수 있는 영역이다.
예를 들어 A의 외부에서 선언한 변수는 A의 외부와 내부에서 접근이 가능하지만 A 내부에서 선언한 변수는 오직 A 내부에서만 접근이 가능하다.

이러한 식별자 유효범위를 안에서부터 바깥으로 나가면서 차례대로 검색해나가는 것을 스코프 체인이라고 한다. 그리고 이것을 가능하게 해주는 것이 LexicalEnvironment의 두번째 수집 자료인 outerEnvironmentReference 입니다.

4.1 스코프체인

스코프체인이란 식별자에 대한 유효범위를 안에서부터 바깥으로 차례대로 검색하면서 나가는 것

outerEnvironmentReference는 현재 호출된 함수가 선언될 당시의 LexicalEnvironment를 참조한다. 이 말은 즉 A 실행 컨텍스트에서 B라는 실행 컨텍스트가 생겼을 때 A의 LexicalEnvironment를 참조한다는 말이다.

여기서 한 가지 outerEnvironmentReference는 자신이 선언된 시점의 LexicalEnvironment만을 참조하고 있다.
🤓이 말은 즉 차례대로만 접근 가능 하고 다른 순서로 접근하는 것 자체가 불가능하다는 것이다.

위와 같은 특성에 의해 여러 스코프에서 동일한 식별자가 존재 할 경우 스코프 체인 상에서 가장 먼저 발견된 식별자에게만 접근이 가능하다는 것

5. ThisBinding

this는 어디서 어떻게 호출했느냐에 따라 저장되는 대상이 다른데 어떠한 대상도 지정되지 않았을때 this는 전역 객체에 저장이 된다.

정리

  • 실행컨텍스트는 실행할 코드에 제공할 환경 정보들을 모아놓은 객체이다. 실행 컨텍스트는 전역 공간에서 자동으로 생성되는 전역 컨텍스트와 eval 및 함수 실행에 의한 컨텍스트 등이 있다. 실행 컨텍스트 객체는 활성화되는 시점에서 VariableEnvironment, LexicalEnvironment, ThisBinding의 세 가지 정보를 수집한다.

  • 실행 컨텍스트를 생성할 때는 VariableEnvironment와 LexicalEnvironment가 동일한 내용으로 구성되지만 LexicalEnvironment는 함수 실행 도중에 변경되는 사항이 즉시 반영되는 반면 VariableEnvironment는 초기 상태를 유지한다. VariableEnvironment는 매개변수명, 변수의 식별자, 선언한 함수의 함수명 등을 수집하는 environmentRecord와 바로 직전 컨텍스트의 LexicalEnvironment 정보를 참조하는 outerEnvironment로 구성되어 있다.

  • 호이스팅은 코드 해석을 좀 더 수월하게 하기 위해 environmentRecord의 수집과정을 추상화한 개념으로, 실행 컨텍스트가 관여하는 코드 집단의 최상단으로 이들을 '끌어올린다'고 해석하는 것입니다. 변수 선언과 값 할당이 동시에 이뤄진 문장은 '선언부'만을 호이스팅하고, 할당 과정은 원래 자리에 남아있게 되는데, 여기서 함수 선언문과 함수 표현식의 차이가 발생한다.

  • 스코프는 변수의 유효범위를 말한다. outerEnvironmentReference는 해당 함수가 선언된 위치의 LexicalEnvironment를 참조한다. 코드 상에서 어떤 변수에 접근하려고 하면 현재 컨텍스트의 LexicalEnvironment를 탐색해서 발견되면 그 값을 반환하고, 발견하지 못할 경우 다시 outerEnvironmentReference에 담긴 LexicalEnvironment를 탐색하는 과정을 거친다. 전역 컨텍스트의 LexicalEnvironment까지 탐색해도 해당 변수를 찾지 못하면 undefined를 반환한다.

  • 전역 컨텍스트의 LexicalEnvironment에 담긴 변수를 전역변수라 하고, 그 밖의 함수에 의해 생성된 실행 컨텍스트의 변수들은 모두 지역변수입니다. 안전한 코드 구성을 위해 가급적 전역변수의 사용은 최소화하는 것이 좋다.

*this에는 실행 컨텍스트를 활성화하는 당시에 지정된 this가 저장된다. 함수를 호출하는 방법에 따라 그 값이 달라지는데, 지정되지 않은 경우에는 전역 객체가 저장된다.

[참고한자료]
정재남, 『코어자바스크립트』, 위키북스(2019)

좋은 웹페이지 즐겨찾기