본문 바로가기
Development

[21.01.06] YDKJSY - The Scope Chain

by igy95 2024. 1. 7.

"Lookup" Is (Mostly) Conceptual

  • lookup (검색) : 변수의 런타임 접근으로써, JS 엔진은 변수가 위치한 스코프에서 출발해 참고하고자 하는 변수가 존재할 때까지 상단 / 외부 스코프로 올라간다. 검색을 중지하는 조건은 매칭되는 변수를 찾았거나, 전역 스코프에 도달하거나 둘 중 하나이다.

하지만 위의 예시는 개념적인 이해를 돕기 위한 설명일 뿐이지, 실제로 작동하는 방식은 아니다. 각 스코프는 초기 컴파일 동안 대개 결정된다. 왜냐하면 렉시컬 스코프는 이미 이 시점에서 완료되고, 변수는 실행 시점에서 발생하는 어떤 것에도 영향을 받지 않기 때문이다.

 

이러한 정보들은 컴파일 동안 결정되기에 AST 단계의 내부 변수에 저장된다. 그리고 프로그램을 실행하는 명령들에 의해 사용된다. 즉, JS 엔진은 애초에 변수를 어느 스코프에서 참조해야 하는지 알기 위해서 여러 스코프를 검색할 필요가 없다. 이렇게 런타임 검색의 필요를 없애는 방식이 렉시컬 스코프의 이점으로 얻을 수 있는 최적화 기법인 것이다.

 

반대로 컴파일 시점보다는 실행 시점에서 파악해야 하는 변수들이 있다. 선언되지 않은 변수들에 대한 참조가 그 예인데, undeclared 에 대한 참조가 반드시 에러를 리턴하지는 않는다.

function test() {
    name = 'kyle';
      console.log(name);
}

test(); //kyle
console.log(name); //kyle

이는 strict mode 냐 아니냐에 따라 달라지는데, 만약 후자일 경우 전역 스코프에서 해당 변수를 새로 생성해준다. 따라서 어느 변수가 어느 스코프에서 적절히 선언되었는지까지 파악하는 문제는 실행 시점까지 넘어가게 된다.

Shadowing

스코프마다 각기 다른 변수들을 취하는 것은 큰 문제가 되지 않는다. 다만 두 개 이상의 스코프가 존재할 때 서로 다른 스코프가 같은 이름의 변수를 가지고 있다면 이는 고려해봐야 하는 부분이다. 때문에 두 개 이상의 같은 이름을 가진 변수들을 유지하기 위해서는 스코프를 분리해야만 한다.

var studentName/*1*/ = "Suzy";

function printStudent(studentName/*2*/) {
    studentName = studentName.toUpperCase();
      console.log(studentName);
}

printStudent("Frank"); //FRANK
console.log(studentName); //Suzy

예시의 studentName 은 전역 스코프와, 함수 스코프 내에서 동일한 이름으로 존재하는 변수이다. printStudent 함수 안에 있는 studentName 은 모두 2번 스코프를 참조하게 되는데, lookup 규칙에 따르면 엔진은 참조하려는 변수에 대한 검색을 마치면 그 위로 올라가지 않고 멈추기 때문에 1번 전역 스코프의 studentName은 고려하지 않는다. 이러한 특성을 shadowing 이라 한다.

정리

함수가 정의 되었을 때, 새로운 스코프도 생성된다. 서로 다른 중첩된 스코프들의 포지셔닝은 자연스레 프로그램 내부에서 (스코프) 계급을 형성하게 되는데, 이를 스코프 체인이라고 한다. 스코프 체인은 변수의 접근을 현재 스코프와 상단 / 외부 에서만 조작한다.

각 스코프들은 자신의 변수 셋을 가지고 있는데, 만약 스코프 체인 내부 다른 스코프에서 동일한 변수명이 존재할 경우 shadowing 이 발동하고 두 변수의 위치를 비교해 더 상단에 위치한 변수의 접근을 막는다.