실행 컨텍스트란?
먼저 스택, 큐에 대해서 학습
스택 : FIFO
큐 : LIFO
실행 컨텍스트
실행할 코드에 제공할 환경 정보를 모아놓은 객체
동일한 환경
자동으로 생성되는 전역공간과
악마로 취급받는 eval? 을 제외하면
실행 컨텍스트 구성 → 함수
// -------------------------- (1)
var a = 1;
function outer() {
function inner() {
console.log(a); //undefined
var a = 3;
}
inner(); // --------------- (2)
console.log(a); // 1
}
outer(); // ----------------- (3)
console.log(a); // 1
처음
JS 코드를 실행하면 (1) 전역 컨텍스트가 콜 스택에 담김
일반적인 실행 컨텍스트와 특별히 다를 것이 없음
JS 파일 열리는 순간 → 전역 컨텍스트 활성화
코드를 순차적으로 진행 하다가 (2) 구문을 만나게 되면
콜 스택의 맨 위
에 outer 실행 컨텍스트가 놓인 상태
outer 실행 중 inner() 실행 구문
을 만나게 되면
inner 함수 내부에서 a 변수의 값을 추출하고 나면
그 이후 순차적으로 스택에 남겨져 있던 모든 컨텍스트 제거
스택 구조를 잘 생각해보면
한 실행 컨텍스트 →맨 위에 쌓이게 되면
현재 실행할 코드에 관여하게 되는 시점
실행 컨텍스트 객체에 저장되는 정보
VariableEnvironment
식별자들에 대한 정보 + 외부 환경 정보
변경 사항은 반영되지 않음
변경 사항이 실시간으로 반영됨
VairibleEnvironment
에 담기는 내용 === LexicalEnvironment와 같지만실행 컨텍스트를 생성할 때
VairibleEnvironment
에 정보를 먼저 담고- 이를 그대로 복사해서
LexicalEnvironment
를 만들고- 이후에는
LexicalEnvironment
를 활용
두개의 내부에는
- environmentRecord
- outer-EnvironmentReference로 구성
초기화 과정
중에는 사실상 완전히 동일하고
이후 코드 진행에서 완전히 달라짐
사전적인
의미로 이해하는 것이 좋음현재 컨텍스트와 관련된 코드의 식별자 정보들이 저장
컨테스트를 구성하는 함수에 지정된
1. 매개변수 식별자
2. 선언된 함수가 있을 경우 → 그 함수 자체
3. var로 선언된 변수의 식별자
순서대로 수집
호스트 객체
로 분류호이스팅
이라고 함편의상 끌어올린 것으로 간주
function a (x) { // 수집 대상 1 - 매개변수
console.log(x); // (1)
var x; // 수집 대상 2 - 변수 선언
console.log(x); // (2)
var x = 2; // 수집 대상 3 - 변수 선언
console.log(x); // (3)
}
좀 더 사람의 입장에서 이해하기 위해서 코드를 변경
실제로는 이렇게 동작하지 않다는 것을 유의
function a () {
var x = 1; // 수집 대상 1 - 매개변수
console.log(x); // (1)
var x; // 수집 대상 2 - 변수 선언
console.log(x); // (2)
var x = 2; // 수집 대상 3 - 변수 선언
console.log(x); // (3)
}
여기서 호이스팅 처리를 한다면?
- environmentReocrd : 현재 실행될 컨텍스트의 대상 코드 내에
- 어떤 식별자가 있는지에만 관심
각 식별자에 어떤 값이 할당될 것인지는 관심이 없음
- 따라서 변수명만 끌어올리고 → 할당 과정은 원래 그자리에 둠
- 이는 매개변수도 마찬가지
변환 후
function a () {
var x; // 수집 대상 1의 변수 선언 부분
var x; // 수집 대상 1의 변수 선언 부분
var x; // 수집 대상 1의 변수 선언 부분
x = 1; // 수집 대상 1의 할당 부분
console.log(x); // (1)
console.log(x); // (2)
x = 2; // 수집 대상 3 - 변수 선언
console.log(x); // (3)
}
이미 선언된 변수 x가 있으므로 무시
호이스팅의 개념에 없으면 두번째
var x
// before 호이스팅
function a () {
console.log(b); // b 함수
var b = 'bbb';
console.log(b); // 'bbb'
function b () { }
console.log(b);
}
// after 호이스팅
function b() {
var b;
var b = function b () { }
console.log(b);
b = 'bbb';
console.log(b);
console.log(b);
}
함수의 선언 : 함수 전체를 끌어올림
변수 → 선언부만 끌어올리는 것과의 차이점
둘 모두 함수를 새롭게 정의할 때 쓰이는 방식
함수 선언문 → function 정의부만 존재
함수 표현식 → 정의한 function을 별도의 변수에 할당
선언문 : 반드시 함수명의 정의되어 있어야함
표현식 : 없어도 됨
기명 함수 표현식
익명 함수 표현식
function a () { } // 함수 선언문 : 함수명 a가 곧 변수명
a(); // 실행 OK
var b = function () {} // (익명) 함수 표현식 : 변수명 b가 곧 함수명
b(); // 실행 OK
var c = function d () {] // 기명 함수 표현식 변수명 : c, 함수명 : d
c(); // 실행 OK
d(); // 에러!
그럼 왜 기명 함수 표현식이 필요한가?
- 예전에는 내부에서 익명 함수 표현식을 호출 할 수 없었음
- undefined, unnamed → 지금은 잘 나옴 →
익명 함수 표현식의 변수명을 함수의 name 프로퍼티에 할당
- 굳이 있어야 하나?
// before 호이스팅
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;
}
// after
var sum = 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;
}
6번째 console.log(multiply)의 경우 실행이 되지 않음
- sum 함수는 선언 전에 호출해도 아무 문제 없이 실행됨
- 이는 양날의 검
- 선언 후에야 호출 가능함 → 우리가 흔히 알고 있음
- 다른 오해의 소지의 예
console.log(sum(3, 4));
// 첫 sum
function sum (x, y) {
return x + y;
}
var a = sum(1, 2);
// 두번 째 sum
function sum (x, y) {
return x + ' + ' + y + ' + ' = ' + (x + y);
}
var c = sum(1, 2);
console.log(c);
고로 함수 표현식으로 작성하는 것이 더 좋음
또는 함수명이 겹치지 않게 선언하는 것 또한 방법
스코프란 : 식별자에 대한 유효범위
이러한 스코프의 개념은 대부분의 언어에 존재함
오직 함수에 의해서만 스코프가 생성 되었음
유효범위를 안 → 밖으로 차례로 검색하는 것
스코프 체인
LexicalEnvironment의 두 번째 수집 자료
인outerEnvironmentReference
임outerEnvironment는 현재 호출된 함수가 선언될 당시의
LexicalEnvironmnet
를 참조함과거 시점인 선언될 당시
에 주목선언하다는 행위가 실제로 일어날 수 있는 시점이면
모든 코드는 실행 컨텍스트가 활성화 → 실행
여러 스코프에서
동일한 식별자를 선언
한 경우
무조건 스코프 체인 상에서가장 먼저 발견된 식별자에만 접근 가능
var a = 1;
var outer = function () {
var inner = function () {
console.log(a);
var a = 3;
};
inner();
console.log(a);
};
outer(); // 10 번째 줄
console.log(a);
실행 과정 - 김
전역 컨텍스트 활성화
environmentRecord
에 {a, outer} 식별자를 저장함outerEnvironment - Reference
에는 아무것도 담기지 않음 (this: 전역 객체)1번째 줄과 2번째 줄 : 전역 스코프에 있는 변수 a에 1을 outer에 함수를 할당
10번째 줄
outer 실행 컨텍스트의 environmentRecord
에 { inner } 식별자를 저장함
outerEnvironment -Reference
에는 outer 함수가 선언될 당시의 LexicalEnvironment
담김LexicalEnvironment
를 참조 복사함3번째 줄
7번째 줄 : inner 함수를 호출함
7번째 줄 : inner 실행 컨텍스트의 environmentRecord
에 { a } 식별자를 저장함
outerEnvironment
에는 inner 함수가 선언될 당시의 LexicalEnvironment
가 담김LexicalEnvironment
즉 [outer, {inner} ] 를 참조 복사함4번째 줄 : 식별자 a에 접근하고자 함
environmentRecord
에서 a를 검색함(undefined 출력)
5번째 줄 : inner 스코프에 있는 변수 a에 3을 할당함
6번째 줄
8번째 줄 :
LexicalEnvironment
에 접근함environmentRecord
로 넘어가는 식으로 계속해서 검색함LexicalEnvironment
에 a가 있으니 a에 저장된 값을 반환9번째 줄 :
11번째 줄
- 식별자 a에 접근하고자 함
- 현재 활성화 상태인 전역 컨텍스트의 environmentRecord
에서 a를 검색함
- 바로 a를 찾을 수 잇음
- 이로써 모든 실행이 종료됨
inner, outer, 전역 모두 접근 가능
하지만 스코프 체인 상에 있는 변수라고 해서 무조건 접근 가능한 것은 아님
- 식별자 a의 경우
- 전역 공간
- inner 내부에서도 선언했음
- inner 함수 내부에서 a에 접근하려고 하면
- inner 스코프의
LexicalEnvironment
부터 검색할 수밖에 없음- inner 내부에서 존재하므로 검색을 진행하지 않고
즉시 반환을 하게 되는 이유임
- 이를
변수 은닉화
실행 컨텍스트 : 실행할 코드에 제공할 환경 정보를 모아 놓은 객체
실행 컨텍스트는의 종류
실행 컨텍스트 객체는 활성화 되는 시점에
VairialbeEnvironment
LexicalEnvironment
environmentRecord
outerEnvironmentReference
LexicalEnvironment
참조호이스팅
선언문 : function name () {}
표현식 : var x = function () {}
스코프
outerEnvironmentReference
: 해당 함수가 선언된 위치의 LexicalEnvironment
참조LexicalEnvironment
탐색outerEnvironmentReference
의 LexicalEnvironment
탐색LexicalEnvironment
까지 탐색해서 찾지 못하면 → undefined코어 자바스크립트 (정재남님)