var x = 'global';
function foo () {
var x = 'function scope';
console.log(x);
}
foo(); // ?
console.log(x); // ?
이름이 같은 변수 x
가 중복 선언되었다. 전역에서 변수 x
를 참조할 때와 함수 foo()
내부에서 변수 x
를 참조할 때 이름이 중복된 2개의 변수 중 어떤 변수를 참조해야 할까?
스코프는 참조 대상 식별자를 찾아내기 위한 규칙이다. 식별자는 자신이 어디에서 선언됐는지에 의해 자신이 유효한 범위를 갖는다.
위 코드에서 전역에 선언된 변수 x
는 어디에든 참조할 수 있다. 하지만 함수 foo
내에서 선언된 변수 x
는 함수 foo
내부에서만 참조할 수 있고 함수 외부에서는 참조할 수 없다.
이러한 규칙을 스코프라고 한다.
스코프가 없다면 같은 식별자 이름은 충돌을 일으키므로 프로그램 전체에서 하나밖에 사용할 수 없다.
자바스크립트에서 스코프는 2가지로 나뉜다.
전역 스코프
코드 어디에서든 참조할 수 있다.
지역 스코프
함수 코드 블록이 만든 스코프로 함수 자신과 하위 함수에서만 참조할 수 있다.
변수의 관점에서 스코프를 구분하면 아래의 2가지로 나뉜다.
전역 변수
전역에서 선언된 변수이며 어디에든 참조할 수 있다.
지역 변수
지역(함수) 내에서 선언된 변수이며 그 지역과 그 지역의 하부 지역에서만 참조할 수 있다.
자바스크립트의 스코프는 타 언어와 달리 '함수 레벨 스코프'를 따른다. 함수 레벨 스코프란 함수 코드 블록 내에서 선언된 변수는 함수 코드 블록 내에서만 유효하고 함수 외부에서는 유효하지 않다는 것이다.
단, ECMAScript6에서 도입된 let
을 사용하면 자바스크립트에서도 블록 레벨 스코프를 사용할 수 있다.
var x = 0;
{
var x = 1;
console.log(x); // 1
}
console.log(x); // 1
let y = 0;
{
let y = 1;
console.log(y); // 1
}
console.log(y); // 0
자바스크립트는 let
, const
를 포함하여 모든 선언을 호이스팅한다. 호이스팅이란, var
선언문이나 function
선언문 등을 해당 스코프의 선두로 옮긴 것처럼 동작하는 특성을 말한다.
하지만 var키워드로 선언된 변수와는 달리 let 키워드로 선언된 변수를 선언문 이전에 참조하면 참조 에러가 발생한다. 이는 let 키워드로 선언된 변수는 스코프의 시작에서 변수의 선언까지 일시적 사각지대(Temporal Dead Zone; TDZ) 에 빠지기 때문이다.
var 키워드로 선언된 변수는 선언단계와 초기화 단계가 한번에 이루어진다. 따라서 변수 선언문 이전에 변수에 접근하더라고 스코프에 변수가 존재하기 때문에 에러가 발생하지 않는다. 다만 undefined를 반환한다.
// 스코프의 선두에서 선언 단계와 초기화 단계가 실행된다.
// 따라서 변수 선언문 이전에 변수를 참조할 수 있다.
console.log(foo); // undefined
var foo;
console.log(foo); // undefined
foo = 1; // 할당문에서 할당 단계가 실행된다.
console.log(foo); // 1
let 키워드로 선언된 변수는 선언 단계와 초기화 단계가 분리되어 진행된다. 초기화 단계는 변수 선언문에 도달해야 이루어진다. 초기화 이전에 변수에 접근하려고 하면 참조 에러가 발생한다. 이 시기에는 메모리 공간이 아직 확보되지 않았기 때문이다. 스코프의 시작 지점부터 초기화 시작 지점까지의 구간을 '일시적 사각지대'라고 부른다.
// 스코프의 선두에서 선언 단계가 실행된다.
// 아직 변수가 초기화(메모리 공간 확보와 undefined로 초기화)되지 않았다.
// 따라서 변수 선언문 이전에 변수를 참조할 수 없다.
console.log(foo); // ReferenceError: foo is not defined
let foo; // 변수 선언문에서 초기화 단계가 실행된다.
console.log(foo); // undefined
foo = 1; // 할당문에서 할당 단계가 실행된다.
console.log(foo); // 1