CoreJS - 스코프, 스코프 체인

SANGKU OH·2020년 11월 26일
1

CoreJavascript

목록 보기
5/10
post-thumbnail

스코프

스코프란 식별자에 대한 유효범위이다.
어떤 경계 A의 외부에서 선언한 변수는 A의 외부뿐 아니라 A의 내부에서도 접근이 가능지만, A의 내부에서 선언한 변수는 오직 A의 내부에서만 접근할 수 있다.

다만, 전역공간을 제외하면 오직 함수에 의해서만 스코프 생성된다.

스코프 체인

이러한 '식별자의 유효범위'를 안에서부터 바깥으로 차례로 검색해나가는 것을 스코프 체인이라 한다.

그리고 이를 가능케 하는 것이 바로 LexicalEnvironment의 두 번째 수집 자료인 outerEnvironmentReference이다.

'선언하다'라는 행위가 실제로 일어날 수 있는 시점이란 콜 스택 상에서 어떤 실행 컨텍스트가 활성화된 상태일 때 뿐이다. 어떤 함수를 선언(정의)하는 행위 자체도 하나의 코드에 지나지 않으며, 모든 코드는 실행 컨텍스트가 활성화 상태일 때 실행되기 때문이다.

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

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

스코프 체인

var a = 1;
var outer = function () {
  var inner = function () {
    console.log(a);
    var a = 3;
  };
  inner();
  console.log(a);
};
outer();
console.log(a);
  • 시작: 전역 컨텍스트가 활성화 된다. 전역 컨텍스트의 environmentRecord { a, outer } 식별자를 저장합니다. 전역 컨텍스트는 선언 시점이 없으므로 전역 컨텍스트의 outerEnvironmentReference에는 아무것도 담기지 않는다.

  • 1번째 줄과 2번째 줄: 전역 스코프에 있는 변수 a에 1을, outer에 함수를 할당한다.

  • 10번째 줄: outer 함수를 호출한다. 이에 따라 전역 컨텍스트의 코드는 10번째 줄에서 임시중단되고, outer 실행 컨텍스트가 활성화되어 2번째 줄로 이동한다.

  • 2번째 줄: outer 실행 컨텍스트의 environmentRecord에 { inner } 식별자를 저장한다. outerEnvironmentReference에는 outer 함수 선언될 당시의 LexicalEnvironment가 담긴다. outer 함수는 전역 공간에서 선언됐으므로 전역 컨텍스트의 LexicalEnvironment를 참조복사한다. 이를 [ GLOBAL, { a, outer } ] 라고 표기합니다. 첫 번째는 실행 컨텍스트의 이름, 두 번째는 environmentRecord 객체이다(this: 전역 객체).

  • 3번째 줄: outer 스코프에 있는 변수 inner에 함수를 할당한다.

  • 7번째 줄: inner 실행 함수를 호출합니다. 이에 따라 outer 실행 컨텍스트의 코드는 7번째 줄에서 임시중단되고, inner 실행 컨텍스트가 활성화되어 3번째 줄로 이동한다.

  • 3번째 줄: inner 실행 컨텍스트의 environmentRecord에 { a } 식별자를 저장한다.
    outerEnvironmentReference에는 inner 함수를 선언될 당시 LexicalEnvironment, 즉 { outer, { inner } }를 참조복사한다(this: 전역객체).

  • 3번째 줄: outer 스코프에 있는 변수 inner에 함수를 할당한다.

  • 7번째 줄: inner 함수를 호출한다. 이에 따라 outer 실행 컨텍스트의 코드는 7번째 줄에서 임시중단되고, inner 실행 컨텍스트가 활성화되어 3번째 줄로 이동한다.

  • 3번째 줄: inner 실행 컨텍스트의 environmentRecord에 { a } 식별자를 저장한다.
    outerEnvironmentRerence에는 inner 함수가 선언될 당시의 LexicalEnvironment가 담긴다. inner 함수는 outer 함수 내부에서 선언됐으므로 outer 함수의 LexicalEnvironment, 즉 { outer, { inner } }를 참조복사한다.

  • 4번째 줄: 식별자 a에 접근하고자 한다. 현재 활성화 상태인 inner 컨텍스트의 environmentRecord에서 a를 검색한다. a가 발견됐는데 여기에는 아직 할당이 된 값이 없다.

  • 5번째 줄: inner 스코프에 있는 변수 a에 3을 할당한다.

  • 6번째 줄: inner 함수 실행이 종료됩니다. inner 실행 컨텍스트가 콜 스택에서 제거되고, 바로 아래의 outer 실행 컨텍스트가 다시 활성화되면서, 앞서 중단했던 7번째 줄의 다음으로 이동한다.

  • 8번째 줄: 식별자 a에 접근하고자한다. 이때 자바스크립트 엔진은 활성화된 실행 컨텍스트의 LexicalEnvironment에 접근한다. 첫 요소의 environmentRecord에서 a가 있는지 찾아보고, 없으면 outerEnvironmentReference에 있는 environmentRecord로 넘어가는 식으로 계속하는 검색한다. 예제에서는 두 번째, 즉 전역 LexicalEnvironment에 a가 있으니 그 a에 저장된 값 1을 반환한다(1 출력).

  • 9번째 줄: outer 함수 실행이 종료된다. outer 실행 컨텍스트가 콜 스택에서 제거되고, 바로 아래의 전역 컨텍스트가 다시 활성화되면서, 앞서 중단했던 10번째 줄의 다음으로 이동

  • 11번째 줄: 식별자 a에 접근하고자 현재 활성화 상태인 전역 컨텍스트의 environmentRecord에서 a를 검색한다. 바로 a를 찾을 수 있다! 이로써 모든 코드의 실행이 완료된다. 후에 전역 컨텍스트가 콜 스택에서 제거되고 종료한다.

전체 윤곽을 바라보면 '전역 컨텍스트 -> outer 컨텍스트 -> inner 컨텍스트'순으로 점차 규모가 작아지는 반면 스코프 체인을 타고 접근 가능한 변수의 수는 늘어난다.

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

그러나 스코프 체인 상에 있는 변수라고 해서 무조건 접근 가능한 것은 아니다!
위 코드 상의 식별자 a는 전역 공간에서도 선언했고, inner 함수 내부에서도 선언했다. inner 함수 내부에서 a에 접근하려고 하면 무조건 스코프 체인 상의 첫 번째 인자, 즉 inner 스코프 LexicalEnvironment에 a 식별자가 존재하므로 스코프 체인 검색을 더 진행하지 않고, 즉시 inner LexicalEnvironment 상의 a를 반환하게 된다.
즉, inner 함수 내부에서 a 변수를 선언했기 때문에 전역 공간에서 동일한 이름의 a 변수에는 접근할 수 없는 셈이다.
이를 변수 은닉화라고 한다.

전역변수와 지역변수

위의 코드를 생각하며 전역변수와 지역변수의 의미를 알아보자

전역변수

위 코드엣 전역변수는 전역 스코프에서 선언한 a와 outer 둘이다.

지역변수

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

this

실행 컨텍스트의 thisBinding에서 this로 지정된 객체가 저장된다.
실행 컨텍스트 활성화 당시 this가 지정되지 않은 경우에는 전역 객체가 저장된
다.

그 밖에는 함수를 호출하는 방법에 따라 this에 저장되는 대상이 다르다.

마무리

실행 컨텍스트

  • 실행 컨텍스트는 실행할 코드에 제공활 환경 정보들을 모아 놓은 객체다.
  • 실행 컨텍스트는 전역 공간에서 자동으로 생성되는 전역 컨텍스트와 eval 및 함수 실행에 의한 컨텍스트 등이 있다.
  • 실행 컨텍스트는 객체는 활성화되는 시점에 VariableEnvironment, LexcalEnvironment, thisBinding의 세 가지 정보를 수집한다.
  • 실행 컨텍스트를 생성할 때는 variableEnvironment와 LexicalEnvironment가 동일한 내용으로 구성되지만, LexicalEnvironment는 함수 실행 도중에 변경되는 사항이 즉시 반영되는 반면 VariableEnvironment는 초기 상태를 유지한다.
  • VariableEnvironment는 매개변수명, 변수의 식별자, 선언한 함수의 함수명 등을 수집하는 EnvironmentRecord와 바로 직전 컨텍스트의 LexicalEnvironment 정보를 참조하는 outerEnvironmentReference로 구성돼 있다.

호이스팅

  • 호이스팅은 코드 해석을 좀 더 수월하게 하기 위해 environmentRecord의 수집 과정을 추상화한 개념이다.
  • 실행 컨텍스트가 관여하는 코드 집단의 최상단으로 이들을 '끌어올린다'고 해석하는 것이다.
  • 변수 선언과 값 할당이 동시에 이뤄진 문장은 '선언부'만을 호이스팅하고, 할당 과정은 원래 자리에 남아있게 되는데, 여기서 함수 선언문과 함수 표현식의 차이가 발생한다.

스코프

  • 스코프는 변수의 유효범위를 말한다.
  • outerEnvironmentReference는 해당 함수가 선언된 위치의 LexicalEnvironment를 참조한다.
  • 코드 상에서 어떤 변수에 접근하려고 하면 현재 컨텍스트의 LexicalEnvironment를 탐색해서 발견되면 그 값을 반환하고, 발견하지 못할 경우 다시 outerEnvironmentRecord에 담긴 LexicalEnvironment를 탐색하는 과정을 거친다.
  • 전역 컨텍스트의 LexicalEnvironment까지 탐색해도 해당 변수를 찾지 못하면 undefiend를 반환한다.

전역변수 / 지역변수

  • 전역 컨텍스트의 LexicalEnvironment에 담긴 변수를 전역변수라 하고, 그 밖의 함수에 의해 생성된 실행 컨텍스트의 변수들은 모두 지역변수이다.
    안전한 코드 구성을 위해 가급적 전역변수의 사용은 최소화 하자!
profile
Prof.Google을 통해 필요한 정보를 이 곳에 insert 🐸

0개의 댓글