자바스크립트에서 변수를 선언하는 "var, let, const"의 차이점에 대해 알아보자!
먼저 알고 있는 가장 기본적인 차이점은 다음과 같다.
var | let | const | |
---|---|---|---|
재선언 | O | X | X |
재할당 | O | O | X |
이 외에도 3개의 변수 간에는 다른 차이점들이 존재한다.
hoisting과 scope인데, 이 둘이 var, let, const의 차이를 가르는 데 중요한 역할을 한다.
일단, Scope는 무엇일까?
'식별자에 접근하는 유효범위'
예? 뭐라구요..?
위의 정의를 풀어서 설명하면,
1. 식별자(변수, 함수, 클래스 등)을 말하는데, 여기에 접근할 수 있는 범위가 존재한다.
2. 범위는 중괄호(block)나 함수(function)에 의해 나뉜다
=> 결론적으로 중괄호나 함수에 의해 나눠진 유효 범위에서 접근할 수 있는 변수, 함수, 클래스를 Scope라고 한다.
- 안에선 바깥이 보이지만, 바깥에서는 안이 보이지 않는다.(like 썬팅)
- 바깥(block, function으로 나눠진 경계)에서 선언한 식별자는 안에서 사용 가능.
- 안에서 선언한 식별자는 바깥에서 알 수 없음.
- 스코프는 중첩이 가능하다.
- 전역 스코프와 지역 스코프
- 가장 바깥을 전역 스코프라고 부른다.
- 그 외에는 지역 스코프이다.
- 우선순위는 '지역변수 > 전역변수' 이다.
block scope는 중괄호에 의해 나눠진 유효 범위를 의미하고
functional scope는 함수에 의해 나눠진 유효 범위를 의미한다.
말은 알겠는데... 실제로는 어떤데 그래서..?
for(var i =0 또는 let i =0; i<10 ; i++) {
console.log(i);
}
// var 키워드로 선언하면
console.log(i); // 9
//let 키워드로 선언하면
console.log(i); // ReferenceError 참조에러
var의 유효범위는 함수 스코프만 따르고,
let과 const의 유효범위는 블록 스코프와, 함수 스코프를 모두 따른다.
for문에 var 또는 let이 블록 스코프 안에 선언되었는데 var는 참조가 가능하고 let은 참조가 불가하다.
이에 따라, 우리는 var, let, const의 차이를 알 수 있는 것이 하나 더 늘어났다.
var | let | const | |
---|---|---|---|
재선언 | O | X | X |
재할당 | O | O | X |
스코프 | 함수 | 블록 | 블록 |
hoisting은 끌어 올린다는 의미를 말한다. 무엇을 끌어 올리나..?
호이스팅은 코드를 실행하기 전에 '변수와 함수'선언을 해당 스코프의 최상단으로 끌어 올리는 것과 같은 현상을 말한다. 실제로 끌어 올리는 게 아니다.
호이스팅이라고 해서 선언을 해당 스코프의 최상단으로 끌어 올린다는 의미로 보이지만, 실제로는 그러한 것이 아니라 끌어 올려진 것 같은 현상을 말한다.
즈기요?? 끌어 올리는 것과 같다니... 무슨 말을 하시는 거에요..
- 자바스크립트 엔진은 작성한 코드를 실행하기 전에 코드들을 형상화한다.
- 자바스크립트 엔진은 실행을 위한 과정에서 선언(var, let, const, 함수 등)들을 스코프에 등록한다.
- 코드가 실행되기 전에 이미 변수와 함수선언이 스코프에 등록이 되어 있어 참조나 호출이 코드를 선언한 곳보다 먼저 나와도 참조가 가능하다.(정확하게 var키워드로 선언한 변수와 함수 선언문일 경우에 그렇다.) 명확하게는 실제로 끌어 올려진 게 아니라, 끌어올려진 것 처럼 보이는 것이다.
변수는 다음의 3단계를 거쳐 생성된다.
- 선언 단계
- 변수를 변수 객체에 등록한다.
- 이 변수 객체는 스코프가 참조하는 대상이 된다.(스코프에 등록)
- 초기화 단계
- 변수 객체에 등록된 변수를 위한 공간을 메모리에 확보한다.
- 이 단계에서 변수는 undefined로 초기화 된다.
- 할당 단계
- undefined로 초기화된 변수에 실제 값이 할당된다.
이 단계에서 var와 let, const가 차이가 발생하여 호이스팅으로 3가지 변수를 구분할 수 있다.
var는 선언과 초기화 단계가 동시에 진행된다. 스코프에 변수를 등록(선언 단계)하면서 메모리에 변수를 위한 공간을 확보하고 undefined로 초기화한다.
=> 변수를 선언하기 이전에 변수에 접근해도 스코프에 변수가 존재하고 이를 위한 메모리 공간이 확보되어 undefined로 초기화가 됐기 때문에 참조에러가 발생하지 않고 undefined를 반환한다. 그리고 변수를 할당한 지점(코드가 실행되는 지점)에 도달하면 그때 초기화된 undefined가 할당된 값으로 바뀐다.
let, const는 선언과 초기화 단계가 구분된다. 스코프에 변수를 등록(선언 단계)하긴 하지만 초기화 단계는 변수를 선언한 곳에 도달했을 때(코드 실행 후) 이뤄진다. 기본적으로, 초기화 이전에 변수에 접근하면 참조 에러가 발생하는데 아직 변수가 초기화 되지 않았다는 것은 변수를 위한 메모리 공간이 확보되지 않았다는 의미이기 때문이다.
즉, let, const의 경우 '스코프에 등록된 시점 ~ 초기화 시작 지점' 사이에는 변수 참조가 불가능하고 이것을 '일시적 사각지대'라고 부른다.
(초기화는 변수에 값이 할당되기 바로 직전에 진행된다.)
실제로 코드를 작성하고 console을 찍어보며 호이스팅에 공부하곤 한다.
console.log(test) // undefined
var test = "hi!";
console.log(test) // hi!
console.log(test) // Uncaught ReferenceError
let test = "hi!";
실제 코드를 테스트 해보면 위와 같은 결과를 얻을 수 있다.
var는 undefined이 반환되지만, let은 아예 참조가 어렵다는 에러가 반환된다.
우리는 이 테스트를 보고 생각할 수 있습니다.
var는 호이스팅이되고 let, const는 호이스팅이 되지 않구나!
하지만 실제로는 그렇지 않다. var, let, const 모두 변수의 선언단계 시점에서 호이스팅이 된다. 다만 위에 설명했던대로 var의 경우 초기화단계가 같이 실행이 되면서 메모리 공간이 확보되어 undefined이 반환되고, let과 const는 선언단계만 거치기 때문에 별도의 메모리 공간이 확보되지 않아 참조가 불가능한 것이다.
즉, var, let, const 모두 호이스팅이 되지만 undefined를 반환하는 var만 호이스팅이 되는 것 처럼 보이는 오해를 겪는 것이다.
console.log(X) // 1
function X() {
return 1;
}
함수도 선언하는 것이기 때문에, 호이스팅이 된다. 하지만 초기화단계를 거치지는 않는다.
console.log(X(2,1)) // Cannot access 'X' before initialzation
const X = (a, b) => {
return a + b;
}
X라는 식별자는 const로 선언되었다. var, let, const의 차이점에서 봤듯이 호이스팅은 되지만 const의 특성에 따라, 초기화 단계를 거치지 않아 에러가 발생한 것이다.
하지만 여기서 선언 키워드를 var로 바꿔도 동작하지 않는다. var로 선언하면 초기화 값으로 undefined가 되는데, undefined가 함수로 기능할 수 없기 때문이다.
var로 선언 시, not a function 에러가 나온다.