02 실행 컨텍스트
01 실행 컨텍스트(execution context)
: 실행할 코드에 제공할 환경 정보들을 모아놓은 객체
Stack(스택)과 Queue(큐)
- Stack: Last In First Out, 비어있는 스택에 a, b, c, d 순으로 데이터를 저장하면 d, c, b, a 순으로 꺼낼 수 있는 형태의 데이터 구조
- Queue: First In First Out, 비어있는 스택에 a, b, c, d 순으로 데이터를 저장하면 a, b, c, d, 순으로 꺼낼 수 있는 형태의 데이터 구조
var a = 1
function outer() {
function inner() {
console.log(a)
var a = 3
}
inner()
console.log(a)
}
outer()
console.log(a)
- 코드 실행 순서
1. 처음 코드 실행 시 (1) 에서 전역 콘텍스트가 콜 스택에 담긴다. (처음 자바스크립트 파일이 열리는 순간 전역 콘텍스트가 활성화 된다고 생각하면 됨)
2. (3) 에서 outer 함수를 호출하면 outer의 환경 정보를 수집해 실행 컨텍스트를 생성한 후 콜 스택에 담음 -> 콜 스택의 맨 위에 outer 실행 컨텍스트가 놓였으므로, 전역 컨텍스트 실행을 일시중단하고 outer 함수 내부의 코드를 순차로 실행한다.
3. 다시 (2) 에서 실행 컨텍스트가 콜 스택의 가장 위에 담기면 outer 컨텍스트 실행을 일시 중단하고 inner 함수 내부의 코드를 순차로 실행한다.
- 활성화된 실행 컨텍스트의 수집 정보
- VariableEnvironment: 현재 컨텍스트 내의 식별자들에 대한 정보 + 외부 환경 정보. 선언 시점의 LexicalEnvironment의 snapshot(스냅샷)으로, 변경 사항은 반영되지 않음.
- LexicalEnvironment: 처음에는 VariableEnvironment와 같지만 변경 사항이 실시간으로 반영됨.
- ThisBinding: this 식별자가 바라봐야 할 대상 객체.
02 VariableEnvironment
- VariableEnvironment에 담기는 내용은 LexicalEnvironment와 같지만 최초 실행 시의 스냅샷을 유지하며 실행 컨텍스트를 생성할 때는 이를 복사해 LexicalEnvironment를 만들어서 활용함.
- VariableEnvironment와 LexicalEnvironment의 내부는 environmentRecord와 outer-EnvironmentReference로 구성돼 있다.
03 LexicalEnvironment(어휘적 환경, 정적 환경)
- "현재 컨텍스트의 내부에는 a, b, c와 같은 식별자들이 있고, 이는 D를 참조하도록 구성돼 있다" 라는 컨텍스트를 구성하는 환경 정보들을 사전에서 접하는 느낌으로 모아놓은 것 (정의 X)
environmentRecord와 hoisting(호이스팅)
- environmentRecord
- 현재 컨텍스트와 관련된 코드의 식별자 정보들이 저장됨. 함수에 지정된 매개변수 식별자, 선언한 함수가 있을 경우 그 함수 자체, var로 선언된 변수의 식별자 등이 식별자에 해당하며, 컨텍스트 내부 전체를 처음부터 끝까지 쭉 훑어나가며 순서대로 수집한다.
- hoisting(호이스팅)
- 변수 정보를 모두 수집했더라도, 코드들은 실행되기 전의 상태이다. 즉 코드가 실행되기 전에 해당 환경에 속한 코드의 변수명들을 모두 알고 있다.
- 이를 이해하기 쉽도록 표현한 것이 "자바스크립트 엔진은 식별자들을 최상단으로 끌어올려 놓은 다음 실제 코드를 실행한다"는 호이스팅인 것
hoisting 규칙
매개변수와 변수에 대한 hoisting
function a(x) {
console.log(x)
var x
console.log(x)
var x = 2
console.log(x)
}
a(1)
- hoisiting이 되지 않았다면 (1)에선 1, (2)에선 undefined, (3)에선 2가 출력될 것으로 생각된다. 하지만 실제 결과는 (1)에서 1, (2)는 1이 출력되고 (3)에서 2가 출력된다.
: hoisting에의해 함수가 실행되는 시점에 이미 var x라는 변수를 인지하고 있으므로 (1)과 (2) 모두 1이 출력되는 것이다.
함수 선언의 hoisting
function a() {
console.log(b)
var b = "bbb"
console.log(b)
function b() {}
console.log(b)
}
a()
- hoisting이 되지 않았다면 (1)에선 undefined, (2)는 "bbb", (3)은 b의 함수가 출력될 것으로 생각된다. 하지만 실제 결과는 (1)에선 b 함수, (2) "bbb", (3) "bbb"가 출력된다.
: hoisting에 의해 변수명과 함수 선언의 정보를 수집한다. 변수는 선언부와 할당부 중 선언부만 수집하는 반면 함수 선언은 함수 전체를 수집한다. 즉, hoisting이 끝난 상태에서의 함수 선언문은 함수명으로 선언한 변수에 함수를 할당한 것처럼 여겨질 수 있기 때문에 b함수가 가장 먼저 출력 되는 것이다.
function declaration(함수 선언식)과 function expression(함수 표현식)
: 둘 모두 함수를 새롭게 정의할 때 쓰이는 방식이지만, declaration은 function의 정의부만 존재하고 별도의 할당 명령이 없으며, 함수명이 반드시 정의돼 있어야 한다. expression은 별도의 변수에 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
}
: 함수 선언문은 함수 전체를 끌어 올리고, 표현식은 변수의 선언부만 끌어올리기 때문에, 선언문은 선언 전에 호출해도 아무런 문제가 생기지 않지만, 표현식은 선언 전에 호출하는 경우 아직 변수에 함수가 할당되어 있지 않기 때문에 "multiply is not a function"이라는 에러 메시지를 출력한다.
-> 상대적으로 함수 표현식이 안전하다
scope, scopeChain, outerEnvironmentReference
- scope: 식별자에 대한 유효범위, ES5까지의 자바스크립트는 전역을 제외하면 오직 함수에 의해서만 스코프가 생성된다.
- scopeChain: 식별자의 유효범위를 안에서부터 바깥으로 차례로 검색해나가는 것, 이를 가능케 하는 것이 LexicalEnvironment의 두 번째 수집 자료인 outerEnvironment이다.
scopeChain
- outerEnvironmentReference는 현재 호출된 함수가 선언될 당시의 LexicalEnvironment를 참조한다. 즉, outerEnvironmentReference는 linked list(연결 리스트)의 형태를 띤다.
- outerEnvironmentReference는 오직 자신이 선언된 시점의 LexicalEnvironment만을 참조하기 때문에 가장 가까운 요소부터 차례로만 접근할 수 있다.
-> 무조건 스코프 체인 상에서 가장 먼저 발견된 식별자에만 접근 가능하게 됨
- variable shadowing(변수 은닉화): inner 함수 내부에서 a에 접근하려고 할 때, 반드시 스코프 체인 상의 첫 번째 인자에 대해서만 접근할 수 있기 때문에 전역을 포함한 외부 공간에서 포함한 동일한 a 변수에 접근할 수 없는 것.
global variable(전역 변수)와 local variable(지역변수)
- global variable: global(전역)에서 선언한 변수
- local variable: 함수 내부에서 선언한 변수
04 this
: 실행 컨텍스트의 thisBinding에는 this로 지정된 객체가 저장된다. 컨텍스트 활성화 시점에 this가 지정되지 않은 경우 전역 객체가 저장됨.