<aside> 💡 - 실행 컨텍스트는 실행할 코드에 제공할 환경 정보들을 모아놓은 객체이다.
</aside>
실행 컨텍스트가 활성화되는 시점에 다음과 같은 절차가 일어난다. (Run Time)
실행 컨텍스트를 이해하기 위해서는 콜 스택에 대한 이해가 필요하다.
스택 vs 큐
스택은 바구니, 큐는 원통이라고 생각하면 좋다.
실행 컨텍스트 : 실행할 코드에 제공할 환경정보들을 모아놓은 객체
→ 동일 환경에 있는 정보를 모아 컨텍스트를 구성
→ 스택의 한 종류인 콜스택에 쌓아올림
→ 가장 위에 샇여있는 컨텍스트와 관련 코드를 실행
컨텍스트의 구성
구성 방법
실행 컨텍스트 구성 예시
// ---- 1번
var a = 1;
function outer() {
function inner() {
console.log(a); //undefined
var a = 3;
}
inner(); // ---- 2번
console.log(a);
}
outer(); // ---- 3번
console.log(a);
실행 순서
코드 실행 → 전역 → outer → inner → outer → 전역 → 종료
실행 컨텍스트 객체의 정보
현재 컨텍스트 내의 식별자 정보 (record)를 갖고 있다.
→ var a = 3의 경우 var a를 의미한다.
외부 환경 정보 (outer)를 갖고있다.
선언 시점 LexcialEnviroment의 snapshot
VE vs LE
이 두가지는 동일하나 snapshot 유지 여부에 따라 다르다.
이 두가지는 담기는 항목은 완벽하게 동일해요. 그러나, 스냅샷 유지여부는 다음과 같이 달라요.
결국, 실행 컨텍스트를 생성할 때, VE에 정보를 먼저 담은 다음, 이를 그대로 복사해서 LE를 만들고 이후에는 주로 LE를 활용한답니다.
현재 컨텍스트와 관련된 코드의 식별자 정보들이 저장(수집)돼요. **기록된다
**라고 이해해보면, **record
**라는 말과 일맥상통하죠?
수집 대상 정보 : 함수에 지정된 매개변수 식별자, 함수 자체, var로 선언된 변수 식별자 등
컨텍스트 내부를 처음부터 끝까지 순서대로 훑어가며 수집
<aside> 💡 순서대로 수집한다고 했지, 코드가 실행된다고 하지는 않았습니다!
</aside>
변수정보 수집을 모두 마쳤더라도 아직 실행 컨텍스트가 관여할 코드는 실행 전의 상태에요(JS 엔진은 코드 실행 전 이미 모든 변수정보를 알고 있는 것)
변수 정보 수집 과정을 이해하기 쉽게 설명한 ‘가상 개념’
<aside> 💡 가상개념이라는 말은, 실제로는 그렇진 않더라도 사람이 이해하기 쉬운 말로 풀어 표현했다는 것을 의미해요 😄
</aside>
호이스팅 법칙 1 : 매개변수 및 변수는 선언부를 호이스팅 합니다.
다음 주석에 딸려있는 3가지 action point에 따라 실습을 진행해보세요!
<적용 전>
//action point 1 : 매개변수 다시 쓰기(JS 엔진은 똑같이 이해한다)
//action point 2 : 결과 예상하기
//action point 3 : hoisting 적용해본 후 결과를 다시 예상해보기
function a (x) {
console.log(x);
var x;
console.log(x);
var x = 2;
console.log(x);
}
a(1);
<매개변수 적용>
//action point 1 : 매개변수 다시 쓰기(JS 엔진은 똑같이 이해한다)
//action point 2 : 결과 예상하기
//action point 3 : hoisting 적용해본 후 결과를 다시 예상해보기
function a () {
var x = 1;
console.log(x);
var x;
console.log(x);
var x = 2;
console.log(x);
}
a(1);
<호이스팅 적용>
//action point 1 : 매개변수 다시 쓰기(JS 엔진은 똑같이 이해한다)
//action point 2 : 결과 예상하기
//action point 3 : hoisting 적용해본 후 결과를 다시 예상해보기
function a () {
var x;
var x;
var x;
x = 1;
console.log(x);
console.log(x);
x = 2;
console.log(x);
}
a(1);
자, 우리는 예상을
1 → undefined → 2로 예상했지만
실제로는
1, 1, 2 라는 결과가 나왔네요
호이스팅이라는 개념을 모르면 예측이 불가능한 어려운 결과입니다.
호이스팅 법칙 2 : 함수 선언은 전체를 호이스팅합니다. 마찬가지로, 2가지 action points에 따라 진행해봅시다.
<적용 전>
//action point 1 : 결과 값 예상해보기
//action point 2 : hoisting 적용해본 후 결과를 다시 예상해보기
function a () {
console.log(b);
var b = 'bbb';
console.log(b);
function b() { }
console.log(b);
}
a();
<호이스팅 적용>
//action point 1 : 결과 값 예상해보기
//action point 2 : hoisting 적용해본 후 결과를 다시 예상해보기
function a () {
var b; // 변수 선언부 호이스팅
function b() { } // 함수 선언은 전체를 호이스팅
console.log(b);
b = 'bbb'; // 변수의 할당부는 원래 자리에
console.log(b);
console.log(b);
}
a();
해석을 편하게 하기 위해서 함수선언문을 함수 표현식으로 바꿔볼게요!
//action point 1 : 결과 값 예상해보기
//action point 2 : hoisting 적용해본 후 결과를 다시 예상해보기
function a () {
var b; // 변수 선언부 호이스팅
**var b = function b() { } // 함수 선언은 전체를 호이스팅**
console.log(b);
b = 'bbb'; // 변수의 할당부는 원래 자리에
console.log(b);
console.log(b);
}
a();
이번에도 우리의 예상은 틀렸네요.
에러(또는 undefined), ‘bbb’, b함수
라고 나올 것 같았지만, 실제로는
b함수, ‘bbb’, ‘bbb’
라는 결과가 나왔어요. 이 또한 호이스팅을 고려하지 않고는 결과를 예측하기가 매우 어려웠어요. 호이스팅을 다루는 김에, 함수의 정의방식 3가지와 주의해야 할 내용을 살짝 짚고 넘어가 보도록 하겠습니다 😉
함수 정의의 3가지 방식
// 함수 선언문. 함수명 a가 곧 변수명
// function 정의부만 존재, 할당 명령이 없는 경우
function a () { /* ... */ }
a(); // 실행 ok
// 함수 표현식. 정의한 function을 별도 변수에 할당하는 경우
// (1) 익명함수표현식 : 변수명 b가 곧 변수명(일반적 case에요)
var b = function () { /* ... */ }
b(); // 실행 ok
// (2) 기명 함수 표현식 : 변수명은 c, 함수명은 d
// d()는 c() 안에서 재귀적으로 호출될 때만 사용 가능하므로 사용성에 대한 의문
var c = function d () { /* ... */ }
c(); // 실행 ok
d(); // 에러!
주의!
위에서 정리해드린대로, LE는 record와 outer를 수집하죠. 그 중, record를 수집하는 과정에서 hoisting이 일어나고, 우리가 익히 알고있는대로 위로 쭉
끌어올려본 결과를 다시 써보면 아래와 같겠네요.
// 함수 선언문은 전체를 hoisting
function sum (a, b) { // 함수 선언문 sum
return a + b;
}
// 변수는 선언부만 hoisting
var multiply;
console.log(sum(1, 2));
console.log(multiply(3, 4));
multiply = function (a, b) { // 변수의 할당부는 원래 자리
return a + b;
};
어떤가요? 눈으로 볼 때는 몰랐지만, 함수 선언문과 함수 표현식은 hoisting 과정에서 극명한 차이를 보입니다. 차이는 알겠지만 이게 왜 위험한지 모르겠다구요? 네. 그러면 아래 예를 통해 그 이유를 확인해보죠.
함수 선언문을 주의해야 하는 이유
...
console.log(sum(3, 4));
// 함수 선언문으로 짠 코드
// 100번째 줄 : 시니어 개발자 코드(활용하는 곳 -> 200군데)
// hoisting에 의해 함수 전체가 위로 쭉!
function sum (x, y) {
return x + y;
}
...
...
var a = sum(1, 2);
...
// 함수 선언문으로 짠 코드
// 5000번째 줄 : 신입이 개발자 코드(활용하는 곳 -> 10군데)
// hoisting에 의해 함수 전체가 위로 쭉!
function sum (x, y) {
return x + ' + ' + y + ' = ' + (x + y);
}
...
var c = sum(1, 2);
console.log(c);
만약 함수 표현식이었다면…?
...
console.log(sum(3, 4));
// 함수 표현식으로 짠 코드
// 함수 선언부만 위로 쭉!
// 이 이후부터의 코드만 영향을 받아요!
var sum = function (x, y) {
return x + y;
}
...
...
var a = sum(1, 2);
...
// 함수 표현식으로 짠 코드
// 함수 선언부만 위로 쭉!
// 이 이후부터의 코드만 영향을 받아요!
var sum = function (x, y) {
return x + ' + ' + y + ' = ' + (x + y);
}
...
var c = sum(1, 2);
console.log(c);
협업을 많이 하고, 복잡한 코드일 수록. 전역 공간에서 이루어지는 코드 협업일 수록 **함수 표현식
**을 활용하는 습관을 들이도록 합시다!!
우리는 이미 앞서 ‘스코프’라는 용어에 대해 몇차례 언급을 해 왔습니다. 실행컨텍스트 관점에서의 스코프를 같이 이해해 보도록 합시다.
스코프
스코프 체인
식별자의 유효범위를 안에서부터 바깥으로 차례로 검색해나가는 것
outerEnvironmentReference(이하 outer)
우리는 지금까지 LE의 구성요소 record와 outer 중 record에 대해서 깊이 알아오고 있었어요. 이번 시간의 주인공은 그 두번째인 outer입니다. outer의 역할을 한 마디로 정의하자면
스코프 체인이 가능토록 하는 것(외부 환경의 참조정보)라고 할 수 있어요
위에 나온 개념을 좀 더 자세히 살펴보도록 하겠습니다.
A함수 내부에 B함수 선언 → B함수 내부에 C함수 선언(Linked List)
**한 경우 어떻게 될까요?전역 컨텍스트의 LexicalEnvironment를 참조
**하게 됩니다.