소스코드의 평가와 실행
- 소스코드 평가
- 실행 컨텍스트 생성
- 변수, 함수 등의 선언문 실행 -> 렉시컬 환경의 환경 레코드에 등록
- 소스코드 실행 (런타임)
- 실행에 필요한 정보를 렉시컬 스코프에서 검색
실행 컨텍스트의 역할
1. 코드 실행 순서 관리
- 실행 컨텍스트 스택으로 관리
- 현재 실행 중인 코드의 실행 순서를 변경 가능(ex 함수 호출에 의한 실행 순서 변경)
2. 스코프 관리(식별자 관리)
- 렉시컬 환경으로 관리
- 선언에 의해 생성된 모든 식별자를 스코프를 구분하여 등록
- 스코프는 중첩 관계에 의해 스코프 체인을 형성 -> 상위 스코프로 이동하며 식별자 검색 가능
실행 컨텍스트 스택
> 코드의 실행 순서를 관리
> 소스코드 평가 -> 실행 컨텍스트 생성 -> 실행 컨텍스트 스택에 push
> 실행 컨텍스트 최상위에 존재하는 실행 컨텍스트는 현재 실행 중인 코드의 실행 컨텍스트이다.
렉시컬 환경
> 식별자와 식별자에 바인딩된 값, 상위 스코프에 대한 참조를 기록
> 스코프와 식별자를 관리
- 구성요소
- 환경 레코드: 식별자를 등록하고 등록된 식별자에 바인딩된 값을 관리하는 저장소
- 외부 렉시컬 환경에 대한 참조: 상위 스코프의 렉시컬 환경에 대한 참조, 스코프 체인이 구현될때 이 경로를 이용 (단방향 링크드 리스트)
실행 컨텍스트의 생성과 식별자 검색 과정
var x = 1;
const y = 2;
function foo(a) {
var x = 3;
const y = 4;
function bar(b) {
const z = 5;
console.log(a+b+x+y+z);
}
bar(10);
}
foo(20); // 42
1. 전역 객체 생성
- 전역 코드 평가 이전에 생성
- 전역 객체에 빌트인 전역 프로퍼티, 빌트인 전역 함수, 표준 빌트인 객체 추가
- 호스트 객체 포함(동작 환경에 따라 바뀜)
2. 전역 코드 평가
전역 코드 평가 순서
1. 전역 실행 컨텍스트 생성
2. 전역 렉시컬 환경 생성
2-1. 전역 환경 레코드 생성
2-2. this 바인딩
2-3. 외부 렉시컬 환경에 대한 참조 결정
1. 전역 실행 컨텍스트 생성: 비어있는 전역 실행 컨텍스트 생성 -> 실행 컨텍스트 스택에 push
2. 전역 렉시컬 환경 생성: 렉시컬 환경 생성 -> 전역 실행 컨텍스트에 바인딩 (참조 연결)
2-1. 전역 환경 레코드 생성: 객체 환경 레코드, 선언적 환경 레코드 바인딩
- 객체 환경 레코드: BindingObject를 통해 var로 선언한 변수, 함수 선언문이 바인딩됨 (window를 통해 참조 가능)
- 선언적 환경 레코드: let, const 로 선언한 변수는 여기에 저장됨. 전역 객체(window)의 프로퍼티가 아닌 이유임
2-2. this 바인딩
- 전역 환경 레코드의 [[GlobalThisValue]] 내부 슬롯에 this가 바인딩 됨 -> 일반적으로 전역 코드의 this는 전역 객체를 가리킴
2-3. 외부 렉시컬 환경에 대한 참조 결정
- 상위 스코프를 가리킴 (단방향 링크드 리스트인 스코프 체인 구현)
- 전역 렉시컬 환경의 외부 렉시컬 환경에 대한 참조는 null (스코프 체인의 종점)
3. 전역 코드 실행
- 변수 할당문 실행 -> x,y에 값 할당, foo 호출
- 변수 할당문, 함수 호출문을 실행하려면 어느 스코프의 식별자를 참조하면 되는지 식별자 결정을 해야 한다.
- 식별자 결정: 실행 중인 실행 컨텍스트에서 식별자를 검색 -> 없다면, 외부 렉시컬 환경에 대한 참조를 타고 상위 스코프로 이동 -> 그래도 없다면 참조 에러 발생 (스코프 체인의 동작 원리)
4. foo 함수 코드 평가
함수 코드 평가 순서
1. 함수 실행 컨텍스트 생성
2. 함수 렉시컬 환경 생성
2-1. 함수 환경 레코드 생성
2-2. this 바인딩
2-3. 외부 렉시컬 환경에 대한 참조 결정
1. 함수 실행 컨텍스트 생성: foo 함수 실행 컨텍스트 생성
- 함수 실행 컨텍스트는 함수 렉시컬 환경이 완성된 이후 실행 컨텍스트 스택에 push 함!
2. 함수 렉시컬 환경 생성: foo 함수 렉시컬 환경 생성 -> 함수 실행 컨텍스트에 바인딩
2-1. 함수 환경 레코드 생성
- 매개변수, arguments 객체, 함수 몸체에서 선언된 식별자를 등록, 관리
- 함수 환경 레코드는 선언적 환경 레코드의 서브 타입이다.
- 따라서 var와 let,const 로 선언된 변수가 따로 바인딩되는게 아님!
-> var로 선언한 변수도 함수 블록 내에 갇히게 되는 이유 == 함수 내부에서 선언한 var 변수가 전역 객체에 바인딩 되지 않는 이유
2-2. this 바인딩
- 함수 황경 레코드의 [[ThisValue]] 내부 슬롯에 this가 바인딩 됨 -> 함수 호출 방식에 따라 결정됨
- foo는 일반 함수로 호출되었으므로 this는 전역 객체를 가리킴 -> [[ThisValue]] 내부 슬롯에 전역 객체 바인딩
- foo 함수 내부에서 this 참조 시 [[ThisValue]] 에 바인딩된 값이 반환된다
2-3. 외부 렉시컬 환경에 대한 참조 결정
- foo 함수 정의가 평가되 시점에 실행중인 실행 컨텍스트의 렉시컬 환경 === 전역 실행 컨텍스트의 렉시컬 환경
- 전역 실행 컨텍스트 렉시컬 환경의 참조 바인딩
- [[Environment]] 에 저장된 값 === 외부 렉시컬 환경에 대한 참조 -> 렉시컬 스코프를 구현하는 매커니즘 -> 클로저 이해하는데 중요한 단서
5. foo 함수 코드 실행
- 식별자 결정을 위해 식별자 검색 -> 검색된 식별자에 값을 바인딩
6. bar 함수 코드 평가
- foo 함수 평가와 동일한 과정을 거쳐 bar 함수의 실행 컨텍스트와 렉시컬 환경이 생성됨
7. bar 함수 코드 실행
- 식별자 결정을 위한 식별자 검색 -> 값 할당
- console.log(a+b+x+y+z) 실행
1. console 식별자 검색: console 스코프 체인에서 검색(상위 스코프로 이동하며 전역 렉시컬 환경까지 이동) -> 객체 환경 레코드의 BindingObject를 통해 전역 객체에서 검색 가능
2. log 메서드 검색: console 객체의 프로토타입 체인을 통해 메서드 검색
3. 표현식 a+b+x+y+z의 평가: a,b,x,y,z 식별자를 스코프 체인에서 각각 검색
4. console.log 메서드 호출: 평가된 값을 console.log 메서드에 전달하여 호출
8. bar 함수 코드 실행 종료
- 실행 컨텍스트 스택에서 bar 함수 실행 컨텍스트 제거
- 하지만 bar 함수의 렉시컬 환경까지 즉시 소멸되는 것은 아님! -> 누군가에 의해 참조되지 않을때 GC가 메모리에서 해제 시킴
- 누군가 값을 참조하고 있다면 소멸되지 않음 (클로저와 연관된 내용)
9. foo 함수 코드 실행 종료
- 실행 컨텍스트 스택에서 foo 함수 실행 컨텍스트 제거
10. 전역 코드 실행 종료
- 더는 실행 할 코드가 없으므로 전역 실행 컨텍스트도 스택에서 pop
실행 컨텍스트와 블록 레벨 스코프
let x = 1;
if(true) {
let x = 10;
console.log(x); // 10
}
console.log(x); // 1
- var 키워드로 선언한 변수는 함수 레벨 스코프를 따른다.
- let, const로 선언한 변수는 블록레벨 스코프를 따른다. (if 문, for 문, while 문, try/catch 문 등)
- 블록 레벨 스코프가 새로 생성될때 외부 렉시컬 환경에 대한 참조는 블록 레벨 스코프가 생성되기 이전에 실행되고 있던 렉시컬 환경이다.
'FE > Javascript' 카테고리의 다른 글
[Javascript] this 복습 (2) | 2025.08.04 |
---|---|
[Javascript] 프로토타입 복습 (1) | 2025.07.29 |
13. 모던 자바스크립트 Deep Dive(스코프) (0) | 2024.12.08 |
12. 모던 자바스크립트 Deep Dive(함수) (0) | 2024.12.07 |
11. 모던 자바스크립트 Deep Dive(원시 값과 객체의 비교) (0) | 2024.12.06 |