[JS] 실행 컨텍스트 - 스코프, 스코프 체인, outerEnvironmentReference

이 글은 📕코어 자바스크립트 책을 바탕으로 정리한 글입니다.

💡 스코프

스코프란? 식별자에 대한 유효범위
어떤 경계 A의 외부에서 선언한 변수는 A의 외부 뿐 아니라 A의 내부에서도 접근이 가능하지만,
A의 내부에서 선언한 변수는 오직 A의 내부에서만 접근할 수 있다.
ES5까지의 자바스크립트는 전역공간을 제외하면 오직 함수에 의해서만 스코프가 생성된다.

ES6에서는 블록에 의해서도 스코프 경계가 발생하게 함으로써 다른 언어와 훨씬 비슷해졌다.
다만 var로 선언한 변수에 대해서는 작용하지 않고 오직 새로 생긴 let과 const, class, strict mode에서의 함수 선언 등에 대해서만 범위로서의 역할을 수행한다.
ES6에서는 둘을 구분하기 위해 함수 스코프, 블록 스코프라는 용어를 사용한다.

이러한 '식별자의 유효범위'를 안에서부터 바깥으로 차례로 검색해나가는 것을 스코프 체인(scope chain)이라고 한다.
그리고 이를 가능하게 하는 것이 바로 LexicalEnvirionment의 두 번째 수집 자료인 outerEnvironmentReference이다.

스코프 체인

outerEnvironmentReference는 현재 호출될 함수가 선언될 당시의 LexicalEnvironment를 참조한다.
여기서 주목해야하는 부분은 '선언될 당시'이다.
'선언하다'라는 행위가 실제로 일어날 수 있는 시점이란 콜 스택 상에서 어떤 실행 컨텍스트가 활성화된 상태일 때 뿐이다.
어떤 함수를 선언하는 행위 자체도 하나의 코드에 지나지 않으며, 모든 코드는 실행 컨텍스트가 활성화 상태일 때 실행되기 때문이다.

예를 들어 A 함수 내부에 B 함수를 선언하고 B 함수 내부에 C 함수를 선언했다고 하자.
함수 C의 outerEnvironmentReference는 함수 B의 LexicalEnvironment를 참조한다.
함수 B의 LexicalEnvironment에 있는 outerEnvironmentReference는 다시 함수 B가 선언되던 때(A)의 LexicalEnvironment를 참조한다.
이 처럼 outerEnvironmentReference는 연결 리스트(linked list)형태를 띤다.
'선언 시점의 LexicalEnvironment'를 계속 찾아 올라가면 마지막엔 전역 컨텍스트의 LexicalEnvironment가 있을 것이다.
각 outerEnvironmentReference는 가장 가까운 요소부터 차례대로만 접근할 수 있고 다른 순서로 접근하는 것은 불가능하다.

📌 정리!
이런 구조적 특성 덕분에 여러 스코프에서 동일한 식별자를 선언한 경우에는 무조건 스코프 체인 상에서 가장 먼저 발견된 식별자에만 접근이 가능하다!

스코프 체인 예제


위 코드를 실행하면

다음과 같은 결과가 나온다.

전역 공간에서는 전역 스코프에서 생성된 변수에만 접근할 수 있다.
outer 함수 내부에서는 outer 및 전역 스코프에서 생성된 변수에 접근할 수 있지만 inner 스코프 내부에서 생성된 변수에는 접근하지 못한다.
inner 함수 내부에서는 inner, outer, 전역 스코프에 접근할 수 있다.

그런데 inner 함수 내부인 [4]번째 라인에서 undefined의 결과가 나온 이유는?
스코프 체인 상에 있는 변수라고 해서 무조건 접근 가능한 것은 아니다.
위 코드 상의 식별자 a는 전역 공간에서도 선언했고 inner 함수 내부에서도 선언했다.
inner 함수 내부에서 a에 접근하려고 하면 무조건 스코프 체인 상의 첫 번째 인자, 즉 inner 스코프의 LexicalEnvironment부터 검색할 수 밖에 없다.
inner 스코프의 LexicalEnvironment에 a 식별자가 존재하므로 스코프 체인 검색을 더 진행하지 않고 즉시 inner LexicalEnvironment 상의 a를 반환하게 된다.
위와 같은 현상을 변수 은닉화(variable shadowing)이라고 한다.

크롬 브라우저에서 스코프 정보 확인하기

크롬 브라우저 환경에서는 스코프 체인 중 현재 컨텍스트를 제외한 상위 스코프 정보들을 개발자 도구의 콘솔을 통해 간단하게 확인할 수 있다.

var a = 1;
var outer = function () {
  var b = 2;
  var inner = function () {
    console.dir(inner);
  };
  inner();
};
outer();

확인하는 방법은 위와 같이 함수 내부에서 함수를 출력하는 것이다.

console.dir()?
개발자가 쉽게 객체의 속성을 얻을 수 있도록 콘솔에서 지정된 JavaScript 객체의 모든 속성을 볼 수 있는 방법

디버거를 이용하면 좀 더 제대로 된 정보를 확인할 수 있다.
console.dir(...) 부분을 debugger로 바꾸어 실행해보자

var a = 1;
var outer = function () {
  var b = 2;
  var inner = function () {
    debugger; <- !! 
  };
  inner();
};
outer();

전역변수와 지역변수

위 예제에서 전역변수는 전역 스코프에서 선언한 a와 outer 둘이다.
지역변수는 outer 함수 내부에서 선언한 inner와 inner 함수 내부에서 선언한 a 둘이다.
즉 전역 공간에서 선언한 변수는 전역변수이고, 함수 내부에서 선언한 변수는 무조건 지역변수이다.

지금껏 전역변수, 지역변수 모두 그저 당연한 의미로 받아들이고 사용했는데, 스코프에 대해 제대로 이해를 하니 전역변수, 지역변수의 의미가 더 와닿는 것 같다.
전역변수를 가급적 최소화해야하는 이유에 대해 더 깊게 생각할 수 있었다.

실행 컨텍스트의 thisBinding에는 this로 지정된 객체가 저장된다.
다음 페이지에서 this에 대해 다뤄보자

좋은 웹페이지 즐겨찾기