본문 바로가기

Languages/JavaScript

[JavaScript] Hoisting(호이스팅), 변수(var, let, const) 완벽 이해하기

728x90

   목차 

1. 변수의 생성과정 
2. 변수의 종류 
3. 변수의 특징 
4. 호이스팅 (Hoisting)
5. const와 let의 차이점 
6. var를 쓰면 안되는 이유

 

 

호이스팅(Hoisting)을 이해하기 위해서는 우선 변수에 대한 이해가 필요하다. 

변수의 생성과정과 종류 및 특징을 알아보자!

 

 

 


 

 

 변수의 생성과정 

 

변수의 생성 과정은 3단계로 이뤄진다.

1. 선언
2. 초기화
3. 할당

 

 

 1. 선언 

let message

 


 2. 초기화 

let message  // undefined

 


 3. 할당 

message = 'hello'  // hello

 

 

개념이 잘 안와닿을 수도 있어서, 쉬운 이해를 위해 비유를 들어보았다.

선언 ( 박스를 사왔다 )    ➔    초기화 ( 박스를 만든다 )        할당 ( 박스에 물건을 담는다 )

 

 


 

 

 변수의 종류 

 

변수의 종류는 3가지이다. 

1. var
2. let 
3. const

 

 


 

 

 변수의 특징 

 

각 변수 별로 생성과정을 알아보자! (매우 중요)

 

 var 

(1) 선언 및 초기화 
(2) 할당 



 let 

(1) 선언 : 호이스팅 때 실행
(2) 초기화 : 실제 코드에 도달했을때 실행
(3) 할당

 


 const 

(1) 선언 + 초기화 + 할당

 

 

-  var  : 선언과 초기화가 동시에 이뤄짐  → 언제든 사용 가능한 상태
(박스를 사와서 박스를 만드는 것까지 동시에 진행됨! 박스가 만들어져있으니 언제든지 사용 가능한 상태, 즉 물건을 담을 수 있는 상태)


-  let  : 실제 코드에 도달했을 때 초기화가 진행되고나서 할당이 된다. 만약 할당이 안되면 초기화된 상태(undefined)만 뜬다.
(박스를 사오긴했는데 안만들고 있다가, 물건 담을 때 박스를 만든다)

 

-  const  : 선언, 초기화, 할당이 한번에 진행된다.  → 따라서 한번 할당하면 재할당이 불가능하다. 
(박스를 사오고, 만들고, 물건까지 담고 뚜껑 닫아서 다시 다른 물건 못담아!)

 


 

 

 호이스팅(hoisting) 

 

JavaScript에서 호이스팅(hoisting)이란, 인터프리터가 변수와 함수의 메모리 공간을 선언 전에 미리 할당하는 것을 의미한다.

쉽게 말해, 변수가 끌어올려지는 현상을 '호이스팅'이라고 부른다. (hoist는 '끌어올리다' 라는 뜻이 있다.)

호이스팅을 설명할 땐 주로 "변수의 선언과 초기화를 분리한 후, 선언만 코드의 최상단으로 옮기는 것"으로 말하곤한다.

즉, JavaScript는 초기화를 제외한 선언만 호이스팅한다. (또한, 선언은 호이스팅 되지만 할당은 호이스팅 되지 않는다!)

변수를 먼저 사용하고 그 후에 선언 및 초기화가 나타나면,
사용하는 시점의 변수는 기본 초기화 상태(
var 선언 시 undefined, 그 외에는 초기화하지 않음)이다.

 

무슨 소리인지 헷갈린다면, 예시를 통해 차근차근 알아보자. 

 

 

 var의 호이스팅 

 

console.log(name); // undefined

var name = 'mike';

 

console.log를 통해 변수(name)를 먼저 사용하고, 그 이후에 var로 선언을 하였다.
실제로는 밑부분에 코드(var name)를 작성하였지만, 선언은 호이스팅이 되어서 코드 최상단부로 끌려올려진다.
(참고로, 이렇게 눈에는 보이지 않지만 말로 표현하는 상태를 렉시컬 환경(Lexical Environment), 즉 언어적 환경이라고 칭한다.)
var는 선언과 동시에 초기화가 이뤄지기 때문에, 선언이 호이스팅된 동시에 초기화까지 진행돼서 결국 undefined가 출력되는 것이다. 

 

 

위의 코드의 실행과정은 다음과 같다.* 자바스크립트는 인터프리터 언어기 때문에. 파싱 후 위에서 한줄씩 실행된다.

console.log(name); // undefined

var name = 'mike';

console.log(name); // mike
1. 전체 코드를 파싱한다.

2. var로 변수 선언 한 것이 호이스팅 된다. 동시에 초기화(undefined)가 진행된다. → 사용 가능한 상태가 된다. (값을 할당 가능)

3.  위에서부터 출력부(console.log)를 만나서 undefined가 출력된다. (아직 값이 할당이 안된 상태이기 때문에 초기화된 상태가 출력된다.)

4. 변수(name)에 mike 값을 할당한다.

5. 밑에 출력부(console.log)를 만나면 값이 할당 된 이후라서 mike가 출력된다.

 

 

 

함수 예제를 통해 더 깊이 살펴보자

 

function sayHi() {
  console.log(phrase);

  var phrase = "Hello";
}

sayHi();

 

'변수 선언'은 '함수 실행이 시작될 때' 처리되지만 (호이스팅도 이때 진행된다)
'할당'은 호이스팅 되지 않기 때문에 '할당 관련 코드에서' 처리된다.
따라서 위 예제는 아래 코드처럼 동작하게 된다.

 

function sayHi() {
  var phrase; // 선언은 함수 시작 시 처리된다.

  console.log(phrase); // undefined

  phrase = "Hello"; // 할당은 실행 흐름이 해당 코드에 도달했을 때 처리된다.
}

sayHi();
1. 전체 코드를 파싱한다.

2. var로 선언한 모든 변수는 함수의 최상위로 끌어 올려진다(hoisted). 동시에 초기화(undefined)가 진행된다. → 사용 가능한 상태가 된다. (값을 할당 가능)

3.  출력부(console.log)를 만나서 undefined가 출력된다. (아직 값이 할당이 안된 상태이기 때문에 초기화된 상태가 출력된다.)

4. 사용가능한 상태에서, 변수(phrase)에 "Hello" 값을 할당한다. (할당은 해당 코드에 도달했을 때 된다는 것을 잊지말자!)

 

 

 

 

 let과 const의 호이스팅 

 

let과 const도 호이스팅이 일어난다.

console.log(name);  // Uncaught ReferenceError: Cannot access 'name' before initialization

let name = 'mike'; 

console.log(name);  // mike

 

이렇게 출력부(console.log) 밑에 let을 선언하면 error가 나서 호이스팅이 안될거라고 생각하지만, 호이스팅은 일어난다.
다만 let과 const는 (var와 달리) 호이스팅이 되면 TDZ(Temporal Dead Zone, 시간상 사각지대)에 들어가게된다.
즉, let과 const로 선언된 변수는 변수 스코프의 맨 위에서 변수의 초기화 완료 시점까지 TDZ에 들어가게되는 것이다.

위의 코드의 실행과정은 다음과 같다.

1. 전체 코드를 파싱한다.

2. let으로 변수 선언 한 것이 호이스팅 된다. 동시에 TDZ로 들어간다. (변수 일시적 사망) → let 변수는 초기화하기 전에는 읽거나 쓸 수 없다.

3. 아직 초기화가 안된 상태에서 출력부(console.log)를 만난다.

4. TDZ에 의해서 let은 안보이게 된다. 에러가 발생한다.(초기화 하기 전에는 변수에 접근을 못한다는 에러가 뜸)

5. 이후 실제 코드에 도달해서, 초기화가 진행되고 할당이 된다.

6. 밑에 출력부(console.log)를 만나면 값이 할당 된 이후라서 mike가 출력된다.

 

 


 

 

 const와 let의 차이점 

 

const는 let과 똑같지만,
유일한 차이점이 있는데, const값의 할당을 동시에 해줘야하고 재할당이 불가하다는 점이다.

 

예시를 통해 살펴보자. 

 

 

 let은 할당을 동시에 안해줘도 된다. 

코드에 도달한 상태에서, 할당을 안해주면 초기화가 된 상태가 뜬다.

 

 

 const는 할당을 동시에 해줘야한다. 

코드에 도달하면, 초기화와 할당을 동시에 해야하는데 할당을 안해줘서 에러가 뜬다.

 

할당해주면 이렇게 잘 뜸

 

 

 let은 재할당이 가능하다. 

 

 

 const는 재할당이 불가능하다. 

 

 

이렇게 보면 let이 덜 까다롭고 자유도가 높다고 생각할 수도 있지만,
값의 재할당이 언제든지 가능하다는 소리는, 언제든지 값이 바뀔 수 있다는 불안요소가 있다는 것을 의미한다.
다른 사람들과 협업을 한다고 생각해보자.
내가  let 변수에 특정 값을 할당해뒀는데, 다른 사람이 1000줄 밑에 그 변수의 값을 재할당해서 바꿨다고 생각해보자. 상상만해도 끔찍하지않은가?

따라서 일단은 '무조건 변수 선언은 const로 한다'고 생각하고, const로 최대한 해결을 해보려고하자.
만약 도저히 let을 쓸 수 밖에 없는 상황이 생긴다면 (조건문 안에서 재할당이 꼭 이뤄져야하는 상황이 발생한다든지...) 그때 let을 사용하면 될 것이다.

 

 


 

 

 var을 쓰면 안되는 이유 

 

다음은 var를 쓰면 안되는 이유에 대해서 설명할 것이다.

 

 

 문제점1)  var는 중복 선언이 가능하다. 

 

var a = 123;
console.log(a); // 123
 
var a = 567;
console.log(a); // 567

분명 a가 두번 선언 되었는데 에러가 나지 않고 모두 잘 출력된다.
위에서 얘기했듯이 var은 호이스팅이 되자마자 초기화(사용가능한 상태)가 된 상태이기 때문에 계속 사용이 가능하다.
이것만 보더라도 협업시 굉장히 심각한 문제가 생길 수 있다.

 

 

 

문제점2) var는 블록스코프가 없다. (쉽게 말해, 방어막 형성을 안한다)

 

var로 선언한 변수의 스코프는 함수 스코프이거나 전역 스코프이다.
블록 기준으로 스코프가 생기지 않기 때문에 블록 밖에서 접근 가능하게된다.

 

if (true) {
  var test = true; // 'let' 대신 'var'를 사용했다.
}

alert(test); // true(if 문이 끝났어도 변수에 여전히 접근할 수 있음)

var는 코드 블록을 무시하기 때문에 test는 전역 변수가 된다. 전역 스코프에서 이 변수에 접근할 수 있는 것이다.

if (true) {
  let test = true; // 'let'으로 변수를 선언함
}

alert(test); // Error: test is not defined

두 번째 행에서 var test가 아닌 let test를 사용했다면, 변수 test는 if문 안에서만 접근할 수 있게 된다. 

 

for (var i = 0; i < 10; i++) {
  // ...
}

alert(i); // 10, 반복문이 종료되었지만 'i'는 전역 변수이므로 여전히 접근 가능하다.

반복문에서도 유사한 일이 일어난다. var는 블록이나 루프 수준의 스코프를 형성하지 않기 때문이다.

 

 

* 참고) var는 '함수스코프'는 못 뚫는다. 

function sayHi() {
  if (true) {
    var phrase = "Hello";
  }

  alert(phrase); // 제대로 출력된다.
}

sayHi();
alert(phrase); // Error: phrase is not defined  // 함수스코프 안에는 접근하지못해서 에러 발생

 

 

var만의 특성은 대부분의 상황에서 좋지 않은 부작용을 만들어낸다.
 
let이 표준에 도입된 이유가 바로 이런 부작용을 없애기 위해서다.
변수는 블록 레벨 스코프를 갖는 게 좋으므로 이제는 
let과 const를 이용해 변수를 선언하는 게 대세가 되었다.
하지만 더 상위 개념인 클로저를 이해하려면 var의 호이스팅 개념을 필수로 알고 있어야 한다.

 

 

 

 

참고자료 

https://ko.javascript.info/var

https://developer.mozilla.org/ko/docs/Glossary/Hoisting

https://developer.mozilla.org/ko/docs/Web/JavaScript/Reference/Statements/let

728x90