Skip to content

Latest commit

 

History

History
167 lines (101 loc) · 6.6 KB

scope.md

File metadata and controls

167 lines (101 loc) · 6.6 KB

스코프 (Scope)

정의

자바스크립트 엔진이 참조의 대상이 되는 식별자(Identifier)를 검색할 때 사용하는 규칙의 집합.

즉, 어떤 변수를 사용하거나 함수를 호출하려고 할 때 해당하는 식별자로 사용하는데,

그 식별자를 검색하는 메커니즘이라고 이해하면 된다.

어떤 경계 A의 외부에서 선언한 변수는 A의 외부 뿐 아니라 A의 내부에서도 접근이 가능하지만,

A의 내부에서 선언한 변수는 오직 A의 내부에서만 접근할 수 있다.

특이하게도, ES5까지의 자바스크립트는 전역 공간을 제외하면 오직 함수에 의해서만 스코프가 생성된다.

(ES6에서 도입된 let을 사용하면 블록 레벨 스코프를 사용할 수 있다)

자바스크립트에서 스코프를 구분해보면 다음과 같이 2가지로 나눌 수 있다.

**전역 스코프 (Global scope)**
코드 어디에서든지 참조할 수 있다.

**지역 스코프 (Local scope or Function-level scope)**
함수 코드 블록이 만든 스코프로 함수 자신과 하위 함수에서만 참조할 수 있다.

- 전역 스코프를 갖는 전역 변수는 코드 어디서든지에서 참조할 수 있다.
- 함수 내부에서 선언된 지역 변수는 그 지역과 그 지역의 하부 지역에서만 참조할 수 있다.

스코프 체인

현재 스코프에서 식별자를 검색할 때 상위 스코프를 연쇄적으로 찾아나가는 방식 을 말한다.

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

  • outerEnvironmentReference는 현재 호출된 함수가 선언될 당시의 LexicalEnvironment를 참조한다.

    (때문에 outerEnvironmentReference는 연결리스트 형태를 띈다)

  • 선언 시점의 LexicalEnvironment를 계속 찾아 올라가면

    마지막엔 전역 컨텍스트의 LexicalEnvironment가 있을 것이다.

  • 각 outerEnvironmentReference는 오직 자신이 선언된 시점의 LexicalEnvironment만 참조하므로

    가장 가까운 요소부터 차례대로만 접근할 수 있다.

이런 구조적 특성 덕분에 여러 스코프에서 동일한 식별자를 선언한 경우에는

무조건 스코프 체인 상에서 가장 먼저 발견된 식별자에만 접근 가능하게 된다.

다음과 같은 과정으로 스코프 체인을 검색한다.

  1. 현재 실행 컨텍스트의 LexicalEnvironment의 EnvironmentRecord에서 식별자를 검색한다.
  2. 없으면 outer 참조 값으로 스코프 체인을 타고 올라가 상위 스코프의 EnvironmentRecord에서 식별자를 검색한다.
  3. 이를 outer 참조 값이 null 일 때까지 계속하고 찾지 못한다면 undefined 를 반환한다.

렉시컬 스코프

코드를 짤 때 변수 및 함수/블록 스코프를 어디에 작성하였는가에 따라 정해지는 스코프를 렉시컬 스코프라고 한다.

(자바스크립트 컴파일러가 소스코드를 토큰으로 쪼개 의미를 부여하는 Lexing 단계에 해당 스코프가 확정된다)

즉, 변수 혹은 함수/블록이 어디에 써있는가를 보고 그 스코프를 판단하면 된다.

아래 예제의 실행 결과를 예측해보자.

var x = 1;

function foo() {
  var x = 10;
  bar();
}

function bar() {
  console.log(x);
}

foo(); // ?
bar(); // ?

위 예제의 실행 결과는 함수 bar의 상위 스코프가 무엇인지에 따라 결정된다.

두가지 패턴을 예측할 수 있는데 첫번째는 함수를 어디서 호출하였는지에 따라 상위 스코프를 결정하는 것이고

두번째는 함수를 어디서 선언하였는지에 따라 상위 스코프를 결정하는 것이다.

첫번째 방식으로 함수의 상위 스코프를 결정한다면 함수 bar의 상위 스코프는 함수 foo와 전역일 것이고,

두번째 방식으로 함수의 스코프를 결정한다면 함수 bar의 스코프는 전역일 것이다.

프로그래밍 언어는 이 두가지 방식 중 하나의 방식으로 함수의 상위 스코프를 결정한다.

첫번째 방식을 동적 스코프(Dynamic scope)라 하고,

두번째 방식을 렉시컬 스코프(Lexical scope) 또는 정적 스코프(Static scope)라 한다.

자바스크립트를 비롯한 대부분의 프로그래밍 언어는 렉시컬 스코프를 따른다.

렉시컬 스코프는 함수를 어디서 호출하는지가 아니라 어디에 선언하였는지에 따라 결정된다.

자바스크립트는 렉시컬 스코프를 따르므로 함수를 선언한 시점에 상위 스코프가 결정된다.

함수를 어디에서 호출하였는지는 스코프 결정에 아무런 의미를 주지 않는다.

위 예제의 함수 bar는 전역에 선언되었다.

따라서 함수 bar의 상위 스코프는 전역 스코프이고 위 예제는 전역 변수 x의 값 1을 두번 출력한다.

변수 은닉화

스코프 체인 상에 있는 변수라고 해서 무조건 접근 가능한 것은 아니다.

var a = 1;
var outer = function () {
	var inner = function () {
		console.log(a);
		var a = 3;
	};
	inner();
	console.log(a);
};
outer();
console.log(a);

위 코드상의 식별자 a는 전역 공간에서도 선언했고 inner 함수 내부에서도 선언했다.

inner 함수 내부에서 a에 접근하려고 하면 무조건 스코프 체인 상의 첫번째 인자,

즉 inner 스코프의 LexicalEnvironment부터 검색할 수밖에 없다. 그곳에 a 식별자가 존재하므로

스코프 체인 검색을 더 진행하지 않고 즉시 inner LexicalEnvironment 상의 a를 반환하게 된다.

즉 inner함수 내부에서 a를 선언했기 때문에 전역 공간에서 선언한 동일한 이름의 a변수에는 접근할 수 없다.

이를 변수 은닉화 라고 한다.

전역변수와 지역변수

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

코드의 안정성을 위해 가급적 전역변수 사용을 최소화 하는것이 좋다.

(전역 변수의 사용은 변수 이름이 중복될 수 있고,

의도치 않은 재할당에 의한 상태 변화로 코드를 예측하기 어렵게 만든다)

**전역변수 최소화 방법들**

* 즉시실행함수(IFE)활용
* 네입 스페이스
* 모듈 패턴
* 샌드박스 패턴 등
* 그 외 (AMD, CommonJS, ES6의 모듈 등)

참조