Execution Context
(실행 컨텍스트)란?실행 컨텍스트는 실행할 코드에 제공할 환경 정보들을 모아놓은 객체이다.
JavaScript
는 동일한 환경에 있는 환경 정보들을 모은 실행 컨텍스트를 콜스택에 쌓아올린 후 실행하여 코드의 환경과 순서를 보장할 수 있게 된다.
즉, 스택(Stack
)의 경우, FILO(First In, Last Out)
의 구조이기에 순서를 보장하며, 콜스택 내부에 쌓인 실행 컨텍스트의 정보를 통해 환경을 보장할 수 있는 것이다.
위에서 말한 환경이란 전역 공간이 될 수 있고, 함수 내부의 환경이 될 수도 있다.
var temp = "temp";
function b() {
console.log("Hello, World");
}
function a() {
b();
}
a(); // Hello, World
위의 예시와 그림을 참고하면,
콜스택엔 전역 컨텍스트를 제외하고 다른 컨텍스트가 없기에 전역 컨텍스트와 관련된 코드를 진행한다.
전역 컨텍스트와 관련된 코드를 진행 중, a
함수를 실행했기 때문에 a
함수의 환경 정보들을 수집하여 a
실행 컨텍스트를 생성하고 콜스택에 담으며, 콜스택 최상단에 a
실행 컨텍스트가 있기 때문에 기존의 전역 컨텍스트와 관련된 코드의 실행을 일시적으로 중단한 후, a
실행 컨텍스트의 코드를 실행한다.
a
함수 내부에서 b
함수를 실행하였기 때문에 b
함수의 환경 정보들을 수집하여 실행 컨텍스트를 생성하고, 콜스택에 담으며, 이전과 똑같이 콜스택 최상단에 b
실행 컨텍스트가 있기 때문에 기존 a
실행 컨텍스트와 관련된 코드의 실행을 일시적으로 중단한다.
b
함수가 종료된 후, b
실행 컨텍스트가 콜스택에서 제거되고, 그 후 콜스택 최상단에는 a
실행 컨텍스트가 있기 때문에 이전에 중단된 지점부터 코드 진행이 재개된다.
a
함수 또한 종료된 후 실행 컨텍스트가 콜스택에서 제거되며, 이후에는 전역 공간에 실행할 코드가 남아있지 않다면 콜스택에서 전역 컨텍스트 또한 제거되며 콜스택에 아무것도 남지 않은 상태로 종료된다.
위의 단계로 JavaScript
코드가 진행된다.
실행 컨텍스트 내부에는
variable environment
,lexical environment
,this binding
이 있다.
VariableEnvironment
VariableEnvironment
란 현재 컨텍스트 내부의 식별자 정보 environmentRecord
, 외부 환경 정보 outerEnvironmentReference
가 포함되어 있다.
VariableEnvironment
에 먼저 정보를 담고, 그대로 LexicalEnvironment
에 복사해 사용한다고 한다.
LexicalEnvironment
LexicalEnvironment
는 초기에는 VariableEnvironment
와 같지만 변경 사항이 실시간으로 적용된다.
즉, VariableEnvironment
초기 상태를 기억하고 있으며, LexicalEnvironment
최신 상태를 저장하고 있다.
environmentRecord
environmentRecord
란 현재 컨텍스트와 관련된 식별자와 식별자에 바인딩된 값이 기록되는 공간이다.
더불어 실행 컨텍스트 내부 전체를 처음부터 끝까지 확인하며 순서대로 수집한다.
var hhj = "HyungJin Han";
console.log(hhj); // HyungJin Han
console.log(hhj); // undefined
var hhj = "HyungJin Han";
위의 예시를 보면, 첫 코드 예시의 경우 문제없이 작동되나, 두 번째 예시의 경우 선언하기도 전에 값을 호출했지만, Reference error
가 발생하지 않고, undefined
가 출력된다.
위의 현상은
JavaScript
의 호이스팅이라는 현상과 관련있다.호이스팅에 관련된 포스팅은 이전에 올렸던 포스팅을 보며 재차 공부할 예정이다.
LexicalEnvironment
의 environmentRecord
의 경우 해당 컨텍스트 환경에 필요한 식별자와 식별자의 값이 기록되며, 함수 실행 시 실행 컨텍스트가 생성되므로(함수 실행보다 environmentRecord
수집이 먼저 되므로) 변수와 같은 식별자를 끌어올리는 것과 같다는 개념의 호이스팅이 생겨났다.
outerEnvironmentReference
const greeding = "Hello, World";
const info = () => {
const hhj = {
age: 28,
name: "HyungJin Han",
};
const jhh = {
age: 23,
breed: "HyungHan Jin",
};
console.log(greeding);
console.log(hhj);
console.log(jhh);
};
info(); // Hello, World { age: 28, name: 'HyungJin Han' } { age: 23, breed: 'HyungHan Jin' }
console.log(hhj); // ReferenceError: hhj is not defined
console.log(jhh); // ReferenceError: jhh is not defined
위의 예시를 보면, 해당 코드의 함수 내부에서는 외부의 greeding
이 접근 가능하며, 내부에서 선언한 hhj
와 jhh
또한 접근이 가능하다.
하지만 외부에서는 내부에 선언된 hhj
와 jhh
에 접근할 수 없다.
그 이유는 outerEnvironmentReference
때문이다.
outerEnvironmentReference
란 현재 호출된 함수가 선언될 당시의 LexicalEnvironment
를 참조하게 된다.
여기서 선언될 당시를 집중해야 하는데, info
함수가 선언될 당시의 outerEnvironmentReference
는 글로벌 실행 컨텍스트의 LexicalEnvironment
를 참조하고 있으며, 해당 환경의 environmentRecord
에 greeding
과 같은 변수의 정보들이 기록되어 있게 된다.
그렇기에 함수 내부에서는 outerEnvironmentReference
를 통해 상위 컨텍스트의 LexicalEnvironment
에 접근하여 environmentRecord
에서 변수인 greeding
을 사용할 수 있게 되는 것이다.
또한 outerEnvironmentReference
는 오직 자신이 선언될 당시의 LexicalEnvironment
를 참조하기 때문에 순차적으로만 접근이 가능하며, 여러 스코프에서 동일한 식별자를 생성하였다고 하더라도 가장 먼저 발견된 식별자만 접근이 가능하다.
const a = "a";
const b = "b";
const sayHi = () => {
const a = "aa";
console.log(a); // aa
console.log(b); // b
};
sayHi();
위의 코드 예제를 보면, a
라는 변수는 현재 컨텍스트의 LexicalEnvironment
=> environmentRecord
에 a
라는 식별자가 있고, outerEnvironmentReference
=> LexicalEnvironment
=> environmentRecord
에도 a
식별자가 있지만, 가장 먼저 발견된(가까운) 식별자에 바인딩 된 값인 aa
를 출력한다.
결국 전역 컨텍스트에서 선언한 a
의 경우 변수 은닉화가 된다.
b
변수는 현재 컨텍스트의 LexicalEnvironment
=> environmentRecord
에 b
라는 식별자가 있으므로, outerEnvironmentReference
참조하여 전역 컨텍스트의 LexicalEnvironment
참조, environmentRecord
의 b
식별자에 접근하여 b
를 출력한다.
결론적으로
outerEnvironmentReference
란 해당 함수가 선언된 위치의LexicalEnvironment
를 참조하며, 변수에 접근을 한다면 해당LexicalEnvironment
에서 발견된다면 사용, 찾지 못할 경우 다시outerEnvironmentReference
를 참조하여 탐색하는 과정을 반복한다.이러한 과정을 스코프 체인 이라고 하며
outerEnvironmentReference
는 스코프 체인을 가능하게 하는 역할이다.
ThisBinding
우선 this
는 컨텍스트를 가르킨다.
method
에서 사용시 해당 method
가 담겨있는 instance or object
를 가르키며, 함수표현식에서 사용시 this
를 바인딩하지 않는 이상 전역 객체를 가르킨다.
더불어 전역 공간에서 this
는 함수 표현식과 같이 전역 객체를 가르킨다.
ES6
의 화살표 함수를 사용하면 this
바인딩, 우회법으로 변수에 this
를 담아 실행할 필요 없이 함수를 실행한 컨텍스트를 바라본다.
const obj = {
outer: function () {
console.log(this); // { outer: [Function: outer] }
// 메소드에서 사용되었기에 obj 출력
function inner() {
console.log(this); // Object [global] {...}
// 함수 표현식에서 사용되었기에 전역 객체 출력
}
inner();
const self = this;
function inner2() {
console.log(self); // { outer: [Function: outer] }
// 우회하기 위해 this를 self에 할당, 출력했기에 obj 출력
}
inner2();
const inner3 = () => {
console.log(this); // { outer: [Function: outer] }
// 화살표함수 사용으로 this는 상위 스코프의 컨텍스트를 가르킴, obj 출력
};
inner3();
},
};
obj.outer();
요약하자면,
this
는 실행 컨텍스트 생성 시, 즉, 함수를 실행할 때 할당해주는 것으로 상황에 따라 전역 객체 또는instance
를 카르킨다.
위에서 설명했듯이, 실행 컨텍스트는
VariableEnvironment
,LexicalEnvironment
,environmentRecord
,outerEnvironmentReference
,hisBinding
등의 여러 정보들이 합쳐져 실행 컨텍스트가 되고, 이것이 콜스택에 쌓여JavaScript
의 코드가 실행된다.
참고 사이트
감구마- JS Execution Context (실행 컨텍스트) 란?
천천히 꾸준하게 - [javascript 자바스크립트] 실행 컨텍스트(Execution Context) 란?