[JavaScript] 스코프, 호이스팅

wo_ogie·2021년 8월 14일
10

스코프(Scope)


  • 변수 이름, 함수 이름, 클래스 이름 등 모든 식별자는 자신이 선언된 위치에 의해 참조할 수 있는 유효 범위가 결정된다. 이러한 유효 범위를 스코프라 한다.
  • 스코프는 전역(global)과 지역(local)으로 구분할 수 있으며, 변수는 자신이 선언된 위치에 의해 스코프가 결정된다.
var x = 'global'

function foo() {
  var x = 'local';
  console.log(x); // local
}

foo()
console.log(x);	// global

위 예제에서 출력 결과가 서로 다른 이유는 변수명(x)은 같지만 스코프가 다른 별개의 변수를 호출하고 있기 때문이다.



호이스팅(Hoisting)


  • 호이스팅은 변수 선언이 스코프의 선두로 끌어 올려진 것처럼 동작하는 JavaScript 고유의 특징을 말한다.

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

1. 변수 중복 선언 허용

var로 선언한 변수는 같은 스코프 내에서 중복 선언이 가능하다.

var x = 1;
console.log(x);	// 1

// var 키워드로 선언된 변수는 같은 스코프 내에서 중복 선언을 허용한다
var x = 100;
console.log(x);	// 100

2. 함수 레벨 스코프

var 키워드로 선언한 변수는 오직 함수의 코드 블록만을 지역 스코프로 인정한다. 즉, iffor문 안에서 선언한 변수도 전역변수가 된다.

/* 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의 함수 레벨 스코프는 의도치 않게 전역 변수를 선언/남발하게 되는 원인이 될 수 있다.


3. 변수 호이스팅

var 키워드로 선언한 변수는 호이스팅에 의해 변수 선언문이 스코프의 선두로 끌어 올려진 것처럼 동작한다. 단, 변수 할당문이 실행되기 이전에 변수를 참조하면 undefined를 반환한다.

// 런타임 이전에 호이스팅에 의해 변수 x가 이미 선언되어 있다
// 이때 변수 x는 undefined로 초기화 되어 있다

console.log(x);	// undefined

x = 10;

console.log(x); // 10

//  변수 선언은 JavaScript 엔진에 의해 런타임 이전에 실행된다
var x;

지금까지 var의 특징에 대해 살펴보았다. 이제부터는 ES6에서 추가된 let, const의 특징에 대해 알아보자.


let, const

1. 변수 중복 선언 금지

앞서 살펴본 var과 달리 let, const는 같은 스코프 내에서 중복 선언을 금지한다.

var x = 1;
var x = 10; // 아무런 Error가 발생하지 않는다

let y = 2;
let y = 20; // SyntaxError: Identifier 'y' has already been declared

2. 블록 레벨 스코프

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

3. 변수 호이스팅

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 변수 선언 및 초기화 과정

var x;
x = 1;

var 변수 선언 및 초기화 과정

var 키워드로 선언한 변수는 런타임 이전에 JavaScript 엔진에 의해 암묵적으로 선언 단계초기화 단계가 동시에 진행된다. 즉, 선언 단계에서 변수 선언을 하는 즉시 초기화 단계에서 변수를 undefined로 초기화한다.


let, const 변수 선언 및 초기화 과정

let x;
x = 1;

let, const 변수 선언 및 초기화 과정

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를 사용한 선언은 호이스팅이 발생하지 않는 것처럼 동작한다

0개의 댓글