[JavaScript]실행 컨텍스트
실행 컨텍스트는 학습하기에 구성이 꽤 복잡한데다 순전히 자바스크립트 스펙을 위한 메커니즘이기 때문에 직접 접근해서 확인하기가 어렵다.
실행 컨텍스트 안에 있는 record, outer 를 중점적으로 살펴보겠다.
1. record로 js 호이스팅 이해하기
선언 라인 전에도 에러가 나지 않고 변수를 참조할 수 있는 현상을 선언문이 마치 최상단에 끌어 올려진듯 하다고 해서 호이스팅 현상이라고 한다.
물리적으로 최상단에 끌어 올려졌기 때문이 아니라 자바스크립트 엔진이 전체 코드를 스캔하면서 변수같은 정보를 실행 컨텍스트 어딘가에 미리 기록해뒀기 때문이다.
이때 기록해두는 곳이 record이다. 정식 명칭은 environment record로 식별자와 식별자에 바인딩된 값을 기록해두는 객체이다.
이 환경 레코드에 변수가 어떻게 저장되는지 보면 호이스팅을 빠삭하게 이해할 수 있다.
변수 호이스팅과 함수 호이스팅이 있다.
console.log(a)
var a='hello'
console.log(a)
코드를 실행하면 전역 실행 컨텍스트에 한 칸을 생성해서 call stack에 넣는다. 그 후 전체 코드를 실행하면서 선언할게 있는지 찾아보고, 있다면 먼저 선언해준다.
선언하는 과정에서 생성해둔 실행컨텍스트 안에 있는 환경 레코드의 새로운 식별자가 변수 a를 기록해둔다. var로 선언했기 때문에 undefined로 값을 초기화 해준다.
이렇게 본격적인 실행에 앞서 스캔하고 준비하는 단계를 생성단계라고 한다.
생성단계 : Execution Context 생성. 실행 컨텍스트를 생성하고 선언문만 실행해서 environment record에 기록.
선언문 외에 나머지 코드를 순차적으로 실행하는데 이 단계를 실행단계라고 한다. 필요한 경우, 생성단계에서 환경 레코드에 기록해둔 정보를 참고하거나 업데이트 하게된다.
console.log도 실행되면 실행 컨텍스트가 생성된다.
선언은 아까 생성 단계에서 이미 했으니 할당만 실행해준다. a에 바인딩된 값을 hello로 업데이트해서 기록해둔다. 이제 마지막 console.log를 실행하면 자바스크립트 엔진은 환경 레코드를 참조해서 a의 값을 hello로 결정한다.
var 대신에 const 키워드로 변수를 선언하면 엔진이 a 식별자를 기록해두기는 하지만 값을 초기화하지는 않는다. 따라서 선언문 이전에 a 값을 참조하려 하면 reference error 가 발생한다.
let이나 const로 선언한 경우엔 선언 라인 이전에 식별자를 참조할 수 없는데, 선언 이전에 식별자를 참조할 수 없는 구역을 일시적 사각지대라고 한다.
var 키워드로 선언한 경우에는 선언과 초기화가 동시에 이뤄진다.
선언 단계에서는 메모리 공간을 확보하고 식별자와 연결해준다.
초기화 단계에서는 식별자에 암묵적으로 undefined 값이 바인딩된다.
반면 let, const는 undefined로 초기화하지 않는다. 자연스럽게 할당문 직전까지는 아무런 값이 담기지 않고, 유효한 값을 읽어올 수 없다. 이러한 동작 때문에 일시적 사각지대가 생긴다.
이러한 let const 키워드가 최근에 추가되었다는 것은 자바스크립트에서도 선언 라인 이전에는 변수를 참조할 수 없다는 일반적인 프로그래밍 방식을 추구할 수 있도록 언어 차원에서 보완되었다고 볼 수 있다.
함수 호이스팅.
study(); //Type Error
var study=()=>{
};
JS에서는 함수를 변수에 담을 수 있다. var키워드에 화실표 함수를 담아 선언문 이전에 실행하려고 하면, 환경 레코드에 기록되어있는 study값은 undefined이고, undefined라는 데이터 타입은 함수와 달리 호출될 수 없기 때문에 타입 에러가 발생합니다.
study(); //reference error
const study=()=>{
}
같은 함수를 const 키워드로 선언하면 아직 환경 레코드에 기록된 값이 없어 reference error가 발생한다.
이렇게 변수에 함수를 담아서 선언하는 방식을 함수 표현식이라고 하는데 함수를 변수에 담고 있기 때문에 앞서 살펴본 변수 호이스팅과 똑같이 동작한다.
이번에 변수에 함수를 담지 않고 function 키워드로 선언해보겠다.
study();
function study(){
}
함수 선언문 방식으로 함수를 선언하는 경우에는 JS엔진이 study함수의 선언과 동시에 완성된 함수 객체를 생성해서 환경레코드에 기록해둔다.
그리고 study 함수를 실행하면 드디어 study 함수가 에러없이 실행되었습니다. 이렇게 변수에 담지 않고 함수 선언문 방식으로 선언한 함수의 경우에는 선언과 동시에 함수가 생성된다는 점이 큰 특징이다.
함수 선언문은 선언과 동시에 함수가 생성되어 선언 전에도 함수를 사용할 수 있다.
참고로 이 방식은 선언 전에도 함수를 사용할 수 있게 되어서 사용을 지양하고자 하는 목소리도 있다.
함수 표현식, 함수 선언문 두 경우에 환경 레코드를 살펴보면 두가지 차이점을 잘 정리해 둘 수 있다.
함수 표현식
- var : 환경 레코드의 값을 undefined로 초기화해둔다. undefined를 호출하려 하면 type error가 난다.
- let, const : 환경 레코드에 값을 초기화해두지는 않아서 참조하려니까 reference error가 난다.
함수 선언문
- 함수 선언과 동시에 함수 생성을 마치고 온전하게 환경 레코드에 저장해두기 때문에 선언 라인 전에도 함수를 호출할 수 있었다.
2. outer로 js 스코프 체이닝 이해하기
outer의 정식 명칭은 Outer Environment reference .외부 환경 참조이다.
바깥 lexical environment를 가리킨다. lexical environment는 outer와 record를 합친 것을 말한다.
렉시컬 환경 또는 정적 환경이라고 부른다.
코드에서 변수나 함수의 값을 결정하는 것을 식별자 결정이라고 부른다. 앞서 살펴본 1번 호이스팅에서도 자바스크립트 엔진이 어떤 식별자의 값을 결정하는 일을 반복하고 있다는 것을 느꼈을 것이다.
식별자 결정은 이번 설명에서 가장 중요한 키워드이기도 하다.
콜 스택 안에 동일한 식별자가 여럿일때 자바스크립트 엔진이 어떻게 outer를 활용해서 의사 결정을 하는지 알아보자.
let lamp=false;
function goTo2F(){
let lamp=true;
function goTo3F(){
let pet='puppy';
console.log(pet);
console.log(corona)
console.log(lamp);
}
goTo3F();
}
goTo2F();
변수 섀도잉 : 동일한 식별자로 인해 상위 스코프에서 선언된 식별자의 값이 가려지는 현상.
현재 활성화된 실행 컨텍스트는 하나이지만 이전 렉시컬 환경을 가리키는 outer로 타고갈 수 있기 때문에 3층에 없으면 2층으로 가서 찾고, 2층에 없으면 전역으로 가서 찾을 수 있다.
이렇게 식별자를 결정할 때 활용하는 스코프들의 연결 리스트를 스코프 체인이라고 하고, 식별자를 결정하기 위해 타고 타고가서 찾는 과정 자체를 스코프 체이닝이라고 한다.
제일 중요한 것은 식별자 결정.
3. Execution Context (실행 컨텍스트) 정리.
실행 컨텍스트 : 코드를 실행하는데 필요한 환경을 제공하는 객체라고 할 수 있다. 여기서 환경이란 코드 실행에 영향을 주는 조건이나 상태를 말한다. 따라서 코드를 실행하는데 필요한 조건이나 상태를 모아둔 객체가 바로 실행 컨텍스트이다.
그런데 실행컨텍스트가 처음부터 이렇게 식별자 결정의 중심에 있지는 않았다. ES3 당시에는 함수가 어디에서 호출되느냐에 따라 스코프가 달라졌다. 즉, 함수가 호출될 때마다 동적으로 그에 걸맞는 스코프 체인을 생성하고 스코프 체인을 연결해줘야 했다.
ES5에 들어서는 식별자 결정을 위한 메커니즘이 변경되었다. 실행 컨텍스트라는 하나의 덩어리, 하나의 묶음으로 관리하도록 변함. 스코프가 호출되는 위치와 상관없이 어디에 선언되어있느냐에 따라 정적으로 결정되고, 하나의 컨텍스트 개념으로 묶어 놓았기 때문에 자바스크립트 엔진은 더 빠르고 효율적으로 식별자를 결정할 수 있게 되었다.
따라서 실행 컨텍스트는 코드를 실행할 때 식별자를 더욱 효율적으로 결정하기 위한 수단으로서 필요한 정보를 한데 모아 제공하는 객체이다.