- Published on
코어 자바스크립트 Chap2
- Authors
- Name
- ywj9811
실행 컨텍스트
실행 컨텍스트는 실행할 코드에 제공할 환경 정보들을 모아놓은 객체이다.
동일한 환경에 있는 코드들을 실행할 때 필요한 환경 정보들을 모아 컨텍스트를 구성하고, 이를 콜 스택에 쌓아 올렸다가, 가장 위에 쌓여있는 컨텍스트와 관련 있는 코드들을 실행하는 식으로 전체 코드의 환경과 순서를 보장한다.
----------------------------------(1)
var a = 1;
function outer() {
function inner() {
console.log(a);
var a = 3;
}
inner(); -----------------------(2)
console.log(a);
}
outer(); -------------------------(3)
console.log(a);
최상단의 공간은 별도의 실행 명령이 없어도, 브라우저에서 자동으로 실행하므로 자바스크립트 파일이 열리는 순간 전역 컨텍스트가 활성화 된다.
콜스택에 쌓이는 순서대로 실행하기 때문에, (1)에서 전역 컨텍스트가 먼저 콜스택에 들어가고, (3)에서 outer가 들어가며 outer가 실행되다가, (2)에서 inner가 들어가며 inner가 실행되게 된다.
이어서 inner가 마무리되면, outer가, outer가 마무리되면 전역컨텍스트가 실행되며 콜스택이 비어지게 된다.
실행 컨텍스트와 환경 정보
어떤 실행 컨텍스트가 활성화 될 때 자바스크립트는 코드들을 실행하는 데 필요한 환경 정보들을 수집하여 실행 컨텍스트 객체에 저장한다.
- VariableEnvironment : 현재 컨텍스트 내의 식별자들에 대한 정보 + 외부 환경 정보. → 선언 시점의 LexicalEnvironment의 스냅샷으로, 변경 사항은 반영되지 않음
- LexicalEnvironment : 처음에는 VariableEnvironment와 동일하지만, 변경 사항이 실시간으로 반영됨
- ThisBinding : this 식별자가 바라봐야할 대상 객체
VariableEnvironment
담기는 내용은 LexicalEnvironment와 동일하지만, 최초 실행 시의 스냅샷을 유지한다.
→ 실행 컨텍스트를 실행할 때 VariableEnviroment에 정보를 담은 다음, 이를 복사하여 LexicalEnvrironment를 생성하여 이후에는 LexicalEnvironment를 주로 사용한다.
이 둘의 내부는 environmentRecord와 outer-EnviromentReference 로 구성되어 있다.
LexicalEnvironment
주로 찾아보면 ‘어휘적 환경’ ‘정적 환경’ 이라는 표현이 나오지만…
‘사전적인 환경’ 이 어울리는 말이라고 생각한다.
→ 백과사전에서 ‘바나나’를 찾아보면 ‘칼로리가 가장 높고 당질이 많은 알칼리성 식품으로 칼륨, 카로틴, 비타민C를 함유함’ 이라고 나오는 것과 같이, ‘현재 컨텍스트의 내부에는 a,b,c 와 같은 식별자가 있으며, 외부 정보는 D를 참조하도록 되어있다.’ 와 같은 정보를 가지고 있기 때문이다.
environmentRecord
이는 현재 컨텍스트와 관련된 코드의 식별자 정보들이 들어있다.
컨텍스트를 구성하는 함수에 지정된 매개변수 식별자, 선언한 함수가 있을 경우 그 함수 자체, 변수의 식별자 등이 해당한다. (컨텍스트 내부를 훑으며 순서대로 수집)
전역 실행 컨텍스트는 변수 객체를 생성하는 대신 자바스크립트 구동 환경이 별도로 제공하는 객체를 활용함 (전역 객체 → window, global 등등)
이를 통해 자바스크립트 엔진은 코드 실행 이전에 이미 해당 환겨에 속한 코드의 변수명을 모두 알고 있게 된다.
따라서 ‘자바스크립트 엔진은 식별자들을 최상단으로 끌어올려놓은 다음 실제 코드를 실행한다’ 처럼 동작하는 것으로 보이는 것이다.
이렇게 ‘호이스팅’ 이라는 개념이 등장하는 것이다.
호이스팅
fucntion a(x) {
console.log(x);
var x;
console.log(x);
var x = 2;
console.log(x);
}
a(1);
이때 호이스팅이 이루어지며 각각 어떻게 출력되게 될 것인지 생각해보자.
a(1)
을 호출한다고 가정해보자.
→ 이때 인자 x에 값이 들어온다면, 이는 함수 내부의 다른 코드들보다 먼저 선언 및 할당이 이루어진 것으로 볼 수 있다.
fucntion a() {
var x = 1;
console.log(x);
var x;
console.log(x);
var x = 2;
console.log(x);
}
마치 이런 모습일 것이다.
그렇다면 모두 호이스팅이 된 모습은 어떨까
fucntion a() {
var x;
var x;
var x;
x = 1;
console.log(x);
console.log(x);
x = 2;
console.log(x);
}
이런 모습일 것이다.
따라서 출력된 값은 순서대로 1, 1, 2 가 될 것이다.
function a() {
console.log(b)
var b = 'bbb'
console.log(b)
function b() {}
console.log(b)
}
a()
이러한 코드가 호이스팅 된 모습을 가정하고 본다면 다음과 같다.
function a()
var b;
function b() {};
// 변수 b에 대한 선언부가 수집되고, 함수 선언문은 전체가 수집됨
console.log(b);
b = 'bbb';
console.log(b);
console.log(b);
}
a();
호이스팅 규칙을 알고 보면 쉽게 알 수 있다.
- 변수명과 함수 선언의 정보를 수집한다. (위로 올린다)
- 변수는 선언부와 할당부를 나누어 선언부만 수집
- 함수는 함수 선언의 경우 함수 전체를 수집
- 함수 선언문의 경우 함수 전체 수집
- 함수 표현식의 경우 선언부만 수집 (함수 전체X)
함수 선언문과 함수 표현식
- 함수 선언문 function 정의부만 존재하고, 별도의 할당 명령이 없는 것
- 함수 표현식 function을 별도의 변수에 할당하는 것
이때, 함수 선언문은 함수명이 반드시 정의되어야 하지만, 함수 표현식은 없어도 된다.
함수 표션식 중 함수를 정의한 표현식을 ‘기명 함수 표현식’, 정의하지 않은 표현식을 ‘익명 함수 표현식’ 이라고 부른다.
→ 일반적으로 함수 표현식은 익명 함수 표현식을 의미함
function a {~~~~} // 함수 선언문 -> a가 곧 변수명
var b = function() {~~~~} // 익명 함수 표현식 -> 변수명 b 가 곧 함수명
var c = function d() {~~~} // 기명 함수 표현식 -> 변수명 c 함수명 d
주의할 점은, 기명 함수 표현식의 경우 밖에서 함수명으로 함수를 호출할 수 없다는 것!
이제 실행컨텍스트와 함께 함수 선언문과 함수 표현식의 실질적인 차이를 살펴보도록 할 것이다.
console.log(sum(1, 2))
console.log(multiply(3, 4))
function sum(a, b) {
return a + b
}
var multiply = function (a, b) {
return a * b
}
실행 컨텍스트의 lexicalEnvironment는 두가지 정보를 수집하는데, 그중 environmentRecord의 정보 수집 과정에서 발생하는 호이스팅을 살펴볼 것이다.
마찬가지로 호이스팅이 발생한 것을 가정하고 코들를 살펴보자.
function sum(a, b) {
return a + b
}
var multiply
console.log(sum(1, 2))
console.log(multiply(3, 4))
multiply = function (a, b) {
return a * b
}
함수 선언문은 전체를 호이스팅한 반면, 함수 표현식은 변수 선언부만 호이스팅 했다.
즉, 함수도 하나의 값으로 취급 받는 것을 알 수 있다.
만약 실행한다면 console.log(multiply(3, 4));
여기서 오류가 발생하고 종료될 것이다.
하지만, 이렇게 끌어올려지는 함수 선언문은 조심할 필요가 있다. 전역 컨텍스가 활성화 될 때 전역공간에 선언된 함수들이 모두 가장 위로 끌어올려지는데, 만약 동일한 변수명에 서로 다른 값을 할당할 경우 나중에 할당한 값으로 덮어씌워지게 된다. 따라서 이런 부분이 코드가 길어지면 문제가 될 수 있으니 조심해야 한다.
→ 오히려 모든 함수가 함수 표현식으로 작성되어 있다면 안전했을지도 모른다!
스코프, 스코프 체인
‘스코프’ 란 식별자에 대한 유효범위이다. 예를 들어 어떤 경계 A의 외부에서 선언한 변수는 A의 외부 뿐만 아니라 A에서도 접근이 가능하지만, A 내부에서 선언한 변수는 오직 A 내부에서만 사용할 수 있다.
이러한 스코프의 개념은 대부분의 언어가 가지고 있으니 어렵지 않다.
하지만, ES5 까지의 자바스크립트는 특이하게도 전역공간을 제외하면 오직 함수에 의해서만 스코프가 생성되게 된다.
→ 물론, ES6 가 되면서 블록 단위의 스코프 경계가 발생해 다른 언어와 유사해졌다. 다만, var로 선언한 변수에 대해서는 작용하지 않는다!
어쨋든, 이러한 식별자의 유효범위를 안에서부터 밖으로 차례대로 검색해 나가는 것을 ‘스코프 체인’ 이라고 한다.
그리고 여기서 사용되는 것이 LexicalEnvironment 의 두번째 수집 대상인 outerEnvironmentReference 이다.
예를 들어, A 함수 내부에 B 함수를 선언하고, B 함수 내부에 C 함수를 선언한 경우, 함수 C의 outerEnvironmentReference는 함수 B의 LexicalEnvironment를 참조한다. 그리고, B의 LexicalEnviroment에 있는 outerEnvironmentReference는 다시 함수 B가 선언되던 때(A)의 LexicalEnviroment를 참조할 것이다.
→ 이처럼 outerEnvironmentReference 는 마치 연결리스트와 같은 형태를 가진다.
만약 계속 타고 올라가면, 마지막에는 전역 컨텍스트의 LexicalEnvironment가 나올 것이다.
→ 이런 구조적 특성에 따라 여러 스코프에서 동일한 식별자를 선언한 경우에는 무조건 스코프 체인에서 가장 먼저 발견된 식별자에만 접근이 가능하다.
이렇게, 스코프체인을 타고 접근 가능한 변수의 수는 늘어나게 된다.
하지만, 전역 공간에서는 전역 스코프에서 생성된 변수에만 접근할 수 있고, outer함수에서는 outer에 선언된 변수 및 전역 스코프에서 선언된 변수에만 접근하고, 그 안의 inner함수에 정의된 변수에는 접근할 수 없다. 즉, 바깥쪽으로는 접근이 가능하지만 안쪽으로는 접근이 불가능한 것이다.
정리
마지막으로 정리하면, 실행 컨텍스트는 실행할 코드에 제공할 환경 정보들을 모아놓는 객체이다.
전역 공간에서 자동으로 생성되는 전역 컨텍스트와 eval 및 함수 실행에 의한 컨텍스트 등이 있다..
실행 컨텍스트 객체는 활성화 되는 시점에 VariableEnvrionment(초기 상태 유지), LexicalEnvironment(함수 실행 중 변경사항 적용), ThisBinding(this로 지정된 객체 저장) 세가지의 정보를 수집한다.
그중, VariableEnvironment와 LexicalEnvironment는 매개변수명, 변수의 식별자, 선언한 함수의 함수명 등 정보를 수집하는 environmentRecord와 직전 컨텍스트의 LexicalEnvironment를 참조하는 outerEnvironmentReference로 구성되어 있다.
스코프란, 변수의 유효범위를 말하며, 변수를 탐색할 때는 스코프체인을 타고 접근하게 된다.