📌 참고 사이트
[JS] Javascript의 실행 컨텍스트 (Execution Context) (tistory.com)
[JS] Javascript의 실행 컨텍스트 (Execution Context)
Javascript 실행컨택스트 (Execution Context) 코드가 평가되고 실행되는 환경의 추상적인 개념입니다. 즉 실행 컨텍스트는 실행 가능한 코드가 실행되기 위해 필요한 환경이라고 할 수 있습니다. 지금
oyg0420.tistory.com
원문은 이거라고 한다.
Understanding Execution Context and Execution Stack in Javascript
Understanding execution context and stack to become a better Javascript developer.
blog.bitsrc.io
📋 실행 컨텍스트 (Execution Context)
코드가 실행되는 환경을 의미하는 추상적인 개념이다.
📍실행 컨텍스트의 종류
➡️ 전역 실행 컨텍스트 (global excution context)
JS 코드를 실행하는 순간 전역 실행 컨텍스트가 생성되게 된다. 이는 애플리케이션이 종료될 때까지 유지된다.
➡️ 함수 실행 컨텍스트 (functional excution context)
함수가 호출될 때마다 그 호출된 함수에 대한 실행 컨텍스트가 새롭게 생성된다.
함수가 선언이 될 때가 아닌 실행이 될 때 함수 실행 컨텍스트가 만들어진다는 점에 유의해야 한다.
📍 실행 스택 (execution stack)
실행 스택은 코드가 실행되는 동안 생성된 모든 실행 컨텍스트를 LIFO의 운용 방식으로 저장하는 용도로 사용된다.
실행 스택의 원리를 좀 더 자세히 살펴보면 다음과 같다.
1️⃣ JS 엔진이 스크립트를 발견하면 전역 실행 컨텍스트를 생성하고 이것을 실행 스택에 push한다.
2️⃣ 함수가 호출이 되면 새로운 함수 실행 컨텍스트를 생성하고 그것을 실행 스택에 push한다. 먼저 호출이 된 것일수록 실행 스택의 아래에 위치한다.
3️⃣ 함수가 종료되면 실행 스택에서 pop되는데, 가장 마지막으로 실행된(실행 스택에 올라간) 함수부터 pop된다. 한 함수가 종료되어 실행 스택에서 사라지면 현재 stack에서의 제어권은 그 이전으로 실행된 함수에게로 넘어가게 된다.
📋 ES5.1 이후에서의 실행 컨텍스트
📍 생성 단계
실행 컨텍스트의 생성 단계에서는 다음과 같은 것들이 생성된다.
➡️ 렉시컬 환경 (Lexical Environment, 어휘 환경 컴포넌트)
자바스크립트 엔진이 자바스크립트 코드를 실행하기 위해 자원을 모아둔 곳이다. (이론상의 객체이므로 직접 눈으로 렉시컬 환경을 확인하는 것은 불가능)
함수 또는 블록의 유효 범위 안에 있는 식별자와 그 값이 쌍으로 바인딩되어 저장되는 곳이다.
* 식별자 : 변수, 상수, 함수, 구조체 등에 부여된 이름들
렉시컬 환경 ex)
let name = 'kanu';
let age = 6;
function bird() {
console.log('I can fly!');
}
위와 같은 경우의 렉시컬 환경을 객체로 표현하면 다음과 같다.
lexicalEnvironment = {
name: 'kanu',
age: 6,
bird: <Function>
};
렉시컬 환경에서는 다음과 같은 3가지 컴포넌트가 존재한다.
1️⃣ 환경 레코드 (Environment Record)
렉시컬 환경에서 변수와 함수 선언이 저장되는 장소이다.
2️⃣ 외부 환경 참조 (Reference to the outer Environment)
현재 실행 컨텍스트 외부의 렉시컬 환경에 접근한다는 의미이다.
JS 엔진은 현재 실행 컨텍스트의 변수를 찾을 때 렉시컬 환경에 찾고자 하는 변수가 없다면 외부 환경까지 접근하여 변수를 찾는다.
3️⃣ This 바인딩 (Binding)
this가 가리키는 값이 정해지는 것을 말한다.
전역 실행 컨텍스트의 this가 참조하는 값은 전역 객체가 되고, 함수 실행 컨텍스트의 this가 참조하는 값은 함수의 호출에 따라 달라진다.
➡️ 변수 환경 컴포넌트 (VariableEnvironment Component)
렉시컬 환경에는 함수 선언과 let, const 키워드의 변수가 저장되고, 변수 환경 컴포넌트에는 var 키워드의 변수가 저장된다는 차이점이 있다.
❗ var, let, const 짚고 넘어가기
➡️ var : 함수 레벨 스코프 (함수 안에서 var로 선언된 변수(지역변수)는 함수 안에서만 유효하고, 함수 밖에서는 참조가 불가, 함수 밖에서 var로 선언한 변수는 전역 변수가 됨.), 재선언과 재할당 가능, 호이스팅 (var로 선언된 변수는 생성 단계에서 undefined라는 값이 할당되기 때문에 선언 전에 참조될 수 있다. 이것은 선언 부분이 위로 끌어올려진 것처럼 동작하기 때문)
➡️ let : 블록 레벨 스코프 (코드 블록, 즉 중괄호 { } 안에 let으로 선언된 변수(지역변수)는 코드 블록 안에서만 유효하여 코드 블록 밖에서 참조할 수 없다. 코드 블록 밖에서 let으로 선언된 변수는 전역 변수), 재선언 불가 but 재할당 가능
➡️ const : 블록 레벨 스코프, 재할당과 재선언 모두 불가, let과 var과는 다르게 선언과 동시에 값이 할당되어야 한다.
* 사실 let과 const에서도 호이스팅은 일어난다. 예를 들어 하나의 스코프 안에서 변수 a를 출력하는 함수를 호출하는 코드가 먼저 나오고, 그 다음에 let a = 1;과 같이 변수가 선언되어져 있다고 할 때, let a; 부분은 끌어올려지게 된다. 즉 생성 단계에서 a 변수의 존재는 알려지게 된다. but var로 선언한 변수에 생성 단계에서 undefined가 할당되는 것과 다르게, let과 const로 선언한 변수는 생성 단계에서 값이 할당되지 않으므로 Referrence Error를 만나게 될 수 있다.
📍 실행 단계
실행 단계에서는 모든 변수에 대해 값이 할당되고, 완료되고, 코드가 최종적으로 실행한다.
실행 단계는 위의 실행 스택의 과정을 그대로 따른다.
예시를 하나 들어 보겠다.
let name = 'kanu';
let age = 31;
var species = 'parrot';
function fly(name) {
var canFly = true;
console.log(`${name} can fly: ${canFly}`);
}
var bird = fly(name);
1️⃣ 위의 코드에 JS 엔진이 진입하면 JS 엔진은 전역의 코드를 실행하기 위해 전역 실행 컨텍스트를 생성한다.
다음과 같은 객체 형식으로 표현이 가능하다.
* 함수 선언식 방식(function 키워드 사용)으로 함수를 정의한 경우 생성 단계에서 함수의 내용이 같이 저장되게 된다. (fly: <func> 참고!)
// 전역 실행 컨텍스트 생성 단계
전역 실행 컨텍스트 = {
// 렉시컬 환경 (let, const로 선언된 변수와 함수 등에 관한 정보가 담겨 있다.)
LexicalEnvironment: {
// 환경 레코드 (변수와 함수 선언이 저장되는 곳)
EnvironmentRecord: {
name: <uninitialized>, // 아직 값이 할당되지 않은 단계
age: <uninitialized>, // 아직 값이 할당되지 않은 단계
fly: <func>,
},
outter: <null>, // 전역 실행 컨텍스트이므로 외부 실행 컨텍스트가 존재 X
ThisBinding: <Global Object> // this가 전역 객체임을 나타냄
},
// 변수 환경 (var로 선언된 변수 등에 관한 정보가 담겨 있다.)
VariableEnvironment: {
EnvironmentRecord: {
species: undefined, // var로 선언되어 있으므로 일단 undefined 할당
bird: undefined, // var로 선언되어 있으므로 일단 undefined 할당
},
outter: <null>, // 외부 실행 컨텍스트 존재 X
ThisBinding: <Global Object>, // this는 전역 객체
}
}
2️⃣ 실행 단계에서는 변수의 할당이 완료되고 전역 실행 컨텍스트는 아래와 같이 값이 채워진 상태로 변화한다.
// 전역 실행 컨텍스트 실행 단계
전역 실행 컨텍스트 = {
LexicalEnvironment: {
EnvironmentRecord: {
name: 'kanu',
age: 6,
fly: <func>,
},
outer: <null>,
ThisBinding: <Global Object>
},
VariableEnvironment: {
EnvironmentRecord: {
species: 'parrot',
bird: undefined, // bird에 할당된 함수가 종료된 것이 아니므로 return되는 값이 없어 undefined이다.
},
outer: <null>,
ThisBinding: <Global Object>,
}
}
3️⃣ 함수 introduce가 실행되면 새로운 함수 실행 컨텍스트가 생성된다. 다음은 생성 단계를 나타낸다.
// 함수 실행 컨텍스트 생성 단계
함수 실행 컨텍스트 = {
// 렉시컬 환경
LexicalEnvironment: {
EnvironmentRecord: {
arguments: {0: 'kanu', length: 1}, // 인수도 렉시컬 환경에 들어감.
},
outer: <GlobalEnvironment>, // fly 함수의 외부 실행 컨텍스트는 전역 실행 컨텍스트
ThisBinding: <Global Object>
},
// 변수 환경
VariableEnvironment: {
EnvironmentRecord: {
canFly: undefined, // 생성 단계이므로 var로 선언한 변수의 값은 undefined로 저장
},
outer: <GlobalEnvironment>,
ThisBinding: <Global Object>,
}
}
4️⃣ 실행 단계에서는 함수에 있는 변수의 값에 할당이 이루어진다.
// 함수 실행 컨텍스트 실행 단계
함수 실행 컨텍스트 = {
LexicalEnvironment: {
EnvironmentRecord: {
arguments: {0: 'kanu', length: 1},
},
outer: <GlobalEnvironment>,
ThisBinding: <Global Object>
},
VariableEnvironment: {
EnvironmentRecord: {
canFly: true, // 이제 값이 할당됨
},
outer: <GlobalEnvironment>,
ThisBinding: <Global Object>,
}
}
5️⃣ fly 함수가 실행을 마치면 return된 값이 bird 변수에 할당된다. 사실 console.log문밖에 없기 때문에 반환되는 값은 undefined이다. 'kanu can fly: true' 만 콘솔창에 출력될 뿐이다.
💡 컨텍스트 생성 단계에서 변수가 초기화될 때 let과 const는 uninitialized로 값이 없지만, var의 경우에는 초기화와 동시에 undefined 값이 할당된다. 즉, 선언이 된 것으로 작동한다. 변수 호이스팅의 원리이다.
❓ 스코프 체인 (Scope Chain)
스코프 체인이란, 일종의 리스트로써 실행 컨텍스트에 존재하는 식별자를 찾기 위한 객체 리스트이다.
자신의 상위 변수 객체 리스트라고도 할 수 있다.
함수 실행 중 변수를 만나면 현재 실행 컨텍스트의 스코프 체인의 0번째 Activation Object에서 찾아보고, 없다면 스코프 체인에 담긴 순서대로 검색해 나가게 된다.
ES5.1 이후에서는 렉시컬 환경의 요소인 outter environment에 의해서 스코프 체인이 이루어진다.
다음 예시를 통해 살펴보자.
let v1 = 'global variable';
function outter() {
let v1 = 'outter variable';
(function inner() {
console.log(v1);
})()
}
outter();
그림을 통해 실행 컨텍스트의 실행 단계를 분석해 보겠다. (각 컨텍스트에서는 렉시컬 환경을 간결하게 표현해 보았다.)
이렇게 되어 결과적으로 콘솔에는 outter variable 이 찍히게 되고, inner 함수부터 차례로 종료되어 실행 스택에서 pop되게 된다.
❗ Scope는 함수가 선언된 위치에 따라서 정해지는 것임! 실행 위치와는 무관!
다음과 같이 inner 함수를 outter 함수 안이 아닌 전역에 선언하여 outter 함수 안에서 inner 함수를 실행시킨다면 위와 결과가 달라진다.
let v1 = 'global variable';
function outter() {
let v1 = 'outter variable';
inner();
}
function inner() {
console.log(v1);
}
outter(); // global variable이 출력됨.
inner 함수 안에는 v1 변수가 없으므로 스코프 체인을 따라 올라가서 변수를 찾게 되는데, 이때 전역 스코프로 올라가게 된다. 애초에 inner 함수의 Local 스코프와 outter 함수의 Local 스코프는 관계가 없다.
이전 예시에서 inner 함수를 outter 함수 안에 정의하여 outter 함수 스코프 안에 inner 함수가 위치하여 inner 함수가 outter 함수 안에 정의되어 있는 v1 변수를 참조한 것과는 다르다.
따라서 전역 변수 v1의 값이 출력되게 된다.
'Javascript study' 카테고리의 다른 글
[JS] Closure (0) | 2023.08.27 |
---|---|
[JS] 실행 컨텍스트 (2) (0) | 2023.08.25 |
[JS] 렉시컬 환경, this 정리 (2) | 2023.08.23 |
[JS] FileReader (0) | 2023.08.21 |
[JS] innerText, innerHTML, textContent (0) | 2023.08.19 |
댓글