Javascript는 클로저를 지원하는 언어이다. 클로저를 지원하는 언어들은 함수가 실행될 때의 환경정보를 객체형태로 저장하게 된다. 이를 실행 컨텍스트(Excution Context)라고 한다.
Javascript의 실행 컨텍스트는 3가지 경우가 있다. 전역 컨텍스트, eval 함수, 함수 실행이다. 이 중에 주로 사용하는 함수 실행 기준으로 확인해보겠다.
실행 컨텍스트의 구성은 다음과 같다.
Variable Environment를 구성하고 Lexical Environment에 복사하여 저장한다. 그리고 이후 Lexcal Environment는 변경사항들을 실시간으로 저장하게 된다. 즉, Variable Environment는 생성 당시의 스냅샷(원본), Lexical Environment는 실시간 추적 객체이다. Variable Environment와 Lexical Environment의 형태는 같기 때문에 Lexical Envrionment에 대해 정리하겠다.
Lexical Envrionment는 Environment Record와 Outer Environment Reference를 가지고 있는데 전자는 현재 컨텍스트의 실행 정보이고 후자는 외부 스코프의 Lexical Envrionemnt를 참조하는 객체이다.
Javascript가 실행되면 우선 전역 컨텍스트를 실행한다. 실행하는 과정 중에 함수를 만나면 새로운 함수 컨텍스트를 생성한다. 생성 당시의 변수, 함수 선언문과 같은 식별자를 수집하게 된다. 컨텍스트는 스택(Stack)로 쌓이게 된다.
Stack
FILO(First In Last Out)형태를 띈 자료구조로, 먼저 들어온 것이 나중에 나가는 방식이다.
스택 가장 상단에 있는 컨텍스트부터 실행하여 차례로 실행하게 된다.
함수가 실행되기 전, 함수에 존재하는 var로 선언한 변수, 매개변수, 함수 선언문 등을 수집하여 environmentRecord에 저장한다. 그러므로 함수가 실행되기 전부터 함수나 변수에 접근이 가능하게 된다. 이처럼 선언되기 전부터 변수나 함수에 접근 가능한 현상을 호이스팅이라고 한다. (작동방식이 변수나 함수를 끌어 올린 것처럼 작동해서 부르는 명칭)
var a = 1;
function outer() {
function inner() {
console.log(a);
var a = 3;
}
inner();
console.log(a);
}
outer();
console.log(a);
위의 결과는 undefined
, 1
, 1
이다. 그 중, 첫번째 결과인 Inner 함수의 실행 결과를 보자.
실행 컨텍스트는 전역 컨텍스트 - outer 컨텍스트 - inner 컨텍스트 순으로 쌓이고 inner 함수부터 실행된다.
inner()
함수 내부의 console.log(a)
는 a라는 변수는 없지만, 바로 아랫줄 var a
가 호이스팅 되어 a를 참조할 수 있게 된다. 하지만 값은 호이스팅 되지 않기 때문에 a의 값은 undefined
가 출력되게 된다.
function a() {
console.log(b);
var b = 'bbb';
console.log(b);
function b() {}
console.log(b);
}
a();
위의 결과는 function b
, ‘bbb’
, ‘bbb’
이다.
function b() {}
가 var b = function b() {}
와 같이 호이스팅 되어 실행되었기 때문에 첫번째 결과가 function b
가 된 것이다.
함수 선언문 vs 함수 표현식
함수 선언문과 함수 표현식은 차이가 있다. 함수 표현식은 함수를 값으로 표현하여 호이스팅 될 때 함수 전체가 호이스팅이 되지 않는다. 이와 반대로, 함수 선언문은 함수 선언문 자체가 호이스팅 된다.
프로그래밍에서 선언 후 사용이 일반적이기 때문에 함수 선언문 보다는 함수 표현식을 사용하는 것이 인지적으로 좋다.
식별자의 유효범위를 스코프라고 한다. 이는 일반적인 프로그래밍 언어에서 두루 사용된다. A의 내부에서 선언한 변수는 A의 내부에서는 접근가능하지만 A 외부에서는 접근할 수 없게 된다.
유효범위를 만드는 스코프는 함수를 통해 만드는 함수 스코프와 ES6+이상에서 적용할 수 있는데 블록 스코프 두가지가 있다. 블록 스코프는 let
, const
, class
등이 적용된다. 이와 달리 var
는 함수 스코프를 가진다.
식별자의 유효범위를 안에서 바깥으로 검색해 나가는 것을 스코프 체인이라고 한다. 이를 가능하게 하는 것은 OuterEnvironmentReference
이다. OuterEnvironmentReference
는 스코프가 생성 될(함수 호출 및 블록 생성) 당시의 LexicalEnvironment
를 참조한다. 이 체이닝 구조는 연결 리스트로 되어있다. 다음의 예를 보자
var a = 1;
function outer() {
function inner() {
console.log(a); // 1
var a = 3;
}
inner();
console.log(a); // 2
}
outer();
console.log(a); // 3
1번 console.log
는 자신의 EnvironemtRecord에 a
가 존재하기에 undefined
가 출력된다.
2번은 우선 outer
컨텍스트가 생성(outer
함수가 선언)될 때, OuterEnvrionmentReference 는 전역 컨텍스트의 LexicalEnvrionment 를 가르키게 된다. 여기서 outer
의 LexicalEnvironment의 environmentRecord에 a
가 없으므로 그 상위 스코프인 전역 LexicalEnvironment를 참조하고 있는 OuterEnvironmentReference 에서 a
를 찾게 된다. 전역 컨텍스트의 EnvironmentRecord에 a
가 있으므로 이 값을 출력하게 된다.
2번과 같이 자기 자신의 EnvironmentRecord부터 OuterEnvironmentReference를 타고 올라가서 식별자를 찾는 과정이 스코프 체이닝 과정이다.