
벌써 일주일이 지나 세번째 스터디 시간!
이번에는 렉시컬 스코프에 대해 발표를 하게 되었다.
렉시컬 스코프: 컴파일/파싱 시점에 코드의 정적 구조에 따라 스코프가 결정되는 방식
렉시컬: 컴파일 세 단계 중 첫번째 단계인 렉싱(파싱)을 의미
렉시컬 스코프를 구슬과 양동이로 예시를 들어보자.
여러 색이 있는 구슬을 같은 색의 양동이에 분류하는 작업 ← **렉시컬 스코프**
- 빨간색 양동이 ← 빨간색 구슬
- 파란색 양동이 ← 파란색 구슬
- 초록색 양동이 ← 초록색 구슬
위와 같이 정리가 되어있다면,
초록색 구슬이 필요할 때 초록색 양동이를 찾으면 된다는 것을 알 수 있다.
선언된 변수나 식별자, 즉 여기서 말하는 구슬의 참조는
현재의 스코프나 현재 스코프의 위, 혹은 바깥(부모) 스코프에 있어야지만 가능하다.
정의가 아래(TDZ) 혹은 안쪽 스코프(자식)에 있으면 불가능하다.
TDZ (Temporal Dead Zone)
let, const, class 등으로 선언된 변수는 해당 선언문이 실행되기 전까지 접근할 수 없는 상태가 되는데, 이를 TDZ라고 한다.
즉, 스코프에 존재는 하지만 아직 초기화되지 않아 사용 불가능한 구역을 말한다.
런타임 중 비선언 구슬 색을 결정짓는 절차를 탐색이라고 개념화 할 수 있다.
// 빨간 버블 양동이: 외부/전역 스코프
var students = [
{id: 14, name: "카일"},
{id: 73, name: "보라"},
{id: 112, name: "지수"},
{id: 6, name: "호진"},
];
function getStudentName (studentID) {
// 파란 버블 양동이: 함수 스코프
for(let student of students) {
// 초록 버블 양동이: 반복문 스코프
if(student.id == studentID) {
return student.name
}
}
}
var nextStudent = getStudentName(73);
console.log(nextStudent); // 보라

위 예시에서 for문이 참조하고 있는 변수 students는 선언이 아니기 때문에 색을 지정할 수 없다.
따라서 파란 양동이 스코프에서 이름이 students인 구슬을 찾지만 students를 찾을 수 없다.
그러면 다음 바깥 스코프인 빨간 양동이로 영역을 확장해 구슬이 있는지 찾는다.
빨간 양동이에는 students가 있으므로 for문의 students는 빨간 양동이에 있는 구슬을 참조한다.
초록 양동이 안에 있는 studentID 역시 동일한 방식으로 참조할 구슬을 찾는다.

매개변수 studentID는 정확히는 파란 양동이 구슬이라고 할 수 없다.
관련된 내용은 YDKJSY 부록 A 참고.

마트료시카 구조 (중첩된 스코프)
단방향 거울 효과 (접근 규칙)
마치 안쪽 인형 속 사람이 반투명 거울을 통해 바깥을 볼 수 있지만, 바깥 사람은 안을 볼 수 없는 것처럼, JavaScript의 스코프도 단방향 접근성을 가진다.

아파트 비유
핵심 규칙
1. 선언 위치가 중요 - 호출 위치는 상관없음
2. 안에서 밖으로 - 내부는 외부 변수에 접근 가능, 역은 불가
3. 스코프 체인 - 없으면 상위 스코프로 계속 올라감
변수 선언
변수(구슬)는 특정 스코프에서 선언되고
선언된 변수(구슬)은 다양한 색을 가지게 되며 같은 색 양동이에 담긴다.
변수 참조
변수 참조는 선언이 이뤄진 스코프를 기준으로한다.
동일한 스코프나 더 깊은 스코프에 있는 변수 참조는 해당 스코프와 동일한 색을 가진 구슬이 된다.
다만, 중간 스코프에서 변수 선언을 섀도잉 하면 변수 참조와 가장 가까운 변수 선언의 스코프를 따른다.
구슬 색 결정
양동이 색과 구슬의 색은 컴파일 중에 결정된다.
컴파일레이션 과정에서 확정된 정보는 프로그램 실행 중에 변수(구슬)를 탐색하는데 사용한다.
JS 엔진 내부 구성요소들이 대화를 통해 코드를 처리하고 실행한다고 가정해보자.
// 빨간 버블 양동이: 외부/전역 스코프
var students = [
{id: 14, name: "카일"},
{id: 73, name: "보라"},
{id: 112, name: "지수"},
{id: 6, name: "호진"},
];
function getStudentName (studentID) {
// 파란 버블 양동이: 함수 스코프
for(let student of students) { // for문의 students
// 초록 버블 양동이: 반복문 스코프
if(student.id == studentID) {
return student.name
}
}
}
var nextStudent = getStudentName(73);
console.log(nextStudent); // 보라
변수의 선언과 할당하는 코드를 JS 엔진은 아래와 같이 처리한다.

1. 컴파일 중 컴파일러가 처리하는 작업
var sturents 를 만난 컴파일러는 스코프 매니저에게 특정 스코프 양동이에 students라는 이름을 진 변수가 있는지 물어본다.
students라는 이름의 변수를 생성해달라고 요청한다.함수와 같은 새로운 스코프를 만나면 스코프 매니저에게 스코프 생성을 요청한다.
위와 같은 동작을 반복하여 엔진이 실행할 코드를 생성한다.

2. 실행 중 엔진이 처리하는 작업
컴파일러는 프로그램 실행 시점에 엔진이 실행할 students = [] 할당문에 대한 코드를 생성한다.
엔진은 추후 실행 시점에 컴파일러가 생성한 코드를 보고 스코프 매니저에게 현재 스코프 양동이에 students라는 이름을 가진 변수가 있냐 물어본다.
students 변수를 찾는다.students 변수를 찾으면 엔진은 여기에 배열([...])의 참조를 할당한다.컴파일
컴파일러가 스코프 매니저에게 요청.
식별자 유무 확인, 스코프 생성, 변수 생성 등의 과정을 반복하여
엔진이 실행할 코드 생성
실행
엔진이 스코프 매니저에게 요청.
생성된 스코프와 변수를 탐색하여 값을 참조하고
함수 스코프를 인스턴스화