이전에 변수 이야기를 하며 호이스팅, 스코프 체인 이야기를 언급했는데요. 왜 그런 현상이 일어나는지 궁금하지 않으셨나요? 실행 컨텍스트를 알게 되면 많은 궁금증이 풀리게 됩니다. 얼른 실행 컨텍스트에 대해 알아보러 갑시다.
실행 컨텍스트란?
실행 컨텍스트란 코드를 실행할 때 필요한 문맥을 의미합니다. 글을 읽을 때 문맥을 파악하라는 말을 자주 하죠? 어휘의 의미와 문장의 앞 뒤 상황을 이해하여 글의 흐름을 파악하라는 말인데요. 실행 컨텍스트도 이와 비슷합니다. 실행 컨텍스트는 코드의 실행과 관련된 환경 정보입니다. 이를 통해 현재 실행되는 코드 내의 식별자 정보와 외부 환경 참조, 함수 실행 순서 등을 관리하게 되는 것이죠.
실행 컨텍스트의 생성/제거 시점
실행 컨텍스트는 코드가 실행되기 전 생성되며, 코드 실행이 완료되면 제거됩니다. 자바스크립트 소스 코드 타입에 따른 실행 컨텍스트가 생성/제거 시점은 다음과 같습니다
- 전역 코드
함수, 코드 블럭 외부의 코드로 코드가 처음 실행될 때 컨텍스트가 생성되고 코드가 모두 실행되면 제거됩니다.
- 함수 코드
함수 내부에 존재하는 코드로 함수가 호출 될 때 컨텍스트가 생성되고 함수가 종료되는 시점에서 컨텍스트가 제거됩니다. if문, for문과 같은 문(statement)에서는 실행 컨텍스트가 생성되지 않습니다.
- eval 코드
문자열로 표현된 코드를 실행할 수 있게 해주는 eval 함수에 인수로 전달되어 실행되는 코드입니다. eval 함수가 호출될 때 컨텍스트가 생성되고, eval 함수가 실행한 코드가 종료되는 시점에 컨텍스트가 종료됩니다.
eval은 문자열로 입력받은 코드가 악의적인 내용을 포함하는 등 보안상 위험이 생길 수 있기에 사용을 지양하고 있습니다.
- module 코드
자바스크립트 module 내부에 존재하는 코드로 module이 import 되는 순간 컨텍스트가 생성되며 해당 모듈 코드가 종료될 때 컨텍스트가 제거됩니다.
콜 스택을 통한 함수 실행 순서 관리
일단 콜 스택에 대해 알아보기 전에, 스택에 대해 알고 계시나요? 스택이란 후입선출(LIFO, Last In First Out) 방식의 자료구조를 의미합니다. 즉, 마지막에 들어온 데이터가 가장 먼저 나가게 되는 것이죠.
콜 스택은 자바스크립트에서 함수 호출 시 생성되는 실행 컨텍스트를 관리하는 스택 자료구조입니다. 함수가 호출될 때 마다 콜 스택에 호출된 함수의 실행 컨텍스트가 차곡차곡 쌓이게 됩니다. 이때 가장 위에 쌓인 실행 컨텍스트를 가진 함수가 가장 먼저 실행됩니다. 즉 가장 니중에 호출된 함수가 가장 먼저 실행되는 것이죠.
함수 실행이 완료되면, 그 함수의 실행 컨텍스트는 콜 스택에서 제거되고 그 다음으로 쌓인 실행 컨텍스트의 함수가 실행됩니다. 이렇게 콜 스택을 통해 함수의 실행 순서를 보장하게 되는 것이죠.
javascript console.log("1. 전역 컨텍스트"); function outerFunc () { console.log("1. outerFunc 컨텍스트"); function innerFunc () { console.log("2. innerFunc 컨텍스트"); } innerFunc(); } outerFunc(); console.log("4. 함수 실행 컨텍스트가 모두 제거 된 후 전역 컨텍스트만 call stack에 남음");
위와 같은 코드가 있을 때 콜 스택에 컨텍스트가 쌓이는 과정을 보겠습니다.
- 전역 컨텍스트 생성 -> 콜 스택에 쌓임 -> 'console.log("1. 전역 컨텍스트")' 실행
- outerFunc 호출 -> outerFunc 실행 컨텍스트 생성 -> 콜 스택에 쌓임 -> 'console.log("2. outerFunc 컨텍스트");' 실행
- innerFunc 호출 -> innerFunc 실행 컨텍스트 생성 -> 콜 스택에 쌓임 -> 'console.log("3. innerFunc 컨텍스트")' 실행
- innerFunc 함수 종료 -> 콜 스택에서 제거.
- outerFunc로 복귀 -> outerFunc 함수 종료 -> 콜 스택에서 제거.
- 전역 컨텍스트로 복귀 -> console.log("4. 함수 실행 컨텍스트가 모두 제거 된 후 전역 컨텍스트만 call stack에 남음"); 실행, 코드가 모두 실행된 후 콜 스택에서 제거.
이와 같이 코드가 실행되다가 함수가 호출되면 해당 함수의 실행 컨텍스트가 콜스택에 쌓이게 되고 해당 함수가 실행됩니다. 함수가 종료되고 콜스택에서 제거되면 이전 컨텍스트의 함수가 이어서 실행되게 됩니다.
실행 컨텍스트에 담긴 정보
그럼 이제 실행 컨텍스트에 어떤 정보가 담겨있을지 알아보도록 하겠습니다.
- VariableEnvironment (변수 환경)
VariableEnvironment는 컨텍스트 생성 시의 식별자 정보와 외부 환경 참조 정보를 가지고 있습니다. 컨텍스트 생성 될 때 컨텍스트의 내의 식별자를 수집합니다. var의 경우 수집과 동시에 undefined로 초기화됩니다. 컨텍스트 생성 당시의 상태를 기억하며 코드 진행 중에 값이 변경되지 않습니다. LexicalEnvironment와 동일한 구조를 가지기 때문에 자세한 구조에 대해선 LexicalEnvironment에서 이야기 해보겠습니다.
- LexicalEnvironment (어휘적 환경)
LexicalEnvironment 역시 식별자 정보와 외부 환경 참조 정보를 가지고 있습니다. 컨텍스트가 생성 될 시점에는 VariableEnvironment를 복사하기 때문에 값이 동일하지만 코드가 실행됨에 따라 식별자 정보를 업데이트 합니다. (const, let으로 선언한 변수의 초기화, 변수 값 할당, 재할당 등) 실제로 코드가 실행되는 동안에는 LexicalEnvironment를 통해 식별자 정보가 관리됩니다.
- environmentRecord (환경 기록)
컨텍스트 내에서 수집된 식별자 정보가 있습니다.
- outerEnvironmentReference (외부 환경 참조)
현재 실행 컨텍스트의 외부 환경을 참조하는 정보입니다. 현재 컨텍스트에 식별자가 존재하지 않을 경우 외부 스코프에서 식별자를 검색합니다. 이를 통해 상위 스코프의 식별자에 접근할 수 있는 것을 스코프체인이라고 합니다. 내부 함수에서는 외부 함수의 식별자를 참조할 수 있지만 외부 함수에서 내부 함수에 참조할 수 없는 이유이기도 합니다.
호이스팅
코드가 실행되기 전 식별자를 수집하는 과정에서 식별자가 코드의 상단으로 끌어올려진 것처럼 동작하는 호이스팅 현상이 발생하게 됩니다. 즉, 실제로 식별자가 코드 위로 끌어올려진 것이 아니라 environmentRecord에 수집된 식별자를 컨텍스트 내에서 접근할 수 있게 되는 것이죠. 이로인해 var를 사용한 변수 선언, 함수 선언문에 도달하지 않아도 해당 식별자에 접근할 수 있게 됩니다.
var와 const, let의 차이
var는 변수 선언이 수집됨과 동시에 undefined로 초기화 되기 때문에 변수 선언문 전에 해당 변수에 접근이 가능했습니다. 이런 예기치 못한 동작을 보완하기 위해 나온 const, let은 선언문 이전에 변수가 초기화 되지 않는데요. 코드가 실행됨에 따라 변수 선언문에 도달했을 때 비로소 변수가 초기화되고 LexicalEnvironment가 업데이트 됩니다. 따라서 세 가지 키워드로 선언된 변수 모두 호이스팅 현상이 발생하지만 const, let은 변수 수집 후 초기화 되기 이전 영역인 TMZ(일시적 사각지대)에서는 변수에 접근할 수 없기 때문에 변수 선언문 이전에 접근할 수 없게 되는 것이죠.
블록은 어떻게 스코프를 생성할까?
실행 컨텍스트는 함수 호출 시에 생성된다고 이야기 했습니다. 그렇다면 블록에서는 어떻게 스코프가 만들어질까요? 바로 블록 코드가 실행될 때 LexicalEnvironment가 생성되기 떄문입니다. 이를 통해 블록 내부 식별자를 관리하고 스코프 체인을 통해 외부 환경의 식별자에도 접근할 수 있게 됩니다. 그리고 블록 내의 코드가 모두 종료 되었을 때 LexicalEnvironment가 제거됩니다.
- ThisBinding(This 바인딩)
This Binding은 this의 값을 결정하는 정보로, 함수 호출 시 this가 참조할 객체를 결정합니다.
포스팅을 마무리하며
이렇게 자바스크립트 코드가 실행될 때의 환경 정보인 실행 컨텍스트에 대해서 알아보았습니다. 이를 통해 호이스팅, 스코프 체이닝, this 바인딩 등 자바스크립트에서 발생하는 다양한 현상들에 대해 이해할 수 있었습니다. 이번 포스팅을 통해 원래 그런 것, 그냥 그런 것은 없다는 사실도 다시끔 느끼게 되었네요.
사실 실행 컨텍스트는 블로그를 시작하며 언젠가 꼭 다뤄야지 했던 주제입니다. 처음 변수 포스팅을 할 때도 '아 이걸 설명하려면 실행 컨텍스트 이야기를 해야하는데' 하면서 어디부터 어디까지 이야기해야하나 참 고민이 많았는데 이렇게 포스팅할 수 있게 되어서 숙제를 덜어낸 기분입니다. 그럼 후련한 마음으로 글을 마무리 하도록 하겠습니다. 여기까지 긴 글 읽어주셔서 감사합니다.😊