ES6가 나온 지가 벌써 6년이 지났습니다.
그런데 코드리뷰를 하다보면 아직도 var로 변수를 할당하는 분들이 자주 보입니다.
물론 짤막하게 동작을 보여주기 위해서 코드를 작성하는 수준에서는 크게 문제될 게 없겠지만
그 코드를 보는 사람들 중 누군가는 그게 정설이라고 생각하고 개발을 시작하게 될지도 모릅니다.
그래서 변수 선언 방식에 대해서 피드백을 드리면, 물론 대부분은 수정해주시지만 왜 굳이 그렇게 써야 하냐고 되묻는 분들이 이따금씩 계셨습니다.
이미 이것에 대한 자료들이 인터넷 여기저기에 차고 넘치는데도요.
그래서 썼습니다. 왜 var를 버리고 const와 let을 사용해야 하는지!하..
let? const?
일단 둘의 차이점을 먼저 언급하고 넘어가는 게 좋을 것 같습니다.
아주 간단히 설명해서 const는 상수선언으로 변경이 불가능하고, let은 변수선언으로 변경이 가능합니다.
따라서 원시자료형을 다룰 때는 변경되지 않을 값이라면 const로, 나중에 값을 변경할 경우라면 let으로 선언하면 되겠죠?
/**
* 이렇게 선언해두면 선언만 보더라도 이게 나중에 변경될 값인지 절대로 변경하지 않을 값인지 한 눈에 알아볼 수 있습니다.
* 불필요하게 주석을 적을 필요도 없겠죠. 만약 var였다면 어땠을까요?
*/
const name = 'luke'
let age = 20
let company = null
만약 function이나 object, array 등의 참조형을 변수에 할당할 경우라면 const를 사용하는 것이 좋겠습니다.
참조형의 경우에는 const로 선언되었다고 하더라도 멤버값은 조작할 수 있거든요.
const test = () => {
const foo = [1, 2];
foo.push(3);
console.log(foo); // [1, 2, 3]
}
HOISTING
일단 호이스팅이 뭔지 모르는 분도 계실 것 같아서 짧게 설명하고 넘어가자면, Javascript는 함수가 호출되기 전에 한번 내부를 쭉 훑어봐서 필요한 변수들을 최상단으로 끌어올립니다. 그래서 이런 말도 안되는 일이 생기기도 해요. 다음 코드가 어떻게 동작할 지 생각해보세요.
function test() {
console.log(sayHi)
if (true) {
var sayHi = 'Hello World'
}
console.log(sayHi)
}
일반적인 상식이라면 두개의 console.log 모두에서 sayHi is not defined!라는 에러를 뱉기를 기대할 겁니다. 근데 우리의 Hoisting은 놀랍게도 첫번째 console.log에서는 undefined를, 두번째 console.log에서는 Hello World를 정상적으로 출력해줍니다.
더욱 놀라운 것은 이러한 Hoisting은 함수선언식에서도 똑같이 동작한다는 거예요. 선언식이란 위의 예시코드처럼 function 키워드를 사용하는 것을 말합니다. 다른 하나는 함수표현식이 있는데, 이건 const, let에 변수를 선언하듯이 함수를 표현하는 걸 말합니다.
function test() { ... } // 함수선언식
const test = () => { ... } // 함수표현식
그래서 예시에 있는 test()는 선언되기 전에 실행해도 문제없이 작동을 합니다. 맞아요, 이상하죠?
그렇기 때문에 Hoisting은 보통 버그를 유발하고는 합니다.
선언중복?
보통 변수명은 겹쳐서 사용할 수 없다는 건 언어를 막론하고 하나의 상식입니다. 근데 var는요, 이게 됩니다.
// const의 경우
const name = 'luke'
const name = 'tofu' // Identifier 'name' has already been declared
// var의 경우
var name = 'luke'
var name = 'tofu'
console.log(name) // tofu
사실 다른 것 다 제쳐놓고 이것만 봐도 var를 사용하지 않아야 할 이유는 충분하다고 생각합니다.
유효범위
var는 함수단위, let/const는 Block 단위 입니다. 이런 식으로 동작하는 언어는 제가 알기로는 var를 사용하는 Javascript 밖에 없을 거예요.
function test() {
var foo = '1';
console.log(foo); // 1
if (true) {
var foo = '2';
console.log(foo); // 2
};
console.log(foo); // 2
}
보시다시피 if문 안에서 변경된 변수가 if문 바깥에서도 동일하게 나타나는 것을 볼 수 있어요.
Hoisting의 문제인지 유효범위의 문제인지를 명확하게 하기 위해서 일부러 if문 바깥에서도 값을 할당해주었습니다.
그러면 만약에 let이면 어떻게 됐을까요?
function test() {
let foo = '1';
console.log(foo); // 1
if (true) {
let foo = '2';
console.log(foo); // 2
};
console.log(foo); // 1
}
let은 블록단위라고 말씀드렸죠? 그래서 함수에 설정된 let과, if문안에 설정된 let은 서로 다른 아이입니다. 그래서 서로 간섭하지 않아요.
그러면 다음 코드는 어떻게 동작할까요?
function test() {
var foo = '1';
console.log(foo); // 1
if (true) {
console.log(foo); // 1
var foo = '2';
console.log(foo); // 2
};
console.log(foo); // 1
}
그럴싸해보이나요? 이걸 let으로 바꾸면 어떻게 나오는지 한번 보시죠.
function test() {
let foo = '1';
console.log(foo); // 1
if (true) {
console.log(foo); // foo is not defined
let foo = '2';
};
console.log(foo);
}
왜 not defined일까요? 엄연히 함수 쪽에서 선언된 변수에는 접근할 수 있어야 할 것 같은데요. 비밀은 유효범위에 있습니다.
if문 블록 안에서 let foo = '2'를 선언했기 때문에, if문에서는 let foo = '2'가 적용되게 되고 바깥에서 선언된 foo는 무시됩니다.
블록 내에서 선언하지 않았다면 바깥에 있는 foo를 알 수 있겠죠.
function test() {
let foo = '1';
console.log(foo); // 1
if (true) {
console.log(foo); // 1
};
}