[Javascript] 스코프 & 호이스팅
스코프에 대해 알아보기 전에, 자바스크립트는 왜이렇게 복잡할까 생각을 해봤다. var과 let, const는 참조할 수 있는게 다르다고? var는 이게 된다고? 싶은 것들이 많았다. 자 그래서 왜 그렇게 되는지 알아보기 위해서는 스코프, 호이스팅에 대해 이해할 필요가 있었다. 알아보자.
스코프란?
스코프란, 식별자가 유효한 범위를 말한다. 식별자를 검색할 때 사용하는 규칙을 말한다.
식별자란, 메모리에 있는 값을 참조할 수 있도록 사용하는 이름일 뿐이다. 변수, 함수, 클래스 등 여러 이름을 뜻한다. 코드 내부에서 이 이름이 겹치면 어떤 값을 가져와야 할 지 모른다. 따라서 우리는 스코프를 알아야 하는 것이다. 이 이름이 언제까지, 어디서 까지 사용이 가능한지 알기 위해서.
코딩을 좀만 하다보면 빨간색 밑줄이 뜨며 당황한 경험이 여럿 있을텐데 if문 안에서 변수를 선언해놓고 if문 밖에서 이를 사용하려고 했다거나 하는 경우가 이 스코프에 대한 실수라고 볼 수 있다.
- 식별자 결정(identifier resolution): 엔진은 어떤 변수를 참조해야 할 것인가를 결정하는 것
- 렉시컬 환경(lexical environment): 코드가 어디(전역, 지역, 함수 등)서 실행되며 주변에 어떤 코드가 있는지에 대한 환경. 코드의 문맥은 렉시컬 환경으로 이루어진다.
- 실행 컨텍스트(execution context): 렉시컬 환경을 구현한 것이 실행 컨텍스트이다. 즉, 실제 자료구조이다. (이는 다른 포스트에서 다룰 예정이다.) 모든 코드는 실행 컨텍스트에서 평가되고 실행된다.
- 식별자는 어떤 값을 구별해야 한다 → 유일해야 한다. → 따라서 식별자인 변수 이름은 중복될 수 없다. → 하나의 값은 유일한 식별자에 연결 되어야 한다.(name binding)
자바스크립트에서는 스코프에 대한 이해가 있어야 다른 응용 개념들을 이해할 수 있다. 따라서 하나씩 알아보자.
전역 스코프 (Global Scope)
- 전역이란 코드의 가장 바깥 영역을 말한다.
- 전역에 선언된 식별자는 어디서든 참조할 수 있다.
지역 스코프 (Local Scope)
- 전역 이외의 스코프. 다른말로 함수 몸체 내부를 말한다.
- 해당 스코프에서 선언된 식별자는 하위 스코프에서 모두 참조가 가능하다.
함수 레벨 스코프(Function Level Scope)
- 지역 스코프는 함수 몸체 내부를 말한다. 즉 함수 내부 어디서든 참조가 가능하다는 뜻이다.
var로 선언한 변수는 함수 레벨 스코프를 갖는다.
function foo() {
var a = 10;
if(a >= 10) {
var b = 20;
}
console.log(a, b);
}
foo(); // 10, 20
var b는 함수 레벨 스코프를 갖기 때문에 if문 내부에서 선언했다고 상관없다. 함수 몸체 내부에서는 모두 동일한 스코프로 인식 되는 것이고, if문 외부에서도 참조가 가능한 것이다. 20이 정상적으로 출력된다.
블록 레벨 스코프(Block Level Scope)
- 블록 레벨 스코프는 다른 언어들과 마찬가지이다.
if, for while, try/catch등으로 묶인 부분을 코드 블록이라고 말하고, 이들에 대해 독립적인 지역 스코프를 만드는 특성을 블록 레벨 스코프 라고 한다. - ES6의
let, const는 블록 레벨 스코프를 갖는다.
function foo() {
const a = 10;
if(a >= 10) {
const b = 20;
}
console.log(a, b);
}
foo(); // Error
function foo()블록에는 b 라는 식별자가 유효하지 않기 때문에 에러가 발생한다.var과 다른 스코프를 갖는 것을 알 수 있다.
렉시컬 스코프(Lexical Scope, Static Scope)
함수의 상위 스코프를 결정하는 방법에 따라 동적 스코프와 정적 스코프로 나뉜다.
- 동적 스코프: 함수를 어디서 호출 했는지에 따라 상위 스코프를 결정한다.
- 정적 스코프: 함수를 어디서 정의 했는지에 따라 상위 스코프를 결정한다.
var x = 1;
function foo() {
var x = 10;
bar();
}
function bar() {
console.log(x);
}
foo(); // ?
bar(); // ?
- 자바스크립트는 정적 스코프, 즉 렉시컬 스코프를 따른다. 따라서
function bar()의 상위 스코프는global이다. 따라서global에 선언되어있는var x = 1을 스코프 체이닝에 의해 참조하고 출력하게 된다. 즉, 두 물음표는 모두 1이다. foo() → bar()로 호출 되는 과정에서foo의 스코프는x값 참조에 아무런 영향을 주지 못한다.
스코프 체인(Scope Chain)
- 스코프 체인이란, 스코프가 계층적인 구조를 갖고 연결된 것을 말한다. 즉, 중첩함수와 같이 함수 안에 함수가 있고, 이는 스코프 안에 스코프가 있는 것이고 이게 계층적으로 연결된다는 뜻이다.
- 변수를 참조할 때 js 엔진은 변수를 참조하는 코드의 스코프에서 시작하여 상위 스코프 방향으로 이동하며 선언된 변수를 검색한다.
- 자바스크립트 엔진은 코드를 실행하기 전에 렉시컬 환경 “자료구조“를 물리적으로 생성 → 변수 선언이 실행되면 변수 식별자가 이 자료구조에 key로 등록되고, 변수 “할당“이 일어나면 이 자료구조의 변수 식별자에 해당하는 값을 변경한다.
호이스팅
호이스팅이란, 변수나 함수의 선언 단계가 먼저 실행되어 마치 해당 스코프의 최상단으로 코드를 옮겨져 동작하는 것처럼 보이는 것을 말한다.
- 호이스팅이라는 말이 끌어올림이라는 뜻이다. 무엇을 끌어올리는가? → 선언 단계
- 자바스크립트에서 엔진은 식별자를 선언 단계와 초기화 단계로 분리한다.
```jsx
function foo() {
console.log(a);
var a = 2;
}
foo();
```
이 코드에서 `console.log(a)`는 `undefined`이다. 호이스팅이란, 선언 단계만 끌어올린다. `var a; a = 2;` 이렇게 선언단계와 초기화 단계로 나뉘어지고, `var a;` 만 위로 끌어올려지는 것이다.
변수 호이스팅
- 위의 예시가 변수 호이스팅이다.
- let, const는 조금 특이하게 동작한다. 마치 변수 호이스팅이 발생하지 않는 것처럼 진행된다.
- var의 경우에는 엔진이 선언단계 → 초기화 단계(undefined)를 한번에 진행한다.
- let의 경우에는 선언단계만 먼저 실행된다. 따라서 렉시컬 환경에 선언을 했다는 정보는 있지만 초기화가 되어있지 않아서 ReferenceError를 반환한다.
- 할당 하기 전까지를 일시적 사각지대(Temporal Dead Zone, TDZ)라고 부른다.
- 그리고 let foo; 를 만나 초기화를 해야 그제서야 undefined로 초기화를 한다.
let foo = 1; { // 블록 레벨 스코프 console.log( foo ); let foo = 2; } - 변수 호이스팅이 되지 않는 것처럼 보일 수 있는데, 변수 호이스팅이 되지 않았다면 스코프 체인에 의해 1을 출력했어야 하는데 변수 호이스팅이 되기 때문에
ReferenceError를 반환한다.
함수 호이스팅
- 함수 선언문이 코드의 선두로 끌어올려진 것처럼 동작하는 특징을 함수 호이스팅이라고 한다.
- 모든 “선언문”은 런타임 이전에 엔진에 의해 실행된다. 고로 함수 선언문으로 함수를 정의하면 런타임 이전에 함수 객체가 먼저 생성된다.
- 변수 호이스팅은 undefined로 초기화 되지만, 함수 호이스팅은 함수 객체로 초기화 된다. 그래서 정상적으로 함수 내부의 로직이 수행이 가능하다. 따라서
function foo() { ... }는 어디에 위치해도 된다는 점. - 반면 함수 표현식은 변수만 호이스팅 된다. 함수는 “값”으로 런타임에 평가된다. 즉, 함수 표현식으로 코딩하면 변수 호이스팅이 발생한다.
클래스 호이스팅
- 클래스는 함수로 평가된다.
- 클래스 선언문으로 정의한 클래스는 런타임 이전에 평가되어 함수 객체를 생성한다. 다만 클래스는 클래스 정의 이전에 참조할 수 없다. 마치 let, const처럼.
- 이 때 생성된 함수 객체는 생성자 함수,
constructor로서 호출할 수 있는 함수이다. 이때 프로토타입도 더불어 생성된다.constructor와prototype은 동시에 존재하며 서로를 참조한다.