실행 컨텍스트를 이해하면 스코프
, 호이스팅
, 클로저
등 자바스크립트의 주요한 동작을 이해할 수 있다.
실행 컨텍스트는 학습하기엔 구성이 상당히 복잡하다.
그러므로 실행 컨택스트 안에 있는 레코드와 아우터를 간단히 알아보자.
자바스크립트 코드가 실행되고 연산되는 범위를 나타내는 추상적인 개념
우리가 코드를 작성하고 실행한다면 실행 컨텍스트(Execution Context) 내부에서 실행되고 있는 것이다.
즉 코드들이 실행되기 위한 환경이자 하나의 박스이자 컨테이너라 볼 수 있다.
A. 변수: 전역 변수, 지역 변수, 매개 변수, 객체의 프로퍼티
B. 함수 선언
C. 변수의 유효범위
D. this
함수B를 호출하는 함수A가 있다고 해보자
콜스택은 후입선출이므로 가장 최근에 추가된 실행 컨텍스트만 활성화된다.
호이스팅이란 선언문이 마치 최상단에 끌어올려진 듯한 현상을 말한다.
이처럼 변수를 선언한 후 선언라인의 이전과 다음에 각각 변수를 출력해보자
undefind
이렇게 선언라인 전에도 에러가 나지 않고 변수를 참조할 수 있는 현상을 호이스팅이라고 한다.
js엔진이 전체 코드를 스캔하면서 변수 같은 정보를 실행컨텍스트의 환경 레코드에 미리 기록해놓는다.
환경 레코드: 식별자와 식별자에 바인딩된 값을 기록해두는 객체
환경 레코드에 변수가 어떻게 저장되는지만 봐도 호이스팅을 빠삭하게 이해할 수 있다.
JS엔진은 코드를 실행하면 전역 컨텍스트 한 칸을 생성해서 콜스택에 넣은 후, 전체 코드를 스캔하면서 선언할게 있다면 먼저 선언해둔다.
생성단계: 위에 설명했듯 본격적인 실행에 앞서 스캔하고 준비하는 단계
실행컨텍스트를 생성하고, 선언문만 미리 생성해서 환경레코드에 미리 기록
실행단계: 선언문 외에 나머지 코드를 순차적으로 실행
필요한 경우 생성단계에서 기록해둔 정보를 참고하거나 업데이트
첫 번째 줄을 출력하려면 TVChannel에 바인딩된 값이 뭔지 알아야 한다.
JS엔진은 현재 활성화된 실행 컨텍스트 내에 환경레코드를 보고 이미 기록된 TVChannel의 값을 참조해서 문제없이 값을 출력한다.
두 번째 줄은 선언은 생성 단계에서 했으므로 해당 라인의 할당만 실행해준다.
TVChannel에 바인딩된 값을 netflix로 업데이트해서 기록해둔다.
세 번째 줄을 실행하면 JS엔진은 환경레코드를 참조해서 TVChannel의 값을 "Netflix"로 결정해낸다.
var 대신 const로 했을 경우
JS엔진이 TVChannel 식별자를 기록해두긴 하지만 값을 초기화하진 않는다.
따라서 선언문 이전에 TVChannel의 값을 참조하려 하면 레퍼런스 에러가 발생한다.
var 키워드는 선언과 초기화가 동시에 이뤄진다.
let, const는 선언하면 값을 undefined로 초기화해두지 않는다.
할당문 직전까지는 변수에 아무런 값이 담기지 않고, 그러므로 유효한 값을 읽어올 수 없다. = 일시적 사각지대
js에선 함수를 변수에 담을 수 있다.
함수표현식
변수에 함수를 담아 선언하는 방식, 변수 호이스팅과 동일하게 동작
var 키워드에 화살표 함수를 담아 선언문 이전에 실행하려 하면 환경 레코드에 기록되어 있는 study의 갑은 undefined이고, undefined라는 데이터 타입은 함수와 달리 호출될 수 없기에 타입 에러가 발생한다.
const의 경우는 아직 환경레코드에 기록된 값이 없으므로 레퍼런스 에러가 발생
함수 선언식
선언과 동시에 함수가 생성되어 선언 전에도 함수를 사용할 수 있다.
js의 엔진이 study 함수의 선언과 동시에 완성된 함수 객체를 생성해서 환경 레코드에 기록해둔다.
그리고 study 함수를 실행하면 에러없이 실행된다.
함수 선언식을 사용하게 되면 선언 전에도 함수를 사용할 수 있게 돼서 사용을 지양하자는 의견도 있다.
함수 표현식과 함수 선언식의 차이
사진의 우측 하단에 단순화한 실행컨텍스트에서 두 동그라미를 합쳐서 렉시컬 인바이러먼트
라 하고 렉시컬 환경
또는 정적 환경
이라고도 한다.
렉시컬 환경이란?
코드 실행 전 변수와 함수 정보를 저장하는 객체로, 스코프를 관리하는 역할을 한다.
소스 코드 실행 시, 변수 이름(식별자)을 키로 이 환경에서 값을 찾는다.
- 언제 생성되나? 코드 블록, 함수, 스크립트 실행 전에 생성
- 역할: 변수, 함수의 선언 위치를 기준으로 스코프를 결정하고 값을 관리
lamp가 false 값인 것을 불이 꺼진 램프 그림으로 추상화했다.
js엔진은 현재 활성화된 실행컨텍스트의 환경레코드를 보고 lamp의 값을 쉽게 결정한다.
goTo2F라는 함수는 선언식이라 선언과 동시에 환경레코드에 통째로 기록될테고, 이 함수를 실행하면 새로운 실행컨텍스트가 생성된다.(매개변수가 있다면 그것도 환경레코드에 기록)
사진을 보면 불이 켜진 램프와 꺼진 램프가 있다. 어떤 것이 변수고 함수일까?
식별자 결정: 코드에서 변수나 함수의 값을 결정하는 것
함수의 실행 컨텍스트를 생성하면 js엔진은 생성된 실행 컨텍스트에 바깥 렉시컬 환경으로 돌아갈 수 있는 아우터를 남겨둔다.
이제 필요한 경우에 이전 실행 컨텍스트의 환경레코드에 저장된 식별자도 참조할 수 있다.
현재 실행 컨텍스트에 없으면 식별자를 찾아서 이전 렉시컬 환경으로 타고 내려간다는 말이다.
그리고 동일한 식별자로 인해 상위 스코프에서 선언된 식별자의 값이 가려지는 현상을 변수 섀도잉이라고 한다.
이렇게 식별자를 결정할 때 활용하는 스코프들의 연결리스트를 스코프 체인이라 하고, 식별자를 결정하기 위해 타고 타고 가는 과정을 스코프 체이닝이라 한다.
코드를 실행하는데 필요한 환경을 제공하는 객체
즉, 코드를 실행하는데 필요한 조건이나 상태를 모아둔 객체가 바로 실행 컨텍스트
실행 컨텍스트의 역사
ES3 시절에는 함수가 어디에서 호출되느냐에 따라 스코프가 달라져서 함수가 호출될 때마다 동적으로 그에 맞는 스코프를 생성하고 스코프체인을 연결해줘야 했다고 한다.
ES5부터는 식별자 결정을 위한 매커니즘이 변경되어 실행컨텍스트라는 하나의 묶음으로 관리하게 변경되었다.
스코프가 호출되는 위치와 상관없이 어디에 선언되어 있느냐에 따라 정적으로 결정되고, 하나의 컨택스트 개념으로 묶어뒀기에 js엔진은 더 빠르고 효율적으로 식별자를 결정할 수 있게 되었다.