[JS] Hoisting이란? (let vs var)

손규성·2022년 10월 23일
1

javascript

목록 보기
4/8
post-thumbnail

호이스팅 소개


호이스팅(Hositing)은 코드를 실행하기 전에 변수/함수 선언을 해당 스코프의 최상단으로 끌어올리는 것 같은 자바스크립트의 성질을 뜻하는 단어이다. 정확히 말하자면 실제로 변수/함수 선언을 최상단으로 끌어올리는 것은 아니고, 실제 코드를 수행하기 전 자바스크립트는 전체적인 코드 수행을 위한 memory 공간을 미리 설정하는데, 이때 선언된 변수 값들을 위한 memory 공간이 미리 할당되면서 선언문보다 참조/호출이 먼저 나와도 Error없이 코드가 수행되게 되는 것이다. (Java, C++, 등 타 언어들의 경우 선언문보다 참조/호출이 먼저 나오는 경우 바로 Error)

이러한 호이스팅 문제를 해결하기 위해 ES6 버전에서 let이라는 새로운 변수 선언문이 추가되었다. (덕분에 모든 자바스크립트 입문자에게 let과 var의 차이점에 대해 헷갈리게 해주는 현상도 같이 발생) var 대신 let을 통해 변수를 선언해도 호이스팅 현상은 똑같이 발생하게 된다. 다만, 호이스팅 현상이 발생하지 않는 것처럼 보이게끔 동작한다고 이해하는 것이 적절하다.


호이스팅과 var의 문제점


ES6 버전 전까지 사용되던 var는 여러 문제점을 가지고 있으며, 호이스팅 현상을 방지하기 위해 var를 사용하지 않는 것이 권장된다. 아래 예시들을 살펴보자.

💡Example 1:

//code 1 

console.log(a); //output: undefined
var a = 1;

//code 2 
var a; 
console.log(a); //output: undefined 
a = 1;
  • code 1에서 console.log(a)가 선언문보다 먼저 나오지만 에러없이 undefined가 출력된다. 앞서 언급했듯이 타 개발언어에서 선언문보다 참조/호출이 먼저 나오면 바로 에러처리될 확률이 높다.

  • var a라는 선언문이 Hoisting 되었기 때문이다. 이때 1이 할당되는 부분은 호이스팅되지 않기 때문에 자바스크립트는 a = undefined로 이해하고 console.log을 수행하게 된다.

  • Hoisting을 감안하면 code 1은 code 2처럼 동작한다고 보면 된다.

💡Example 2:

for(var i = 1; i < 5; i++) {
  console.log(i);
}

console.log(i);

/* output: 
1
2
3
4
5
*/
  • i 값은 for문 안에서 선언된 지역변수임에도 불구하고 for문 밖에서 console.log(i)를 통해 5까지 출력되는 것을 확인할 수 있다.

  • var는 function-scope이므로 함수 외의 블록에서 선언된 변수는 전역변수로 호이스팅 된다.

💡Example 3:

//code 1

var a = 1; 
console.log(a); //output: 1

var a = 2;
console.log(a); //output: 2


//code 2
var a = 10; 
console.log(a); //output: 10;

{
  var a = 20;
  console.log(a); //output: 20
}
console.log(a); //output: 20
  • 변수는 주민등록번호와 같으므로 변수가 겹치는 것은 불가능해야 정상이지만var의 경우 변수 재선언이 동일 블록에서 가능하다. code 1을 보면 var a = 1를 통해 a 변수를 선언하고 1이라는 값을 이미 할당했음에도 아래 var a = 2를 통해 a 라는 변수를 다시 선언할 수 있다.

  • 앞서 언급했듯이 var는 함수 외의 블록에서 선언된 경우 전역 변수로 호이스팅된다. 때문에 code 2 를 통해 확인할 수 있는 것처럼 a 변수를 새로운 블록 안에서 재선언했음에도, 블록 밖에서 다시 a를 호출하면 a는 새로운 값이 할당되어 있다.


let과 var의 차이점


let은 이러한 var의 단점을 보완한 상위호환 개념이라고 생각해도 무방하다.

letvar
블록 레벨 스코프 (block-scope)함수 레벨 스코프 (function-scope)
변수 재선언 불가변수 재선언 가능
Hoisting 발생하지 않는 것처럼 동작Hoisting 현상 발생

1. let은 block-scope이므로 함수 외의 블록에서 선언되어도 지역 변수로 남는다.

💡Example 1:

for (let i = 1; i < 4; i++) {
  console.log(i); //output: 1, 2, 3, 4
}

console.log(i); //output: ReferenceError: i is not defined
  • for문 블록 안에서 선언된 변수 i는 블록 밖에서 접근 불가능하다. (ReferenceError)

2. let은 동일 블록 내에서 변수 재선언이 안된다.

💡Example 2:

//code 1 
let a = 1; 
console.log(a);

let a = 2;
console.log(a);
// SyntaxError: Identifier 'a' has already been declared

//code 2
let a = 10; 
console.log(a); //output: 10
{
  let a = 20;
  console.log(a); //output: 20
}
console.log(a); //output: 10
  • code 1 → let을 통해 동일 블록에서 a 변수를 재선언하는 것은 불가능하다. (SyntaxError)

  • code 2 → 서로 다른 블록에서 let을 통해 선언된 동일 변수는 서로에게 영향을 주지 않는다.


3. let, 혹은 const를 사용한다고 JS Engine의 동작원리가 바뀌는 것이 아니라 Hoisting 현상이 일어나지 않는 것처럼 동작되게 만들어진 것이다.

  • let이나 const를 사용해도 JS Engine은 코드 실행 전 선언된 변수들을 인식하고, 이를 코드 실행을 위한 memory에 넣어두게 된다. 다만 이때 var를 통해 선언된 변수는 undefined 값으로 저장되고, letconst를 통해 선언된 변수들은 <uninitialized>로 저장된다.

💡변수 생성 과정

var만 변수 선언과 함께 undefined 값이 저장되는지 알기 위해서는 변수 생성 과정에 대한 이해가 필요하다. 변수는 3단계에 걸쳐 생성된다.

1. 선언 단계 (Declaration)

  • 변수를 실행 컨텍스트의 변수 객체에 등록하고, 이 변수 객체는 스코프가 참조하는 대상이 된다.

2. 초기화 단계 (Initialization)

  • 변수 객체에 등록된 변수를 위한 공간을 메모리에 확보하고, 변수는 undefined로 초기화 된다.

3. 할당 단계 (Assignment)

  • undefined로 초기화된 변수에 실제 값을 할당한다.

var 를 통해 변수를 선언하면 선언/초기화 단계가 동시에 진행된다. 때문에 선언문보다 호출이 먼저 나와도 이미 undefined로 초기화 되어 있는 변수를 호출하게 되는 것이다.

let 으로 선언된 변수는 두 단계가 따로 진행된다. 즉 호이스팅을 통해 JS Engine이 스코프에 맞게 변수를 미리 선언하는 것은 동일하지만, 선언문과 할당문에 도달해야만 다음 단계들이 진행된다. 선언은 되었지만 초기화 되기 전까지의 단계, 즉 스코프의 시작점부터 선언문 코드까지의 공간을 TDZ (Temporal Dead Zone)이라고 부른다.


References

profile
블로그 이사 → https://sqsung.tistory.com/

0개의 댓글