일 | 월 | 화 | 수 | 목 | 금 | 토 |
---|---|---|---|---|---|---|
1 | 2 | 3 | 4 | |||
5 | 6 | 7 | 8 | 9 | 10 | 11 |
12 | 13 | 14 | 15 | 16 | 17 | 18 |
19 | 20 | 21 | 22 | 23 | 24 | 25 |
26 | 27 | 28 | 29 | 30 | 31 |
Tags
- 백준
- Python
- 이벤트버블링
- v-on
- vue
- 3003번
- 쿠버네티스
- DevOps
- MSA
- 실행 컨텍스트
- hoisting
- LeetCode
- 배열파티션
- 리스트복사
- 도커
- 객체지향의 사실과 오해
- 젠킨스
- 2588번
- 코어자바스크립트
- 10869번
- 이벤트캡쳐링
- 파이썬
- 우테코
- 프리코스
- JavaScript
- v-for
- 빅오표기법
- v-model
- 10926번
- v-if
Archives
- Today
- Total
새오의 개발 기록
코어 자바스크립트: 2. 실행 컨텍스트 본문
02. 실행 컨텍스트
1. 실행 컨텍스트란?
실행할 코드에 제공할 환경 정보들을 모아놓은 객체
- 동일한 환경에 있는 코드들을 실행할 때 필요한 환경 정보들을 모아 컨텍스트를 구성하고, 이를 콜 스택에 쌓아올렸다가, 가장 위에 쌓여있는 컨텍스트와 관련 있는 코드들을 실행하는 식으로 전체 코드의 환경과 순서를 보장함.
- 동일한 환경: 하나의 실행 컨텍스트를 구성할 수 있는 방법으로 전역공간, eval()함수, 함수 등이 있음
- 자동 생성되는 전역공간과 eval을 제외하면 실행 컨텍스트를 구성하는 방법은 함수 실행 뿐임
실행 컨텍스트와 콜 스택
// ---------------------------(1)
var a = 1;
function outer() {
function inner() {
console.log(a); // undefined
var a = 3;
}
inner(); // ---------------------------(2)
console.log(a);
}
outer(); // ---------------------------(3)
console.log(a);
- 처음 자바스크립트 코드를 실행
- (1) 전역 컨텍스트가 콜 스택에 담김
- 콜 스택에 전역 컨텍스트 외에 다른 덩어리가 없으므로 전역 컨텍스트와 관련된 코드들을 순차로 진행
- (3) 에서 outer 함수를 호출
- outer 실행 컨텍스트를 생성한 후 콜 스택에 담음
- 전역 컨텍스트와 관련된 코드의 실행을 일시중단하고 outer 실행 컨텍스트와 관련된 코드들을 순차로 실행
- (2)에서 inner 함수를 호출
- outer 컨텍스트와 관련된 코드의 실행을 일시중단하고 inner 실행 컨텍스트와 관련된 코드들을 순차로 실행
- inner 함수 내부에서 a 변수에 값 3을 할당하고 나면 inner 함수의 실행이 종료되면서 inner 실행 컨텍스트가 콜 스택에서 제거됨
- 아래에 있던 outer 컨텍스트가 콜 스택의 맨 위에 존재하게 되므로 중단했던 (2)의 다음 줄부터 이어서 실행
- a 변수의 값을 출력하고 나면 outer 함수의 실행이 종료되어 outer 실행 컨텍스트가 콜 스택에서 제거됨
- (3)의 다음 줄부터 이어서 실행
- a의 변수의 값을 출력하고 나면 전역 공간에 더는 실행할 코드가 남아 있지 않아 전역 컨텍스트도 제거됨
- 콜 스택에는 아무것도 남지 않은 상태로 종료
이처럼 어떤 실행 컨텍스트가 활성화될 때 자바스크립트 엔진은 해당 컨텍스트에 관련된 코드들을 실행하는 데 필요한 환경 정보들을 수집해서 실행 컨텍스트 객체에 저장되며 이 객체에 담기는 정보는 아래와 같음
- VariableEnvironment: 현재 컨텍스트 내의 식별자들에 대한 정보 + 외부 환경 정보. 선언 시점의 LexicalEnvironment의 스냅샷으로, 변경사항은 반영되지 않음
- LexicalEnvironment: 처음에는 VariableEnvironment와 같지만 변경 사항이 실시간으로 반영됨
- ThisBinding: this 식별자가 바라봐야 할 대상 객체
- VariableEnvironment와 LexicalEnvironment의 내부는 environmentRecord와 outerEnvironmentReference로 구성됨
2. VariableEnvironment
- VariableEnvironment에 담기는 내용은 LexicalEnvironment와 같지만 최초 실행 시의 스냅샷을 유지한다는 점이 다름
- 실행 컨텍스트를 생성할 때 VariableEnvironment에 정보를 먼저 담은 다음, 이를 복사해서 LexicalEnvironment를 만들고, 이후에는 LexicalEnvironment를 주로 활용
3. LexicalEnvironment
environmentRecord와 호이스팅
- environmentRecord에는 현재 컨텍스트와 관련된 코드의 식별자 정보들이 저장됨
- 식별자 정보: 컨텍스트를 구성하는 함수에 지정된 매개변수 식별자, 선언한 함수가 있을 경우 그 함수 자체, var로 선언된 변수의 식별자 등
- 컨택스트 내부 전체를 처음부터 끝까지 쭉 훑어나가며 순서대로 수집함
- 변수 정보를 수집하는 과정을 마치고 나면 코드가 실행되기 전임에도 자바스크립트 엔진이 이미 해당 환경에 속한 코드의 변수명들을 모두 알고 있음 -> 자바스크립트 엔진은 식별자들을 최상단으로 끌어 올려놓은 다음 실제 코드를 실행함과 같음 -> 호이스팅
호이스팅 규칙
매개 변수와 변수 선언에 대한 호이스팅
// 호이스팅 전
function a (x) { // 수집 대상 1(매개변수)
console.log(x);
var x; // 수집 대상 2(변수 선언)
console.log(x);
var x = 2; 수집대상 3(변수 선언)
console.log(x);
}
a(1);
- environmentRecord는 현재 실행될 컨텍스트의 대상 코드 내에서 변수명만 끌어올리고 할당 과정은 원래 자리에 그대로 둠
- 매개변수도 마찬가지로 동작함
// 호이스팅을 마친 상태
function a () {
var x; // 수집 대상 1의 변수 선언 부분
var x; // 수집 대상 2의 변수 선언 부분
var x; // 수집 대상 3의 변수 선언 부분
x = 1; // 수집 대상 1의 할당 부분
console.log(x); // (1)
console.log(x); // (2)
x = 2; // 수집 대상 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) // (3)
}
a();
// 호이스팅을 마친 상태
function a () {
var b; // 수집 대상 1. 변수는 선언부만 끌어올린다.
function b () { } // 수집 대상 2. 함수 선언은 전체를 끌어올린다.
console.log(b); // (1)
b = 'bbb'; // 변수의 할당부는 원래 자리에 남겨둠
console.log(b); // (2)
console.log(b); // (3)
}
a();
- a 함수를 실행하는 순간 a 함수의 실행 컨텍스트가 생성됨
- 이때 변수명과 함수 선언의 정보를 위로 끌어올림
- 변수는 선언부와 할당부를 나누어 선언부만 끌어올리는 반면 함수 선언은 함수 전체를 끌어올림
// 함수 선언문을 함수 표현식으로 바꾼 코드
function a () {
var b;
var b = function b () { }
console.log(b); // (1)
b = 'bbb';
console.log(b); // (2)
console.log(b); // (3)
}
a();
- 호이스팅이 끝난 상태에서의 함수 선언문은 함수명으로 선언한 변수에 함수를 할당한 것처럼 여길 수 있음
- (1) b함수 (2) 'bbb' (3) 'bbb' 출력됨
함수 선언문과 함수 표현식
function a () { /*...*/ } // 함수 선언문. 함수명 a가 곧 변수명.
a(); // 실행 OK.
var b = function () { /*...*/ } // (익명) 함수 표현식. 변수명 b가 곧 함수명.
b(); // 실행 OK.
var c = function d () { /*...*/ } // 기명 함수 표현식. 변수명은 c, 함수명은 d.
c(); // 실행 OK.
d(); // 에러
- 함수 선언문: function 정의부만 존재하고 별도의 할당 명령이 없는 것
- 함수명이 반드시 정의되어 있어야 함 -> 기명 함수 표현식
- 기명 함수 표현식은 외부에서는 함수명으로 함수를 호출할 수 없으며 함수 내부에서만 접근 가능함(ex. function d)
- 함수 표현식: 정의한 function을 별도의 변수에 할당하는 것을 말함
- 함수명을 반드시 정의할 필요 없음 -> 주로 익명 함수 표현식 의미
함수 선언문의 호이스팅
console.log(sum(1, 2));
console.log(multiply(3, 4);
function sum (A, b) { // 함수 선언문 sum
return a + b;
}
var multiply = function (A, b) { // 함수 표현식 multiply
return a * b;
}
// 호이스팅 마친 상태
function sum (A, b) { // 함수 선언문은 전체를 호이스팅
return a + b;
};
var multiply; // 변수는 선언부만 끌어올림
console.log(sum(1, 2));
console.log(multiply(3, 4);
multiply = function (A, b) { // 변수의 할당부는 원래 자리에 남겨둠
return a * b;
}
함수 선언문의 위험성
- 전역 컨텍스트가 활성화될 때 전역공간에 선언된 함수들이 모두 가장 위로 끌어올려짐.
- 동일한 변수명에 서로 다른 값을 할당할 경우 나중에 할당한 값이 먼저 할당한 값을 덮어씌움(override)
- 의도한 값과 다른 값이 할당될 수 있음 -> 에러 발생되지 않음
- 함수표현식으로 정의하는 경우 함수를 선언한 줄보다 이전 줄에 함수를 호출하면 에러가 발생해서 디버깅 하기 쉬움
스코프, 스코프 체인, outerEnvironmentReference
- 스코프: 식별자에 대한 유효범위
- 스코프 체인: 식별자의 유효범위를 안에서부터 바깥으로 차례로 검색해나가는 것
- 이를 가능케 하는 것이 LexicalEnvironment의 두 번째 수집 자료인 outerEnvironmentReference
스코프 체인
- outerEnvironmentReference는 현재 호출된 함수가 선언될 당시의 LexicalEnvironment를 참조함
- 선언이 일어날 수 있는 시점이란 콜 스택 상에서 어떤 실행 컨텍스트가 활성화된 상태일 뿐
- ex) A 함수 내부에 B 함수를 선언하고 다시 B 함수 내부에 C 함수를 선언한 경우, 함수 C의 outerEnvironmentReference는 함수 B의 LexicalEnvironment를 참조함.
- 함수 B의 LexicalEnvironment에 있는 outerEnvironmentReference는 다시 함수 B가 선언되던 때(A)의LexicalEnvironment를 참조
- 이처럼 outerEnvironmentReference는 연결리스트 형태임
- 선언 시점의 LexicalEnvironment만 참조하고 있으므로 가장 가까운 요소부터 차례대로만 접근 가능
- 즉 여러 스코프에서 동일한 식별자를 선언한 경우에는 무조건 스코프 체인 상에서 가장 먼저 발견된 식별자에만 접근 가능함
var a = 1;
var outer = function () {
var inner = function () {
console.log(a);
var a = 3;
};
inner();
console.log(a);
};
outer();
console.log(a);
- 시작
- 전역 컨텍스트 활성화
- 전역 컨텍스트의 environmentRecord에 { a, outer } 식별자를 저장
- 전역 컨텍스트는 선언 시점이 없으므로 전역 컨텍스트의 outerEnverinmontReference에는 아무것도 담기지 않음
- 1번째 줄과 2번째 줄
- 전역 스코프에 있는 변수 a에 1을, outer에 함수를 할당
- 10번째 줄
- outer 함수 호출
- 전역 컨텍스트의코드는 10번째 줄에서 임시중단되고, outer 실행 컨텍스트가 활성화되어 2번째 줄로 이동
- 2번째 줄
- outer 실행 컨텍스트의 environmentRecord에 { inner } 식별자 저장
- outer 함수는 전역 공간에서 선언됐으므로 전역 컨텍스트의 LexicalEnvironment를 참조복사함
- 이를 [ GLOBAL, { a, outer } ] 라고 표기.
- 첫 번째는 실행 컨텍스트의 이름, 두 번째는 environmentRecord 객체(this: 전역 객체)
- 3번째 줄
- outer 스코프에 있는 변수 inner에 함수를 할당
- 7번째 줄
- inner 함수를 호출
- outer 실행 컨텍스트의 코드는 7번째 줄에서 임시 중단
- inner 실행 컨텍스트가 활성화되어 3번째 줄로 이동
- 3번째 줄
- inner 실행 컨텍스트의 environmentRecord에 { a } 식별자를 저장
- inner 함수는 outer 함수 내부에서 선언됐으므로 outer 함수의 LexicalEnvironment, [ outer, { inner} ]를 참조 복사
- 4번째 줄
- 식별자 a에 접근
- 현재 활성화 상태인 inner 컨텍스트의 environmentRecord에서 a를 검색 -> undefined
- 5번째 줄
- inner 스코프에 있는 변수 a에 3을 할당
- 6번째 줄
- inner 함수 실행이 종료됨
- inner 실행 컨텍스트가 콜 스택에서 제거되고, 바로 아래의 outer 실행 컨텍스트가 다시 활성화되면서, 앞서 중단했던 7번째 줄의 다음으로 이동함
- 8번째 줄
- 식별자 a에 접근하고자 함
- 첫 요소의 e.r에서 a가 있는지 찾아보고, 없으면 outerEnvironmentReference에 있는 e.r로 넘어가는 식으로 계속 검색
- 전역 LexicalEnvironment에 a가 있으니 a에 저장된 값 1을 반환
- 9번째 줄
- outer 함수 실행이 종료됨
- outer 실행 컨텍스트가 콜 스택에서 제거되고, 바로 아래의 전역 컨텍스트가 다시 활성화되면서, 앞서 중단했던 10번째 줄의 다음으로 이동함
- 11번째 줄
- 식별자 a에 접근하고자 함
- 현재 활성화 상태인 전역 컨텍스트의 environmentRecord에서 a를 검색함 -> 1 출력
- 모든 코드 실행 완료. 전역 컨텍스트가 콜 스택에서 제거되고 종료됨
- 변수 은닉화
- 스코프 체인 상에 있는 변수라고 해서 무조건 접근 가능한 게 아님
- inner 함수 내부에서 a에 접근하려고 하면 무조건 스코프 체인 상의 첫 번째 인자, 즉 inner 스코프의 LexicalEnvironment부터 검색할 수밖에 없음
- inner 스코프에 a가 있으면 전역 공간의 a 변수에는 접근할 수 없음
전역변수와 지역변수
- 전역 변수: 전역 공간에서 선언한 변수
- 지역 변수: 함수 내부에서 선언한 변수
- 가급적 전역변수 사용을 최소화하고자 노력하는 것이 좋음
4. this
- 실행 컨텍스트의 thisBinding에는 this로 지정된 객체가 저장됨
- 실행 컨텍스트 활성화 당시에 this가 지정되지 않은 경우 this에는 전역 객체가 저장됨
- 그 밖에는 함수를 호출하는 방법에 따라 달라짐
'Javascript > 코어 자바스크립트' 카테고리의 다른 글
코어 자바스크립트: 3. this (0) | 2023.02.07 |
---|---|
코어 자바스크립트: 1. 데이터 타입 (0) | 2022.12.21 |