2015년, ES6에서 JavaScript를 보다 더 편리하게 사용할 수 있게 해주는 새로운 기능들이 많이 나왔다.
6년이 지난 2021년 현재, ES6은 이미 널리 사용되고 있어서 많은 JavaScript 개발자들이 ES6의 기법에 익숙해져 있는 상태다.
그 중,
- "변수를 선언할 때,
var
를 사용하지 마라"- "
const
와let
을 사용하라"
는 것이 정설로 퍼져있는데,
다시 한번 var
, const
, let
의 차이점에 되짚고 넘어가보자 한다.
- 기본적으로
const
를 사용하자🙆🏻♀️const
를 사용하지 못하는 경우(재할당이 필요한 경우)에는let
을 사용하자.var
는 절대 사용하지 말자🙅🏻♀️
JavaScript에서 변수를 사용하기 위해서는 먼저 선언(Declaration)을 해줘야 한다.
변수 선언(Declaration)이란, 변수를 사용하기 위해 x
라는 이름(식별자, Identifier)의 변수를 만들어주는 것.
var
, let
은 선언만 행할 수 있지만 const
의 경우 선언만 행하는 것이 불가능하다.
var x;
let score;
const person; // SyntaxError: Missing initializer in const declaration
변수 선언과 동시에, 값을 할당하는 것.
const
는 선언과 동시에 초기화가 동시에 이루어져야만 한다.
var x = 0;
let score = 80;
const job = "developer";
대입, 저장이라고도 표현한다. 변수에 값을 할당하는 것.
또는 이미 값이 저장되어 있는 변수에 다른 값을 할당하는 것.
const
는 상수, 변하지 않는 값이기 때문에 할당이 불가능하다.
var x = 0;
x = 1;
let score = 90;
score = 20;
const person = "John";
person = "Jason"; // TypeError: Assignment to constant variable.
var city = "Seoul";
console.log(city); // "Seoul"
var city = "Busan";
console.log(city); // "Busan"
var city = "Incheon";
console.log(city); // "Incheon
var
는 기존에 선언된 변수가 있어도 재선언이 가능하기 때문에,
이미 선언해놓은 변수에 또다른 값을 재할당해버리는 예상치 못한 실수를 범하기 쉬우며 재선언에 의한 디버깅을 힘들게 한다.
const sports = "soccer";
const sports = "baseball"; // SyntaxError: Identifier 'sports' has already been declared
let score = 80;
let score = 100; // SyntaxError: Identifier 'score' has already been declared
const
와 let
은 재선언이 불가능하다.
같은 변수명으로 또다시 재선언을 하게 되면 에러를 통해 그 변수는 이미 선언이 되어있음을 알려준다.
const
와 let
은 재선언이 가능하다는 var
의 위험을 사전에 차단시켜주고 있다는 것을 알 수 있다.
var city = "Seoul";
city = "Busan";
console.log(city); // "Busan"
let score = 50;
score = 100;
console.log(score); // 100
var
와 let
은 기존에 할당된 값과는 또다른 새로운 값을 재할당시키는 것이 가능하다.
const sports = "soccer";
sports = "baseball"; // Uncaught TypeError: Assignment to constant variable.
const
는 constant, 상수 그 자체이므로 처음에 const
키워드를 사용하여 변수 선언 및 초기화를 하고 난 뒤에는 새로운 값을 재할당할 수 없다.
함수 내부에서 선언된 변수는 그 함수 내부에서만 참조가 가능하다. 이를 function scope라고 한다.
var
는 function scope를 가지기 때문에,
var
로 선언된 변수는 오로지 함수의 code block({}
)만을 scope로 인정한다.
var
의 function scope를 증명하는 첫번째 예시를 보자.
function sayHello() {
var greetings = "Hello";
console.log(`sayHello says: ${greetings}`); // sayHello says: Hello
}
sayHello();
console.log(`I say: ${greetings}`); // ReferenceError: greetings is not defined
sayHello
함수 내부에서 var
로 선언된 변수 greetings
는,
함수 내부에서는 정상적으로 참조할 수 있지만 sayHello
함수 외부에서 변수 greetings
를 참조하면 에러가 발생한다.
다음 두번째 예시 코드를 보자.
if (true) {
var fruit = "grape";
}
console.log(fruit); // "grape";
함수가 아닌 영역(위 코드에서는 if)에서 var
를 이용해 선언한 변수는 global variable(전역 변수)로 간주한다.
따라서 if문의 외부에서 변수 fruit
을 참조한 결과, "grape"
가 출력된다.
위에서도 언급했듯이 var
는 함수의 code block({}
)만을 scope로 인정하기 때문이다.
function scope를 가진 var
와 달리, const
, let
는 block scope를 가진다.
if, switch, while, try/catch문 등은 block{}
로 감싸져있는데,
block scope란, 각 block{}
으로 감싸진 코드 범위를 말한다.
block scope 내에서 선언된 변수는 해당 block{}
내부에서만 유효하며, block{}
외부에서는 참조할 수 없는 지역 변수가 된다.
바로 예시 코드를 살펴보자.
function test() {
// if문도 하나의 block{}이다.
if (true) {
// block{} 내부에 block scope를 가진 const, let로 변수를 정의
const x = 1;
let y = 2;
}
// 같은 함수 내에 있더라도, 변수가 정의됐던 block(if문) 외부에서는 해당 변수를 참조할 수 없다.
console.log(x); // ReferenceError: x is not defined
console.log(y); // ReferenceError: y is not defined
}
test();
const
, let
로 변수 x
, y
를 선언했다.
하지만 const
, let
은 block scope가 적용되기 때문에
같은 함수 내에 있어도 변수가 정의됐던 block(if문)의 외부에서는 해당 변수를 참조할 수 없다.
또다른 예시를 보자.
if (true) {
const fruit = "grape";
let sports = "soccer";
console.log(fruit); // "grape"
console.log(sports); // "soccer"
}
console.log(fruit); // ReferenceError: fruit is not defined
console.log(sports); // ReferenceError: sports is not defined
이 예시는 var: Function scope에서 사용했던 예시에서
var
를 const
, let
으로 변경해준 것이다.
var
는 function scope를 가졌기 때문에 함수가 아닌 block은 무시되어
if문 외부에서도 변수를 참조할 수 있는 것에 반해,
const
, let
은 block scope를 가졌기 때문에 if문도 하나의 block으로 인정하여
block(if문) 내부에 선언한 변수는 block(if문) 외부에서는 사용할 수 없다는 것을 알 수 있다.
- 호이스팅을 변수 및 함수 선언이 물리적으로 작성한 코드의 상단으로 옮겨지는 것으로 가르치지만, 실제로는 그렇지 않습니다. 변수 및 함수 선언은 컴파일 단계에서 메모리에 저장되지만, 코드에서 입력한 위치와 정확히 일치한 곳에 있습니다.
- JavaScript는 초기화가 아닌 선언만 끌어올립니다(hoist)
- Hoisting - MDN
Hoisting이란, 단어 그대로 끌어올려지다(hoist)라는 의미를 가졌으며
scope 안의 어디서든 변수, 함수 선언을 하면 JavaScript 내부적으로 선언만을 끌어올려서 처리함으로써,
해당 scope 유효 범위의 최상단에 선언된 것과 동등해지는 현상이 발생되는 것을 말한다.
아래 코드에서는 var
를 이용하여 변수 fruit
를 선언하기 전에, console.log
로 변수 fruit
를 참조하려고 하고 있다.
console에는 어떤 결과가 나타날까?
function findFruit() {
console.log(fruit); // -> ????
var fruit = "orange";
}
findFruit();
상식적으로 생각하면 변수 선언 전에 변수를 참조하게 됐을 때는 에러가 발생될 것만 같다.
하지만 실제로는 에러가 아니라 결과값으로 undefined
가 나온다.
그 이유는, scope의 하단에 있는 fruit
라는 변수의 선언 부분이 해당 scope의 최상단으로 hoisting되었기 때문이다.
var
로 선언된 변수는 선언과 초기화가 동시에 이루어지면서 undefined
를 값으로 가지는데,
var
로 선언된 변수가 hoisting 되어 scope의 최상단에서 선언과 초기화가 동시에 이루어짐으로써 console.log
로 fruit
를 참조했을 때 undefined
라는 결과값이 나타난다.
위의 코드를 풀어서 쓰면 아래와 같다.
function findFruit() {
var fruit; // 변수 선언이 hoisting되어 scope 최상단에서 먼저 이루어짐
console.log(fruit); // var는 선언, 초기화와 동시에 값으로 undefined를 가짐
fruit = "orange"; // 값의 할당은 hoisting되지 않음
}
findFruit();
const
, let
또한 hoisting이 발생한다.
var
처럼 const
, let
으로 변수를 선언해도 scope의 최상단으로 변수의 선언이 hoisting 되기는 하지만,
선언하기 전에 먼저 const
나 let
으로 선언된 변수를 참조하려고 하면
var
와 달리 ReferenceError
가 발생된다.
const
와 let
에는 TDZ(Temporal Dead Zone)가 존재하기 때문이다.
TDZ란, 변수의 선언 단계와 초기화 단계의 사이에 존재하는 공간으로, 초기화되지 않은 변수에 access하는 것을 허용하지 않는 공간을 말한다.
이게 무슨 말일까?
console.log(animal); // ReferenceError: Cannot access 'animal' before initialization
const animal = "cat";
2행의 코드인 const animal = "cat"
에서 hoisting이 일어났다.
hoisting이 발생됐다는 증거는, Cannot access 'animal' before initialization
라는 에러가 발생했다는 점에 있다.
hoisting이 발생되지 않았다면, animal is not defined
와 같은 에러가 발생했을 것이다.
먼저 animal
이라는 const
변수의 선언은 hoisting에 의해 최상단으로 끌어올려졌다.
하지만, const
의 경우 선언만 하는 것이 불가능하고 선언과 동시에 초기화를 행해줘야하기 때문에 ReferenceError: Cannot access 'animal' before initialization
라는 에러가 발생한 것이다.
즉, 초기화를 행하지 않은 상태의 const
로 선언된 변수가 hoisting 되면서 TDZ에 빠지게 되어 나타난 에러인 것이다.
앞서 var
의 hoisting 부분에서 언급했듯, var
는 선언과 초기화가 동시에 이루어지면서 undefined
를 값으로 가지는 반면,
const
와 let
은 그렇지 않다.
TDZ는 초기화를 행하지 않은 변수의 참조를 허용하지 않는 공간이다.
var
와는 달리 const
와 let
은 선언과 초기화가 동시에 이루어지지 않기 때문에, const
와 let
으로 선언된 변수가 hoisting 되어 버리면 TDZ에 빠져버리게 되는 것이다.
함수 외부에서 var
로 선언된 변수는 global object(전역 객체. window object)의 property가 되어, global variable(전역 변수)가 된다.
따라서 프로그램 어디서든 access할 수 있게 된다.
var apple = "red";
console.log(window.apple) // "red";
var
로 선언된 변수 apple
는 전역 객체인 window
의 property로 할당된다.
let apple = "red";
console.log(window.apple) // undefined
var
와는 달리, const
, let
으로 선언된 변수는 window
의 property로 할당되지 않았음을 알 수 있다.
- 재선언 가능🙆🏻♀️
- 재할당 가능🙆🏻♀️
- Function scope
- Hoisting이 발생한다.
- window 전역 객체의 property이다🙆🏻♀️
- 재선언 불가능🙅🏻♀️
- 재할당 가능🙆🏻♀️
- Block scope
- Hoisting이 발생하지만, TDZ가 존재한다.
- window 전역 객체의 property가 아니다🙅🏻♀️
- 재선언 불가능🙅🏻♀️
- 재할당 불가능🙅🏻♀️
- Block scope
- Hoisting이 발생하지만, TDZ가 존재한다.
- window 전역 객체의 property가 아니다🙅🏻♀️
- 재선언을 가능하게 하기 때문에, 같은 이름의 변수를 여러 번 생성 가능하게 한다.
- 재선언, 재할당을 가능하게 하기 때문에 의도치 않게 변수와 값을 바꿔버릴 가능성이 크다. → 에러의 추적과 디버깅을 어렵게 함.
- Function scope이기 때문에, 함수 안이라면 어디서든지 참조가 가능해버리며, 함수 내의 다른 block(if, switch, try/catch 등)에 선언된 값도 참조해버리기 때문에 예상치 못한 값을 도출해버린다.
- var는 어느 위치에 변수를 선언하더라도 제일 최상단으로 hoisting 되어버린다.
- ...등등
- 기본적으로
const
를 사용하자🙆🏻♀️const
를 사용하지 못하는 경우(재할당이 필요한 경우)에는let
을 사용하자.var
는 절대 사용하지 말자🙅🏻♀️
잘못된 정보가 있다면 마구마구 지적 부탁드립니다🙇♀️