var x = 'global'
function foo() {
var x = 'local';
console.log(x); // local
}
foo()
console.log(x); // global
위 예제에서 출력 결과가 서로 다른 이유는 변수명(x
)은 같지만 스코프가 다른 별개의 변수를 호출하고 있기 때문이다.
console.log(x); // 1️⃣
var x = 10;
console.log(x); // 2️⃣
C, Java, Python과 같은 프로그래밍 언어를 공부해 본 사람이라면 이렇게 답할 것이다.
🙋🏻♂️ 소스코드는 위에서부터 한 줄씩 순차적으로 실행됩니다. 따라서
1️⃣
의 코드가 실행될 때에는 변수x
가 선언되어 있지 않으므로 에러가 발생할 것입니다
하지만, JavaScript에서는 1️⃣
의 결과로 undefined
가, 2️⃣
의 결과로 10이 출력된다.
이런 결과가 나오는 이유에 대해서 이해하려면 JavaScript 엔진에서 변수 선언이 동작하는 방식에 대해 이해할 필요가 있다.
JavaScript에서 변수 선언은 런타임(소스코드가 순차적으로 실행되는 시점) 이전에 실행되지만, 값의 할당은 런타임에 실행된다.
JavaScript 엔진은 소스코드를 한 줄씩 순차적으로 실행하기에 앞서, 먼저 소스코드의 평가 과정을 거치면서 소스코드를 실행하기 위한 준비를 한다. 이때 JavaScript 엔진은 변수 선언을 포함한 모든 선언문(변수 선언문, 함수 선언문 등)을 소스코드에서 찾아내 먼저 실행한다. 즉, JavaScript에서는 변수 선언 위치에 상관없이 어디에서든지 변수를 참조할 수 있다.
var
로 선언한 변수는 선언 즉시 undefined
로 초기화 된다는 특징이 있다. 따라서 소스코드를 순차적으로 실행하는 런타임 이전 단계에서 var
로 선언한 변수는 이미 선언되어 있으며 undefined
로 초기화 된 상태이다.
이렇듯 변수 선언문이 코드의 선두로 끌어 올려진 것처럼 동작하는 JavaScript 고유의 특징을 호이스팅(hoisting)이라 한다.
마지막으로 예제 하나만 더 살펴보도록 하자.
var x = 'global';
function foo() {
console.log(x); // 1️⃣ undefined
var x = 'local';
}
foo();
console.log(x); // 2️⃣ global
호이스팅은 스코프를 단위로 동작한다.
어떤 분들은 1️⃣
에서 global이 출력될 것이라 예상하는 사람들도 있을 것이다. 하지만, 그렇지 않다.
foo()
함수가 호출되면서 1️⃣
을 실행하기 이전에는 이미 var x = 'local';
에서 변수 x
의 선언과 함께 undefined
로 초기화가 진행된 상태이다. 그러므로 1️⃣
에서는 undefined
가 출력되는 것이다.
이로써 도입부에서 언급한 호이스팅의 의미를 모두 설명하였다.
도대체 자바스크립트 엔진은 이러한 과정들을 어떻게 처리하는지에 대해 궁금하다면 실행 컨텍스트(Execution Context)와 렉시컬 환경(Lexical Environment)에 대해 공부하는 것을 추천한다.
마지막으로 앞서 언급한 호이스팅의 정의를 다시 한 번 읽어보고 다음 내용으로 넘어가도록 하자.
호이스팅은 변수 선언이 스코프의 선두로 끌어 올려진 것처럼 동작하는 JavaScript 고유의 특징을 말한다.
변수는 var
, let
, const
키워드를 사용하여 선언할 수 있다.
이제, 지금까지 살펴본 스코프, 호이스팅을 참고하여 키워드 각각의 특징에 대해 알아보자.
var
로 선언한 변수는 같은 스코프 내에서 중복 선언이 가능하다.
var x = 1;
console.log(x); // 1
// var 키워드로 선언된 변수는 같은 스코프 내에서 중복 선언을 허용한다
var x = 100;
console.log(x); // 100
var
키워드로 선언한 변수는 오직 함수의 코드 블록만을 지역 스코프로 인정한다. 즉, if
나 for
문 안에서 선언한 변수도 전역변수가 된다.
/* Example1 */
if (true) {
var x = 10;
}
console.log(x); // 10
/* Example2 */
for (var i = 0; i < 3; i++) {
console.log(i); // 0 1 2
}
console.log(i); // 3
/* Example3 */
function foo() {
// x는 foo 함수 내부에서만 사용할 수 있는 변수이다
var x = 10;
console.log(x); // 10
}
foo();
console.log(x); // ReferenceError
이러한 var
의 함수 레벨 스코프는 의도치 않게 전역 변수를 선언/남발하게 되는 원인이 될 수 있다.
var
키워드로 선언한 변수는 호이스팅에 의해 변수 선언문이 스코프의 선두로 끌어 올려진 것처럼 동작한다. 단, 변수 할당문이 실행되기 이전에 변수를 참조하면 undefined
를 반환한다.
// 런타임 이전에 호이스팅에 의해 변수 x가 이미 선언되어 있다
// 이때 변수 x는 undefined로 초기화 되어 있다
console.log(x); // undefined
x = 10;
console.log(x); // 10
// 변수 선언은 JavaScript 엔진에 의해 런타임 이전에 실행된다
var x;
지금까지 var
의 특징에 대해 살펴보았다. 이제부터는 ES6에서 추가된 let
, const
의 특징에 대해 알아보자.
앞서 살펴본 var
과 달리 let
, const
는 같은 스코프 내에서 중복 선언을 금지한다.
var x = 1;
var x = 10; // 아무런 Error가 발생하지 않는다
let y = 2;
let y = 20; // SyntaxError: Identifier 'y' has already been declared
var
키워드로 선언한 변수는 함수 레벨 스코프를 따른다.
하지만 let
, const
키워드로 선언한 변수는 모든 코드 블록(함수, if문, for문, try/catch문 등)을 지역 스코프로 인정하는 블록 레벨 스코프를 따른다.
let x = 1;
if (true) {
let x = 10;
let y = 2;
console.log(x); // 10
console.log(y); // 2
}
console.log(x); // 1
console.log(y); // ReferenceError: y is not defined
console.log(x); // ReferenceError: x is not defined
let x = 1;
console.log(x); // 1
위 예제를 보면 let
, const
키워드로 선언한 변수는 호이스팅이 발생하지 않는 것처럼 보인다. 하지만, 그렇지 않다. let
, const
키워드로 선언한 변수도 호이스팅이 발생한다.
let x = 1;
if (true) {
console.log(x);
let x = 10;
}
위의 예제에서 console.log(x);
의 실행 결과는?
🙋🏻♀️ 전역 변수
x=1
이 선언된 상태이고let x = 10;
는 아직 실행되지 않았으므로console.log(x)
에서는 1이 출력될 것입니다.
틀렸다. 실행 결과 ReferenceError
가 발생한다. 이는 let
, const
키워드로 선언한 변수도 여전히 호이스팅이 발생하기 때문이다.
JavaScript에서의 변수 선언과 초기화 과정을 살펴보면서 이해해보자.
var x;
x = 1;
var
키워드로 선언한 변수는 런타임 이전에 JavaScript 엔진에 의해 암묵적으로 선언 단계와 초기화 단계가 동시에 진행된다. 즉, 선언 단계에서 변수 선언을 하는 즉시 초기화 단계에서 변수를 undefined
로 초기화한다.
let x;
x = 1;
let
, const
키워드로 선언한 변수는 선언 단계와 초기화 단계가 분리되어 진행된다. 즉, 런타임 이전에 JavaScript 엔진에 의해 암묵적으로 선언 단계가 진행되긴 하지만 초기화 단계는 변수 선언문에 도달했을 때 실행된다. 이때 스코프의 시작 지점부터 초기화 단계 전까지, 즉 변수를 참조할 수 없는 구간을 일시적 사각지대(TDZ)라고 한다.
위에서 보았던 예제를 다시 가져와봤다.
let x = 1;
if (true) {
console.log(x); // ReferenceError: Cannot access 'x' before initialization
let x = 10;
}
let
키워드로 선언한 변수인 x
가 호이스팅이 발생하지 않았다면 console.log(x);
는 1을 출력해야 한다. 하지만 여전히 호이스팅이 발생했기 때문에 에러가 발생하는 것이다.
정리해보자
JavaScript는 모든 선언(
var
,let
,const
,function
,class
등)을 호이스팅 한다.
이 중 ES6에서 도입된let
,const
,class
를 사용한 선언은 호이스팅이 발생하지 않는 것처럼 동작한다