[10분 테코톡] 💙 하루의 실행 컨텍스트(15분) 를 보고 정리한 글입니다 :)
ECMAScript 스펙에 따르면 실행 컨텍스트를 실행 가능한 코드를 형상화하고 구분하는 추상적인 개념이라고 정의합니다. 쉽게 말하자면 실행 컨텍스트는 실행 가능한 코드가 실행되기 위해 필요한 환경 이라고 말할 수 있습니다. 정말 무슨 말인지 모르겠네요 😵
실행 컨텍스트를 알면 scope, hoisting, closure 등 자바스크립트의 주요한 동작을 이해할 수 있다고 합니다. 하지만 이를 쉽게 학습하기는 어렵습니다. 위의 이미지와 같이 구성이 복잡하고 직접 코드를 확인한다거나 하기가 어렵습니다.
그래서 렉시컬 환경(Lexical Environment) 카테고리의 레코드(Environment Record)와 아우터(Outer Environment Reference)를 중점적으로 살펴보도록 할 것입니다.
자바스크립트 코드를 실행시키면 자바스크립트 엔진은 실행 컨택스트 스택(Execution Context Stack) 이라는 통에 전역 실행 컨텍스트(Global Execution Context) 를 담습니다. 이때 전역 실행 컨텍스트 안에는 Record와 Outer가 담겨있게 됩니다.
만약 전역에서 함수 A를 호출한다고 가정해보겠습니다. 이때 함수 A의 실행 컨텍스트를 생성해서 또 콜 스택에 담습니다. 콜 스택에서는 가장 최근에 추가된 실행 컨텍스트만 활성화 됩니다. 쉽게 말하면 위에서 스택이라는 통을 내려다봤을 때 보이는 실행 컨텍스트가 활성화된 실행 컨텍스트 입니다. 그렇다면 현재 활성화된 실행 컨텍스트는 함수 A의 실행 컨텍스트라고 볼 수 있습니다.
만약 함수 A다음으로 함수 B도 있었다고 가정 하겠습니다. 함수 B가 선언 및 실행을 마치고 종료되면 함수 B의 실행컨텍스트가 사라집니다. 그러면 함수 A로 돌아갑니다. 이어서 함수 A가 종료되면 이 실행 컨텍스트도 사라집니다. 전역에 있는 코드가 마지막 라인까지 모두 실행되면 전역 실행 컨텍스트도 사라집니다.
(이해를 돕기 위한 이미지 입니다)
console.log(TVChanel); // undefined
var TVChanel = "Netflix";
console.log(TVChanel); // Netflix
자바스크립트는 다른 언어와 다르게 선언 전에도 에러가 나지 않고 변수를 참조할 수 있습니다. 이를 호이스팅(Hoisting: 선언문이 마치 최상단에 끌어올려진 듯한 현상) 이라고 합니다. 이런 현상이 가능한 이유는 코드를 물리적으로 최상단으로 끌어올린 것이 아니라, 자바스크립트 엔진이 먼저 전체 코드를 스캔하면서 변수 정보를 실행컨텍스트 어딘가에 미리 기록해놓기 때문입니다.
이때 기록해놓는 곳이 바로 환경 레코드(Environment Record: 식별자와 식별자에 바인딩된 값을 기록하는 객체) 입니다. 이 환경 레코드에 변수가 어떻게 저장되는지만 봐도 호이스팅을 이해하기 쉽다고 합니다! 다음으로는 호이스팅을 두가지 종류로 나누어서 살펴보겠습니다.
생성 단계 (Creation Phase)
Execution Context 생성, 선언문만 실행해서 Environment Record에 기록
자바스크립트 엔진은 코드를 실행하면
전역 실행 컨텍스트 한 칸을 생성해서 콜스택에 넣습니다.
전체 코드를 스캔하면서 선언할게 있는지 찾아보고 있다면 먼저 선언해둡니다.
선언하는 과정에는 생성해둔 실행 컨텍스트 안에 있는 환경레코드 에 새로운 식별자 TV Channel을 기록합니다. 그리고 var 키워드로 선언했기 때문에 값을 undefined
로 초기화 해둡니다.
실행 단계 (Execution Phase)
선언문 외 나머지 코드 순차적 실행, Environment Record 참조하거나 업데이트
선언은 아까 생성단계에서 했습니다. TV Channel에 바인딩 된 값(undefined)를 netflix로 업데이트 해서 기록해 둡니다.
이후 마지막 라인을 실행하면 자바스크립트 엔진은 환경레코드를 참조해서 TV Channel의 값을 Netflix로 결정해냅니다. (console.log가 Netflix를 출력)
만약 var
키워드 대신에 const
키워드를 사용하게 된다면 어떻게 될까요? 이 경우에는 엔진이 TV Channel 식별자를 기록해두긴 하지만 값을 초기화하지는 않습니다. 따라서 선언문 이전에 TV Channel을 참조하려고 하면 Reference Error을 만나게 됩니다. 자바스크립트 엔진이 아직 TV Channel의 값을 읽어올 수 없었기 때문입니다.
이렇게 let 또는 const로 선언했을때, 선언 이전에 식별자를 참조할 수 없는 구역을 일시적 사각지대 TDZ(Temporal Dead Zone) 라고 부릅니다. 이렇게 var 키워드와 다르게 동작하는 let/const 키워드가 비교적 최근에 추가되었다는 건 자바스크립트에서도 '선언 라인 이전에는 변수를 참조할 수 없다' 는 일반적인 프로그래밍 방식을 추구할 수 있도록 '언어 차원에서 보완되었다'라고도 볼 수 있습니다.
자바스크립트 에서는 함수를 변수에다가 담을 수 있습니다. 만약 var 키워드에 화살표 함수를 담아 선언문 이전에 실행하려고 하면 환경레코드에 기록되어 있는 study의 값은 undefined이고 undefined는 함수와 달리 호출될 수 없기 때문에 타입 에러가 발생합니다.
같은 함수를 const 키워드로 선언하면 아직 환경레코드에 기록된 값이 없어 Reference Error가 발생합니다. 이렇게 변수에 함수를 담아서 함수를 선언하는 방식을 함수 표현식(Function Expression: 함수 호이스팅과 동일하게 동작)이라고 합니다. 함수를 변수에 담고 있기 때문에 앞서 살펴본 변수 호이스팅과 똑같이 동작합니다.
이번에는 변수에 함수를 담지 않고 function 키워드로 선언해보겠습니다. 함수 선언문(Function Declation: 선언과 동시에 함수가 생성되어 선언 전에도 함수를 사용할 수 있음) 방식으로 함수를 선언하는 경우에는 자바스크립트 엔진이 study 함수의 선언과 동시에 완성된 함수 객체를 생성해서 환경 레코드에 기록해 둡니다. 그리고 study 함수를 실행하면 드디어 study 함수가 에러 없이 실행됩니다. 이렇게 변수에 담지 않고 함수 선언문 방식으로 선언한 함수의 경우에는 선언과 동시에 함수가 생성된다는 점이 큰 특징입니다. (참고로 이 방식을 사용하면 선언 전에도 함수를 사용할 수 있기 때문에는 요즘에는 지양하는 방식이라고 합니다 ❌)
함수 표현식, 함수 선언문 두 경우에 환경레코드를 살펴보면 두가지 차이점을 잘 정리해둘 수 있습니다. 함수 표현식은 var 키워드 일때는 환경레코드의 값을 undefined로 초기화해두는데 이를 호출 하려고해서 Type Error가 났었습니다.
let const일때는 환경레코드에 값을 초기화해두지 않아서 참조하려니까 Reference Error가 났습니다.
함수 선언문은 선언과 동시에 함수 생성을 마치고 온전하게 환경레코드에 저장해두기 때문에, 선언 라인 전에도 함수를 호출할 수 있습니다.
[JavaScript] 실행 컨텍스트 (Execution Context) - 2 보러가기