이전에 학습한 내용들을 복기하기 위하여 작성한 내용입니다. (업데이트: 2022년 4월 12일)
✔️ 실행 컨텍스트는 실행할 코드에 제공할 환경 정보들을 모아놓은 객체이다.
// ----------------------- (1)
var a = 1;
function outer() {
function inner() {
console.log(a);
var a = 3;
}
inner(); // ---------- (2)
console.log(a); // 1
}
outer(); // ------------- (3)
console.log(a); // 1
✔️ 자바스크립트를 실행하는 순간 (1) 전역 컨텍스트가 콜 스택에 담긴다.
✔️ (3)에서 outer 함수를 호출하면 자바스크립트 엔진은 outer 함수에 대한 환경 정보를 수집해서 outer 실행 컨텍스트를 생성한 후 콜 스택에 담는다.
✔️ 콜스택의 맨 위에 outer 실행 컨텍스트가 놓이므로 전역 컨텍스트와 관련된 코드의 실행은 일시중단하고 대신 outer 실행 컨텍스트와 관련된 코드, 즉 outer 함수 내부의 코드들을 순차적으로 실행한다.
✔️ 다시 (2) 에서 inner 함수의 실행 컨텍스트가 콜 스택의 가장 위에 담기면 outer 컨텍스트와 관련된 코드의 실행을 중단하고 inner 함수 내부의 코드를 순서대로 진행한다.
✔️ 이렇게 inner, outer, 전역 컨텍스트가 차례대로 실행되고 종료되면서 콜스택은 아무것도 남지 않은 상태로 종료된다.
사진 출처: Velog (사진에 오타가 조금 있습니다)
✔️ 현재 실행 컨텍스트 내의 식별자들에 대한 정보 (environmentRecord) + 외부 환경 정보 (outerEnvironmentReference)의 스냅샷(Snapshot)을 유지한다.
실행 컨텍스트를 생성할 때 Variable Environment 정보를 먼저 담은 다음, 이를 그대로 복사해서 Lexical Environment를 만들고, 이후에는 주로 Lexical Environment를 활용하게 된다.
풀어서 이야기하면, 내부 환경정보는 Environment Record에 저장하고 외부 환경 정보는 Outer Environment에 저장한다. 그리고 Lexical Environment는 Variable Environment(스냅샷)을 카피를 떠서 사용한다. 그러므로, 최초 실행시에는 사실상 완전히 동일하고 이후 코드 진행에 따라 서로 달라지게 된다.
✔️ 렉시컬 환경은 매개 변수명(Parameter), 변수의 식별자(Identifier), 선언한 함수의 함수명(Function)등을 수집하는 "environmentRecord"와 바로 직전 실행 컨텍스트의 LexicalEnvironment 정보를 참조하는 "outerEnvironment"로 구성되어 있다.
✔️ environmentRecord에는 현재 컨텍스트와 관련된 코드의 식별자 정보들이 저장된다.
✔️ 호이스팅은 자바스크립트 인터프레터가 매개변수, 선언된 변수의 식별자, 선언된 함수 자체가 유효 범위(Scope)의 최상단으로 끌어올려진 것을 의미한다.
✔️ 호이스팅이란 변수의 선언을 유효 범위(Scope)의 최상단으로 끌어올리는 행위를 말합니다. (같은 의미)
선언 방식 | Hoisted? | Initial Value | Scope |
---|---|---|---|
함수 선언문 | ✅ YES | 실제 함수 | Block (in Strict Mode) |
var | ✅ YES | undefined | Function |
let, const | ✅ YES and ❌ NO | Uninitizlied (TDZ) | Block |
함수 표현식 및 화살표 함수 | 🤷🏻♂️ Depends if using var or let/const | 🤷🏻♂️ Depends if using var or let/const | 🤷🏻♂️ Depends if using var or let/const |
functuon a(x) { // 수집 대상 1 (매개 변수)
console.log(x); // (1)
var x; // 수집 대상 2 (변수 선언)
console.log(x); // (2)
var x = 2; // 수집 대상3 (변수 선언)
console.log(x); // (3)
}
a(1);
보통 예상하는답
정답은
그럼 위의 예제를 다르게 한번 봐볼까요?
functuon a() {
var x; // 수집 대상 1의 변수 선언 부분
var x; // 수집 대상 2의 변수 선언 부분
var x; // 수집 대상 3의 변수 선언 부분
x = 1;
console.log(x); // (1)
console.log(x); // (2)
x = 2;
console.log(x); // (3)
}
a(1);
function a() {
console.log(b); // (1)
var b = "bbb"; // 수집 대상 1 (변수 선언)
console.log(b); // (2)
function b () { } // 수집 대상 2 (함수 선언)
console.log(b);
}
a();
function a() {
var b; // 수집 대상 1. 변수는 선언부만 끌어올린다.
function b() { } // 수집 대상 2. 함수 선언은 전체를 끌어올린다.
console.log(b); // (1)
b = "bbb"; // 변수의 할당부는 원래 자리에 남겨둔다.
console.log(b); // (2)
console.log(b); // (3)
}
a();
console.log의 결과
원본 코드만 볼때는 (1) 부분은 undefined가 나거나 오류가 날 것 같지만, 함수 선언은 함수 전체가 호이스팅이 되기 때문에 위와 같은 결과를 갖게된다.
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;
}
var sum = function sum (a, b) { // (1) // (3)
return a + b;
}
var mulitply; // (2)
console.log(sum(1, 2))); // (4)
console.log(multiply(3, 4));// (5)
multiply = function (a, b) {
return a * b;
}
위의 코드를 메모리 관점에서도 해석을 하자면
❓ 언제 함수 선언문(Function Declaration)과 함수 표현식(Function Expression)을 사용해야 하나요?
✔️ 스코프란 식별자에 접근할 수 있는 유효범위입니다.
예시를 보고 설명하는게 가장 이해하기 쉽다.
var a = 1; // (1)
var outer = function () { // (2)
var inner = function () { // (4)
console.log(a); // (6)
var a = 3; // (7)
};
inner(); // (5)
console.log(a); // (8)
}
outer(); // (3)
console.log(a); // (9)
undefeind
를 출력 하게된다.위의 내용을 정리하자면, 새로운 함수 실행문을 만나면 새로운 실행 컨텍스트가 된다. 이 함수(inner)의 environmentRecord에는 수집한 식별자 정보를 저장하고, outerEnvironmentReference에는 이 함수가 선언 되었을 당시 실행 컨텍스트(outer)의 LexicalEnvironment가 된다.
마치, 방 안에서 부터 변수 a를 찾는 것이라고 생각하면 된다. 안방(inner)에 변수 a가 있으면 이를 사용한다. 없다면 거실(outer)로 나가서 변수 a를 찾는다. 없다면 아예 집 밖(global)으로 나가서 변수 a를 찾는다.
✔️ 실행 컨텍스트의 thisBinding에는 this로 지정된 객체가 저장된다.
✔️ this는 함수를 호출하는 방식에 따라 달라진다.
1. Regular Function Call
- 일반 함수로 호출하는 경우 this는 window 객체를 가리킨다. Strict Mode인 경우에는 undefined를 리턴한다.
2. Dot Notation
- 메소드를 사용하는 Dot Notation인 경우, this는 Dot(.) 앞에 있는 것을 가리킨다.
3. Call, Apply, Bind
- 첫 번째 매개변수가 this가 무엇을 가리키는지 결정한다.
- 두 번째 인자부터는 함수의 인자로 사용한다.
- apply는 두 번째 인자를 배열로 받는다.
- bind는 함수를 즉시 실행하지 않고 binding된 새로운 함수를 반환한다. 즉, 변수에 저장할 수 있게된다.
4. "new" Keyword
- 함수 앞에 new 키워드를 사용하게 되면 this가 {}를 가리킨다. 즉, 빈 객체(새 인스턴스)를 생성하고 그 객체(인스턴스)를 가리킨다.