이번에는 식별자를 결정하는 과정과 함수 코드는 자바스크립트 엔진이 어떻게 평가하고 실행하는지 과정을 알아볼 것이다.
식별자는 스코프가 다른 경우에 같은 이름을 가질 수 있다. 이는 스코프가 다르면 동일한 이름의 식별자가 여러 개 존재할 수 있음을 뜻한다. (이는 의도치 않은 에러를 발생시킨다..ㅠ )
식별자 결정을 위해서 실행 중인 실행 콘텍스트에서부터 식별자를 검색하기 시작한다
선언된 식별자는 실행 콘텍스트의 렉시컬 환경 레코드에 등록되어 있다.
만약 실행 중인 실행 콘텍스트에서 식별자를 찾지 못하면 외부 렉시컬 환경에 대한 참조에 바인딩된 렉시컬 환경, 즉 상위 스코프로 이동하여 식별자를 검색한다.
이것이 바로 스코프 체인의 동작 원리이다.
전역 함수의 평가과정이 끝나고 실행하는 도중 함수를 만나 전역 렉시컬 환경 레코드에서 함수명에 해당하는 식별자를 찾아 바인딩된 함수 객체를 실행한다고 해보자.
var x = 1;
const y = 2;
function foo(a){
var x = 3;
const y =4;
function bar(b){
const z =5;
console.log(`${x + y + a + b + z}`);
}
bar(10);
}
foo(20); <- 실행위치
함수의 코드 평가 과정
- 함수 실행 콘텍스트 생성
- 함수 렉시컬 환경 생성
- 함수 환경 레코드 생성
- this 바인딩
- 외부 렉시컬 환경에 대한 참조 결정
- 함수 코드 실행
1) 함수 실행 컨텍스트 생성
foo 함수 실행 콘텍스트를 생성한 후 함수 렉시컬 환경이 완성된 다음 실행 콘텍스트에 푸시된다.
현재 running execution context는 foo 함수 실행 콘텍스트이다.
2) 함수 렉시컬 환경 생성
함수 렉시컬 환경에도 2개의 컴포넌트 환경 레코드와 외부 렉시컬 환경에 대한 참조로 구성된다.
2.1) 함수 환경 레코드 생성
전역 환경 레코드는 선언적 환경 레코드와 객체 환경 레코드로 구성된 것과 달리
함수 환경 레코드는 매개변수, arguments 객체, 함수 내부에서 선언한 지역 변수와 중첩 함수를 등록하고 관리하는 것 같다. (최근 arguments를 활용한 함수를 작성하는데 MDN에서 deprecated 되었다던데?..)
그렇다면 foo 함수 환경 레코드에는 x, y, bar, arguments 객체, a가 환경 레코드에 등록되었을 것이다.
(매개변수 a와 arguments객체는 다르다)
2.2) this 바인딩
함수 환경 레코드의 [[ThisValue]] 내부 슬롯에 this가 바인딩된다. 바인딩될 대상은 함수의 호출 방식에 따라 결정된다.
foo함수는 일반 함수로 호출되었기 때문에 this는 전역 객체를 가리킨다. 만약 생성자 함수로 호출되었다면 생성될 인스턴스가 바인딩되었을 것이다.
2.3) 외부 렉시컬 환경에 대한 참조 결정
foo 함수 정의가 평가된 시점에 실행 중인 실행 컨텍스트의 렉시컬 환경의 참조가 할당된다.
이는 중요한데 foo 함수는 전역 코드에 정의된 전역 함수이다. 이 경우 foo함수의 정의는 전역 코드 평가 시점에 평가되며 이 시점의 실행 중인 (foo함수를 실행한?) 실행 컨텍스트는 전역 실행 컨텍스트이다. 따라서 외부 렉시컬 환경에 대한 참조에는 전역 렉시컬 환경의 참조가 할당된다.
(그럼 함수는 어디에서 실행되느냐가 아닌 어디에 선언되어있느냐에 따라 상위 스코프가 결정된다는 이야긴데.... 그럼 외부 라이브러리는?? 아 추가 설명이 있다.)
자바스크립트 엔진은 함수 정의를 평가하여 함수 객체를 생성할 때 현재 실행 중인 실행 컨텍스트의 렉시컬 환경,
즉 함수의 상위 스코프를 함수 객체의 내부 슬롯 [[Environment]]에 저장한다.
여기서 잠깐 혼동되었는데 [[ThisValue]]는 함수 호출 방식에 따라 this에 바인딩된 객체를 참조하고
[[Environment]]는 외부 렉시컬 환경, 즉 상위 스코프를 의미한다.
함수 객체를 생성할 때 함수 객체의 내부 슬롯에 상위 스코프를 저장한다고 했다. 따라서 foo 함수의 렉시컬 환경에 존재하는 외부 렉시컬 환경 참조에는 전역 렉시컬 환경이 바인딩될 것이다.
즉 함수 객체의 내부 슬롯[[Environment]]이 렉시컬 스코프를 구성하는 메커니즘이다. 이는 클로저를 이해할 수 있는 중요한 단서이다.
3) 함수 코드 실행
var x = 1;
const y = 2;
function foo(a){
var x = 3;
const y =4;
function bar(b){
const z =5;
console.log(`${x + y + a + b + z}`);
}
bar(10);
}
foo(20); <- 실행위치
매개변수에 인수가 할당되고, 변수 할당 문이 실행되어 지역 변수 x, y에 값이 할당되고 함수 bar()가 호출된다.
이때 식별자 결정을 위해 foo 함수 실행 컨텍스트의 렉시컬 환경에서 식별자를 검색한다.
x, y 모두 현재 실행 중인 실행 컨텍스트의 렉시컬 환경에서 검색 가능하기 때문에 검색된 식별자에 값을 바인딩한다.
3.1) bar 함수 코드
현재 실행 중인 실행 컨텍스트는 foo 함수 실행 컨텍스트이다. foo함수 실행 과정에서 bar()함수를 호출한다.
그럼 bar함수 코드를 평가하면서 다음과 같은 순서로 처리될 것이다.
- bar 함수 실행 컨텍스트 생성
- bar 함수 실행 컨텍스트의 렉시컬 환경 생성
- 렉시컬 환경의 환경 레코드 생성
- this 바인딩( 일반 함수로 호출되었기 때문에 this에는 전역 객체 바인딩)
- 외부 렉시컬 환경 참조 생성 (이 경우 함수를 평가한 주체는 foo 때문에 foo 함수의 렉시컬 환경이 바인딩된다)
3.2) console의 식별자 검색
먼저 console 식별자를 스코프 체인에서 검색한다. 현재 실행 중인 bar 실행 컨텍스트의 렉시컬 환경에서 시작하여 외부 렉시컬 환경에 대한 참조로 이어지는 foo 실행 컨텍스트의 렉시컬 환경으로 진행되며 이후 전역 렉시컬 환경으로 이어진다. 그 이유는 bar, foo에서 console 식별자가 없기 때문이다.
console 식별자는 전역 렉시컬 환경의 객체 환경 레코드의 BindingObject를 통해 전역 객체에서 찾을 수 있다.
이제 console 식별자에서 log 메서드를 검색한다. 이때 console 객체의 프로토타입 체인을 통해 메서드를 검색한다.
이제 a, b, x, y, z 식별자를 검색하며 현재 실행 컨텍스트 부터 연속되는 외부 렉시컬 환경에 대한 참조로 진행된다.
표현식이 평가되어 생성된 값을 console.log() 메서드에 전달하여 호출한다.
3.3) bar 함수 코드 실행 종료
현재 실행 컨텍스트 스택에는 bar 함수 실행 컨텍스트가 최상단에 위치하고 있다. 이 말은 running execute context가 bar 함수 실행 컨텍스트라는 이야기이며, cosole.log 메서드 호출되고 종료 시 진행할 코드가 없어 코드의 실행이 종료된다. 이후 bar 함수 실행 컨텍스트는 스택에서 pop되고 foo 실행 컨텍스트가 최상단에 위치한다.
실행 컨텍스트 스택에서 pop 되었다고 bar 함수 렉시컬 환경까지 즉시 소멸하는 것은 아니다. 렉시컬 환경을 실행 컨텍스트에 의해 참조되기는 하지만 독립적인 객체이다. 객체를 포함한 모든 값은 누군가에 의해 참조되지 않을 때 비로소 가비지 컬렉터에 의해 메모리 공간의 확보가 해제되어 소멸한다.
3.4) foo 함수 코드 종료
foo 실행 컨텍스트도 마찬가지로 더 이상 진행할 코드가 없기 때문에 실행컨텍스트 스택에서 pop 된다.
3.5) 전역 코드 실행 종료
이후 최상단에 있는 전역 실행 컨텍스트도 스택에서 pop된다. 이후 실행 컨텍스트 스택에는 아무것도 남아있지 않게 된다.
+ 블록 레벨 스코프
var 키워드로 선언한 변수는 오로지 함수의 코드 블록만 지역 스코프로 인정하는 함수 레벨 스코프를 따른다. 하지만 let, const로 선언한 변수는 모든 코드 블록(if문, 함수, for문, while문 try/catch문 등)을 지역 스코프로 인정하는 블록 레벨 스코프를 따른다.
이 경우 선언적 환경 레코드를 갖는 블록 레벨 스코프 환경을 새롭게 생성해 기존의 렉시컬 환경을 교체한다.
이 때 생성된 렉시컬 환경의 외부 렉시컬 환경에 대한 참조는 블록 레벨 스코프 실행 이전의 렉시컬 환경을 가리키며 블록 레벨의 스코프를 가진 코드 블록이 실행이 종료되었을 때 이전의 렉시컬 환경으로 되돌린다.
'JavaScript' 카테고리의 다른 글
클로저의 활용 (0) | 2022.02.24 |
---|---|
클로저 (0) | 2022.02.24 |
전역 코드 실행 컨텍스트 (0) | 2022.02.22 |
this (0) | 2022.02.04 |
함수, 1급 객체 (0) | 2022.01.25 |
댓글