실행 컨텍스트
실행 컨텍스트란?
- 실행할 코드에 제공할 환경 정보들을 모아 놓은 객체.
- 자바스크립트는 어떤 실행 컨텍스트가 활성화되는 시점에 선언된 변수를 위로 끌어올리고(호이스팅), 외부 환경 정보를 구성하고, this 값을 설정한다.
- 동일한 환경에 있는 코드들을 실행할 때, 필요한 환경 정보들을 모아 컨텍스트를 구성하고 이를 콜 스택에 쌓았다가 가장 위에 있는 컨텍스트와 관련있는 코드들을 실행하는 식으로 전체 코드의 환경과 순서를 보장한다.
- 우리가 실행 컨텍스트를 구성하는 방법은 함수를 실행하는 것
- 실행 컨텍스트 객체는 활성화되는 시점에 VariableEnvironment, LexicalEnviornment, ThisBinding 세 가지 정보 수집
- 실행 컨텍스트를 생성할 때 VariableEnvironment와 LexicalEnviornment가 동일한 내용으로 구성되지만 LexicalEnviornment는 함수 실행 도중에 변경되는 사항이 즉시 반영. VariableEnvironment는 초기 상태 유지.
- environmentRecord는 매개변수명, 변수의 식별자, 선언한 함수의 함수명 등 수집. 바로 직전의 LexicalEnviornment 정보를 참조하는 outerEnviromentRecord로 구성
렉시컬 환경(Lexical Environment)
- "현재 컨텍스트의 내부에 a,b,c 식별자들이 있고 외부 정보는 D를 참조하도록 구성돼있다"라는 컨텍스트를 구성하는 환경 정보들을 정의하여 모아놓은 것
호이스팅
- 호이스팅은 변수 정보를 수집하는 과정에서 더욱 이해하기 쉬운 방법으로 대체한 가상 개념
- 실행 컨텍스트가 활성화 되면 자바스크립트 엔진은 해당 컨텍스트에 관련된 코드들을 실행하는 데 필요한 환경정보를 수집해서 실행 컨텍스트 객체에 저장한다. 이 객체에 담기는 정보들은 VariableEnvironment(environmentRecord, outerEnviromentRecord), LexicalEnviornment(environmentRecord, outerEnviromentRecord), ThisBinding
- environmentRecord 에는 현재 컨텍스트와 관련된 코드의 식별자 정보들이 저장된다.
원본 코드
const a = (x) => {
console.log(x);
var x;
console.log(x);
var x = 2;
console.log(x);
}
a(1)
- environmentRecord는 현재 실행될 컨텍스트의 대상 코드 내에 어떤 식별자가 있는지 관심. 각 식별자에 어떤 값이 할당될 것인가 관심 없다. 따라서 변수를 호이스팅할 변수명만 끌어올리고 할당 과정은 원래 자리에 남겨둔다. 따라서 변수를 호이스팅 할 때는 변수명만 끌어올린다.
호이스팅 마친 상태
const a = (x) => {
var x;
var x;
var x;
x = 1;
console.log(x); // 1
console.log(x); // 1
x = 2;
console.log(x); // 2
}
a(1)
- 함수의 경우 함수 선언은 전체를 끌어올린다. 함수 표현식은 변수 선언부만 호이스팅한다.
함수 선언문과 함수 표현식
- 함수 선언문은 function 정의부만 존재하고 별도의 할당 명령이 없는 것, 반드시 함수명이 정의되어야 함.
- 함수 표현식은 정의한 function을 별도의 변수에 할당하는 것. 일반적으로 익명 함수 표현식을 말함
함수 선언문
funtion a () { // 함수명 a가 곧 변수
...
}
a()
(익명) 함수 표현식
const b = function () { // 변수명 b가 곧 함수명
...
}
b()
기명 함수 표현식
const c = function d () { // 변수명 c, 함수명 d
...
}
c()
스코프
- 식별자에 대한 유효범위. A의 외부에서 선언한 변수는 A의 외부뿐 아니라 A의 내부에서도 접근이 가능하지만 A의 내부에서 선언한 변수는 오직 A의 내부에서만 접근이 가능하다. 오직 함수에 의해서만 스코프만 형성된다.
- 식별자의 유효범위를 안에서 바깥으로 차례대로 검색해가는 것을 스코프 체인이라고 한다.
- 이를 가능케하는 것이 LexicalEnvironment의 두 번째 수집 자료인 outerEnviromentReference
스코프 체인
- outerEnviromentReference는 현재 호출된 함수가 선언될 당시의 LexicalEnvionment를 참조한다.
- 선언 시점의 LexicalEnvionment를 계속 찾아 올라가면 마지막엔 전역 컨텍스트의 LexicalEnvionment가 있을 것이다.
- 또한, outerEnviromentReference는 오직 자신이 선언된 시점의 LexicalEnvionment만 참조하고 있으므로 가장 가까운 요소부터 차례대로만 접근할 수 있도 다른 순서로는 불가능하다.
- 이러한 구조적 특성 덕분에 여러 스코프에서 동일한 식별자를 선언한 경우에 무조건 스코프 체인 상에서 가장 먼저 발견된 식별자에만 접근 가능하다.
스코프 체인
var a = 1;
var outer = function(){
var inner = function(){
console.log(a)
var a = 3;
}
inner()
console.log(a)
}
outer();
console.log(a);
스코프 체인 부수기
-
시작과 동시에 전역 컨텍스트 활성화. 전역 컨텍스트의 environmentRecord { a, outer } 식별자 저장. 전역 컨텍스트는 선언 시점이 없으므로 전역 컨텍스트의 outerEnviromentReference에는 아무것도 없다.
-
전역 스코프에 있는 변수 a에 1을, outer에 함수를 할당
-
outer 함수 호출. 전역 컨텍스트 코드는 잠시 중단 그리고 outer의 실행 컨텍스트가 활성화 된다.
-
outer 실행 컨텍스트의 environmentRecord에 { inner } 식별자를 저장한다. outer 함수는 전역 공간에서 선언됐으므로 전역 켄텍스트의 LexicalEnviroment를 참조복사한다. 이를 [GLOBAL, { a, outer} ] 라고 표기. 첫 번째 요소는 실행 컨텍스트, 두 번째 요소는 environmentRecord 객체
-
outer 스코프에 있는 변수 inner에 함수를 할당
-
inner 함수를 호출. 이에 따라 outer 실행 컨텍스트는 임시 중단. inner 실행 컨텍스트 활성화
-
inner 실행 컨텍스트의 environmentRecord에 { a } 식별자 저장. inner 함수는 outer 함수 내부에서 선언됐으므로 outer 함수의 LexicalEnvironment 즉, [outer, { inner }]를 참조 복사한다.
-
식별자 a에 접근하고자 함. 현재 활성화 상태인 inner 컨텍스트의 environmentRecord에서 a를 검색. a가 있지만 아직 할당된 값은 없다.
-
inner 스코프에 있는 변수 a에 3를 할당
-
inner 함수 실행 졸요. inner 실행 컨텍스트가 콜 스택에서 제거. outer 실행 컨텍스트가 다시 활성화되면서 console.log(a)
에 접근. 이때 자바스크립트 엔진은 활성화된 실행 컨텍스트의 LexicalEnvironment에 접근한다. 첫 요소의 environmentRecord에서 a가 있는지 찾고 없으면 outerEnviromentReference에 있는 environmentRecord로 넘아가는 식으로 계속 검색한다. 전역 LexicalEnvironment에 a가 있으니 그 a에 저장된 값 1을 반환한다.
11.outer 함수 실행이 종료된다. outer 실행 컨텍스트가 콜 스택에서 제거됨. 바로 아래의 전역 컨텍스트가 다시 활성화됨
-
console.log(a);
식별자 a에 접근. 현재 활성화 상태인 전역 컨텍스트인 environmentRecords에서 a를 검색한다. 바로 a를 찾을 수 있다.
- 정리 : 전역 컨텍스트 -> outer 컨텍스트 -> inner 컨텍스트 순으로 규모가 작아지는 반면 스코프 체인을 타고 접근 가능한 변수는 늘어난다.