Language/Javascript

Javascript - var, let, const의 차이점 + 호이스팅

둉이 2021. 7. 24. 18:15

211203에 호이스팅 관련 설명을 보충했습니다!

 


 

자바스크립트에서는 ES6 문법부터 var를 이용한 변수 선언을 권장하지 않고 있다.

 

메모리 누수 문제 때문이라고만 알고 정확한 이유는 모른 채 const와 let을 사용하고 있었는데, 오늘 정확한 이유와 차이점 등을 정리해 보려고 한다.

 

var, let, const의 차이는 변수 재선언 가능 여부와 변수 재할당 가능 여부에 있다.

 

변수 재선언이란?
같은 변수명으로 다시 변수를 선언하는 것
변수 재할당이란?
선언된 변수에 값을 다시 할당하는 것

 

- var : 변수 재선언 가능 → 예기치 못한 값을 반환할 수 있음

var t = 3
console.log(t)  // 3 출력
var t = 'hello'
console.log(t)  // hello 출력
t = 3.14
console.log(t)  // 3.14 출력

 

- let : 변수 재선언 불가, 변수 재할당 가능

let t = 3
console.log(t)  // 3 출력
let t = 'hello'
console.log(t)  // 오류 발생(재선언 불가)
t = 'hello'

console.log(t)  // hello 출력

 

- const : 변수 재선언, 재할당 불가 → 상수처럼 사용, 선언과 동시에 초기화가 이루어져야 함

 

 

 

그리고 var과 let, const는 호이스팅에서도 차이점이 있다.

 

호이스팅이란?
변수 및 함수의 선언부를 코드 최상단으로 끌어올리는 것을 의미

→ 함수 내에서 선언한 변수는 해당 함수의 최상단으로, 함수 밖에서 선언한 전역 변수는 스크립트의 최상단으로 끌어올림

 

console.log(hee)  // undefined 출력
console.log(foo)  // 오류 발생
console.log(bar)  // 오류 발생

var hee = 123
let foo = 789
const bar = 456

이러한 호이스팅으로 인해 위 코드처럼 var 변수가 선언되기 전에 변수를 호출하면 undefined를 반환한다.

 

하지만 let과 const로 선언된 변수는 Reference Error가 발생한다.

그럼 let과 const로 선언된 변수들은 호이스팅이 안되는 것일까?

 

정답은 NO!

 

let과 const도 호이스팅의 대상은 맞다.

var과의 차이를 보이는 이유는 var 변수와 let/const 변수의 메모리 할당 방식에 차이가 있기 때문이다.

 

변수의 선언 - 초기화 - 할당 순서

 

변수는 선언 - 초기화 - 할당의 3단계를 걸쳐서 메모리에 적재된다.

 

- 선언

변수명을 등록하여 자바스크립트 엔진에 변수의 존재를 알림

 

- 초기화

값을 저장하기 위한 메모리 공간을 확보하고 암묵적으로 undefined를 할당하여 초기화

 

- 할당

= 연산자를 사용하여 값을 할당한 경우, 해당 변수의 데이터 영역에 값을 할당

 

 

var은 선언과 초기화 단계가 동시에 이루어진다.

 

하지만 let과 const는 선언과 초기화 단계가 따로 이루어진다.

 

그렇기 때문에 변수 선언부는 호이스팅되지만 초기화는 할당 단계에서 이루어지므로 스코프의 시작 시점부터 변수 할당 이전(초기화) 단계 전까지는 변수에 접근할 수 없다.

 

왜냐? 메모리 공간에 변수를 위한 공간이 할당되지 않은 상태이니까!

 

이러한 선언과 초기화 단계 사이의 일시적인 시점을 일시적 사각지대(TDZ, Temporal Dead Zone)라고 한다.

 

 

 

변수 뿐만 아니라 함수에서도 호이스팅은 일어난다!

 

일단, 함수는 함수 표현식과 함수 선언문으로 나뉜다.

 

함수 선언문은 다음과 같은 일반적인 프로그래밍 언어에서 주로 사용되는 형태를 갖고 있다.

function 함수명() { ... }

 

함수 표현식은 변수에 함수 자체를 할당하는 방식으로, 다음과 같은 형태를 갖는다.

const 함수명 = () => { ... }
// 또는
const 함수명 = function () { ... }

 

함수 선언문의 경우에는 함수 전체가 호이스팅의 대상이다.

 

따라서, 함수 선언문이 정의되기 전에도 해당 함수를 호출할 수 있다.

 

하지만 함수 표현식은 변수에 값을 할당하는 방식이므로 변수 선언부에서 먼저 호이스팅이 일어나고 초기화 및 할당은 해당 실행 흐름에 도달했을 때 일어난다.

 

따라서, 함수 표현식으로 함수를 선언한 경우에는 함수가 정의되기 전에는 해당 함수를 호출할 수 없다.

 

 

그럼 함수 선언문이 좋은걸까 아니면 함수 표현식이 좋은걸까?

 

함수 선언문은 변수에 함수를 할당하므로 동일한 변수명을 갖는 서로 다른 값이나 함수를 덮어씌워 의도하지 않은 코드 동작을 야기할 수 있다.

 

게다가 에러를 발생시키지도 않으므로 디버깅이 어렵다.

console.log(sum(3, 4));  // 호이스팅으로 인해 오류가 발생하지 않음, '3+4=7' 반환

function sum(x, y) { return x + y; }

var a = sum(1, 2);
function sum(x, y) { return x + '+' + y + '=' + (x + y); }
console.log(a);  // '1+2=3' 반환

var c = sum(1, 2);
console.log(c);  // '1+2=3' 반환

→ 결론: 함수 표현식이 안전하다!

 

 

그럼 화살표 함수 사용 vs function 키워드를 사용하여 함수를 선언하는 방식이 호이스팅과 관련이 있을까?

 

이것도 NO!

 

함수 선언문 vs 함수 표현식 차이만 호이스팅에 영향을 준다.

 

 

 

그 외 근본적인 차이는 무엇일까?

 

결론부터 말하자면, var는 function-scoped이고 let과 const는 block-scoped이다.

 

이게 뭔 소리냐?

 

function-scoped는 말 그대로 선언된 함수를 기준으로 유효 범위를 인정한다.

 

var 키워드로 선언된 변수는 함수 내에서만 유효하며, 함수 외부에서 선언된 경우에는 전역 변수로 취급한다. (호이스팅)

 

그로 인해 재할당이 발생하거나 어딘가에 이름이 같은 변수가 존재한다면 결과값이 꼬일 수 있는 위험이 있다. 

(따라서 변수의 스코프는 최대한 좁게 만드는 것 권장)

 

 

block-scoped는 { }(블록)을 기준으로 유효 범위를 인정한다.

 

아래 코드에서 let, const로 선언된 변수가 최상단으로 호이스팅되지 않는 이유도 let과 const 변수는 block-scoped이므로 블록 안에서 선언된 변수들은 해당 블록의 지역 변수로 처리되기 때문이다.

 

아래 예시처럼 { } 안에 선언된 변수는 지역 변수이므로 { } 밖에서 호출할 경우 오류가 발생하거나 블록 밖의 값을 반환한다.

let foo = 123  // 전역 변수

{
  var hee = 789  // var은 함수 스코프
  let foo = 456  // 지역 변수
  const bar = 456  // 지역 변수
}

console.log(hee)  // 789 출력
console.log(foo)  // 123 출력
console.log(bar)  // 오류 발생