본 글은 JavaScript Internals: Execution Context 을 읽고 실행 컨텍스트를 드디어 이해해 감동받아 작성되었습니다. 출처도 저기
실행 컨텍스트
: 실행할 코드에 제공할 환경 정보를 모아놓은 객체
동일한 환경에 있는 코드들을 실행 시, 필요한 환경 정보를 모아 컨텍스트를 구성하고, 이를 콜 스택
에 쌓아 올렸다가, 가장 위에 쌓여있는 컨텍스트와 코드를 실행하는 방식으로 전체 코드의 환경과 순서를 보장합니다.
ECMAScript code는 네가지 타입으로 나뉩니다.
1. global code
: 자동으로 생성되는 전역공간
2. eval code
: 잘 안쓰임
3. function code
: 함수 실행
4. ES6에서는 블록
에 의해 새로운 실행 컨텍스트 생성
각각의 코드들은 실행 컨텍스트를 가지고 있습니다.
실행 컨텍스트는 코드가 어떤 환경에서 실행되는지 알려주는 개념입니다. 런타임에 자바스크립트 엔진에 의해 생성되고 사용되는 객체들의 모음이라고도 할 수 있습니다.
엔진이 스크립트 파일을 실행하기 전에 글로벌 실행 컨텍스트(Global Execution Context, GEC)
가 생성되고, 함수를 호출할 때마다 함수 실행 컨텍스트(Function Execution Context, FEC)
가 생성됩니다. 그렇기 때문에 글로벌 컨텍스트는 한 프로그램에 한 개만 존재하게 됩니다.
글로벌 생성 → 스크립트 파일 생성 → 함수 호출 → 함수 생성 → 함수실행 종료 → 함수제거 → 모든 코드 실행 종료 → 글로벌 제거
JS의 Execution stack은 LIFO 구조로 이뤄져 있습니다. 왜냐면 JS는 싱글 스레드이기 때문에 여러 일을 동시에 처리할 수 없기 때문이죠ㅠㅠ.
그래서 맨 위의 컨텍스트가 실행이 완료되어 사라진 뒤에야 그 다음의 컨텍스트가 실행될 수 있습니다.
실행 컨텍스트는 세가지 구조로 이뤄져있습니다.
1. this binding
2. lexical environment
3. variable environment
this는 자신을 불러온 객체를 가르킵니다.
실행 컨텍스트 활성화 당시, this가 지정되지 않은 경우(자신을 불러온 객체가 없는 경우) this에는 전역 객체가 저장됩니다.
하지만 arrow function을 사용하게 되면, 부모의 객체를 가르키게 됩니다.
function Coffee(){
this.menu = 'latte';
return {
menu : 'milk',
drink : function () {
console.log(this.menu) // milk
}
}
}
여기서는 this가
{
menu : 'milk',
drink : function () {
console.log(this.menu) // milk
}
}
여기서 호출하는 걸로 쓰입니다. but, arrow function을 사용하게 되면
function Coffee(){
this.menu = 'latte';
return {
menu : 'milk',
drink : () => {console.log(this.name)} // latte
}
}
여기선 부모 요소인 latte가 this 값이 됩니다.
글로벌 컨텍스트에서 this는 구동환경에 따라 달라집니다.
브라우저에서 전역객체는 window
객체를 의미합니다.
반면 Node.js(서버)상에서 전역객체는 global
객체를 의미합니다.
이들은 자바스크립트 내장 객체가 아닌 호스트 객체로 분류됩니다.
스택에 아무것도 없는 상태에서 호출되기 때문에 최상단 노드인 window가 글로벌 컨텍스트를 불러낸 객체가 됩니다.
next.js는 ssr 방식으로 동작하기 때문에 글로벌 객체가 global이 사용됩니다.
(window 사용 불가)
this = 함수를 호출한 객체
식별자들을 연관된 렉시컬 환경 범위 안의 값들 맵핑시켜주는 구조
let
과 const
으로 선언된 식별자들의 값을 보관하는 곳입니다.
부모 렉시컬 환경의 참조값을 갖고 있습니다.
= JS엔진이 최근 렉시컬 환경을 찾지 못하면 외부 환경의 내부에 있는 변수를 볼 수 있도록 해줍니다.
global context에서 outer reference == null
function context에서 outer reference == 부모의 lexical 환경 (글로벌이나 다른 함수 실행 컨텍스트)
함수안에 선언된 변수는 Environment Record에 저장될 뿐만 아니라, argument에 유사배열객체 안에도 저장됩니다.
var로 선언된 식별자들의 값을 저장하는데 사용됩니다.
Lexical Environment와 비슷하지만 최초 실행시의 스냅샷을 유지한다는 점이 다릅니다.
실행 컨텍스트 생성 시 Variable Environment에서 해당 정보를 먼저 담은 다음, 이를 그래로 복사해 Lexical Environment를 생성합니다. Variable Enviroment는 변하지 않은 채로, 복사한 Lexical Environment 값을 사용하게 됩니다.
실행 컨텍스트는 두 가지 단계를 거칩니다.
1. 생성단계
2. 실행단계
글로벌의 생성단계에선 window와 this가 생성됩니다.
Environment Record에서 변수가 선언될 때, default value로 undefined / uninitialized 를 갖게 됩니다. 변수가 var로 선언되면 undefined, 변수가 let이나 const로 선언되면 unintialized의 값을 가지게 되는데 그렇기 때문에 변수 선언 전에 변수가 사용되면 Reference Error가 뜨게 되는 겁니다.
그리고 모든 함수 선언은 전체적으로 메모리에 올리갑니다.
실행단계에 들어오면, JS엔진은 한줄 한줄 코드를 실행합니다.
코드를 실행하면서 이미 메모리에 올라와 선언된 변수들에 값을 할당해줍니다.
생성단계동안 default 값을 선언한 변수에 할당하고, 함수 선언을 메모리에 올리는 과정
변수 호이스팅시, 변수명만 끌어올리고 할당 과정은 원래 자리에 남겨둠
함수 선언문 : function 정의부만 존재하고 별도의 할당 명령이 없는 것
함수 표현식 : 정의한 function을 별도의 변수에 할당하는 것
일반적으로 함수 표현식은 익명 함수 표현식 (함수명을 정의하지 않음)
함수 선언문은 전체를 호이스팅, 함수 표현식은 변수 선언부만 호이스팅
협업시 같은 이름을 가진 함수를 작성하는 위험을 덜기 위해, 함수 표현식을 사용하는 것이 권장 됩니다.
스택에서 실행 컨텍스트가 사라진 뒤, 함수의 렉시컬 환경을 메모리에 올리는 방법
클로저는 내부 함수가 외부 함수의 렉시컬 환경에 접근할 수 있도록 해줍니다. 함수안에 함수가 있고 그걸 리턴해 다른 함수로 넘겨주는 기능을 갖고 있다고 생각하면 됨.
렉시컬 환경
을 칭하는 다른 단어로 식별자에 대한 유효범위
변수가 최근 실행 컨텍스트에서 무엇이 접근 가능한지 정의한 범위
함수와 블록에 의해서 스코프가 생성되는 데, 이를 함수 스코프
, 블록 스코프
라고 합니다.
블록스코프
블록스코프는 ES6에서 생긴 개념으로 var로 선언한 변수에서는 적용되지 않고, let, const, class, strict mode에서의 함수 선언 등에서만 적용
outerEnvironmentReference를 이용해 식별자의 유효범위를 안에서부터 바깥으로 차례대로 검색해나가는 것을 의미합니다.
outerEnvironmentReference는 현재 호출된 함수가 선언될 당시의 LexicalEnvironment를 참조하면서 연결리스트의 형태를 가짐.