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
};
}