Menu

자바스크립트 let, const, var 차이와 사용법 정리

자바스크립트에서 변수를 선언하는 세 가지 방법과, 요즘 코드가 왜 기본은 const, 필요할 때만 let, var는 거의 쓰지 않는지 그 이유를 짚어봅니다.

키워드는 세 개, 역할은 하나

자바스크립트에서 변수를 선언하는 방법은 var, let, const 이렇게 세 가지가 있습니다. 셋 다 이름에 값을 묶어준다는 점은 똑같지만, 스코프, 재할당 규칙, 그리고 _선언 전에 접근했을 때의 동작_에서 차이가 납니다. 요즘 코드에서는 대부분 const를 쓰고, 값이 바뀌어야 할 때만 let을 쓰며, var는 거의 쓰지 않습니다.

자바스크립트 var let const 차이를 한 줄로 정리하면 이렇습니다:

index.js
Output
Click Run to see the output here.

이 페이지의 나머지 부분에서는 왜 저 세 줄이 그런 식으로 동작하는지 하나씩 풀어보겠습니다.

const: 기본값으로 쓰는 선언

const재할당이 불가능한 바인딩을 만듭니다. 한 번 const x = 5라고 선언했다면, 이후에 x = 6처럼 값을 다시 넣을 수 없습니다. 시도하는 순간 엔진이 TypeError를 던집니다.

index.js
Output
Click Run to see the output here.

규칙은 딱 이게 전부입니다. 잘 짜인 코드에서는 대부분의 변수가 재할당될 일이 없기 때문에, const가 사실상 거의 모든 상황에 어울립니다. 일단 const부터 쓰는 습관을 들이면, 값이 실제로 바뀌는 지점이 눈에 확 들어오죠.

여기서 많이들 헷갈리는 부분이 있는데, const가 지켜주는 건 그 자체가 아니라 _바인딩_입니다. 즉, 값이 객체나 배열이라면 내부 내용은 여전히 바꿀 수 있어요:

index.js
Output
Click Run to see the output here.

const는 "이 이름이 항상 이 객체를 가리킨다"는 뜻이지, "이 객체가 절대 변하지 않는다"는 뜻이 아닙니다. 객체 자체를 얼려야 한다면 Object.freeze(user)를 쓰면 되지만, 실무에서는 대부분 const로 선언한 객체는 직접 수정하지 않는다는 관례에 기대는 편입니다.

let: 진짜로 재할당이 필요할 때

let은 재할당이 가능하다는 점만 빼면 const와 완전히 똑같습니다. 카운터, 누적값, 반복문 변수처럼 값이 시간에 따라 실제로 바뀌는 곳에 쓰면 됩니다.

index.js
Output
Click Run to see the output here.

let으로 선언했는데 한 번도 재할당하지 않는다면 const로 바꾸는 게 좋습니다. 대부분의 프로젝트에 설정된 린터가 알아서 잡아줄 거예요.

블록 스코프

letconst는 모두 _블록 스코프_를 가집니다. 여기서 블록이란 {} 사이에 있는 모든 것을 말해요. if문의 본문, for문의 본문, 함수 본문은 물론이고 그냥 { ... }로 감싼 독립된 블록까지 전부 포함됩니다. 블록 안에서 선언한 변수는 그 블록 바깥에서는 존재하지 않습니다.

index.js
Output
Click Run to see the output here.

우리가 원하는 게 바로 이런 동작이다. 변수가 자기 역할에 맞는 범위 안에만 머물러 있고, 이름이 실수로 충돌할 일도 생기지 않는다.

var는 이렇게 동작하지 않는다 — 그래서 var를 피해야 하는 것이다.

var: 함수 스코프와 예상치 못한 동작

var는 가장 가까운 블록이 아니라 가장 가까운 함수 를 기준으로 스코프가 잡힌다. 즉, iffor 안에서 선언한 var가 바깥 함수로 줄줄 새어 나온다:

index.js
Output
Click Run to see the output here.

이런 느슨한 스코프 규칙 때문에 자바스크립트에서 고전적인 버그가 수없이 생겨났습니다. 가장 유명한 예가 반복문인데, 모든 반복이 같은 var i를 공유하다 보니 콜백들이 하나같이 마지막 값만 바라보게 되는 문제죠.

게다가 var는 같은 스코프 안에서 동일한 이름을 다시 선언해도 아무 경고 없이 넘어갑니다. 그래서 오타가 조용히 묻혀버리기 쉽습니다:

index.js
Output
Click Run to see the output here.

요즘 나오는 코드는 거의 다 letconst를 쓴다. var는 레거시 코드베이스나 오래된 스택오버플로 답변, 구버전 환경에서 돌려야 하는 스크립트 정도에서나 가끔 마주친다.

호이스팅과 일시적 사각지대(TDZ)

세 가지 변수 선언 방식 모두 호이스팅 된다. 즉 엔진이 블록 안의 코드를 실행하기 전에 이미 해당 변수의 존재를 알고 있다는 뜻이다. 다만 선언문이 실행되기 전까지의 동작 방식은 서로 다르다.

var는 호이스팅되면서 undefined로 초기화된다. 그래서 var 선언문보다 앞 줄에서 참조해도 에러가 나지 않는다:

index.js
Output
Click Run to see the output here.

letconst도 호이스팅되긴 하지만 초기화는 되지 않습니다. 그래서 선언문에 도달하기 전에 건드리면 에러가 납니다. 스코프에 진입한 시점부터 실제 선언이 실행되기 전까지의 이 구간을 일시적 사각지대(Temporal Dead Zone, TDZ) 라고 부릅니다:

index.js
Output
Click Run to see the output here.

TDZ는 버그가 아니라 의도된 기능입니다. "선언하기 전에 사용" 상황을 조용히 undefined로 넘기는 대신 요란하게 에러로 터뜨려 주기 때문에, 오타나 순서 실수를 잡아내는 데 큰 도움이 됩니다.

for...of 반복문에서 const 사용하기

자주 마주치는 사소하지만 유용한 패턴이 하나 있습니다. for...of 반복문은 매 반복마다 새로운 바인딩을 만들어 주기 때문에, 반복마다 값이 "바뀌는" 것처럼 보여도 루프 변수에 const를 쓸 수 있습니다.

index.js
Output
Click Run to see the output here.

반복할 때마다 name은 매번 새로운 바인딩으로 만들어집니다. 하나의 변수가 계속 재할당되는 게 아니에요. 반면에 우리가 흔히 쓰는 for (let i = 0; i < n; i++) 패턴에서는 i가 하나의 바인딩이고 그 값을 계속 증가시키는 구조라서 여전히 let이 필요합니다.

실전에서 쓰는 기준

자바스크립트 변수 선언은 다음 순서로 고르면 됩니다.

  • 기본은 const. 값을 다시 할당할 일이 없다면 그 의도를 코드에 명확히 드러내세요.
  • 바인딩이 정말 바뀌어야 할 때만 let.
  • **var**는 기존 코드베이스가 요구하는 경우에만.

이 원칙을 꾸준히 지키면 변수만 봐도 의도가 바로 읽힙니다. 재할당되는 값인지 아닌지, 이 블록에만 묶여 있는지 함수 전체에 퍼져 있는지가 한눈에 드러나죠.

index.js
Output
Click Run to see the output here.

MAX_RETRIESusers는 한 번 정하면 바뀌지 않으니 const로 선언합니다. successful은 값이 계속 증가하므로 let이 적절하죠. 반복문 안의 user는 매 반복마다 새로 바인딩되는 값이라 const로 둡니다. 이렇게 위에서 아래로 쭉 읽기만 해도 어떤 값이 변하고 어떤 값이 고정인지 코드를 실행하지 않아도 한눈에 들어옵니다.

다음 주제: 원시 타입(Primitive Types)

이제 변수를 선언할 줄 알게 됐으니, 그다음 궁금해지는 건 "그 변수에 어떤 값을 담을 수 있느냐"입니다. 자바스크립트에는 숫자, 문자열, 불리언을 비롯한 몇 가지 원시 타입이 있고, 각각 나름의 특성과 함정이 있습니다. 이 내용은 다음 페이지에서 이어서 다뤄보겠습니다.

자주 묻는 질문

자바스크립트에서 let, const, var는 어떻게 다른가요?

constlet은 ES2015에서 추가된 블록 스코프 변수이고, var는 그 이전부터 있던 함수 스코프 변수입니다. const는 재할당이 막혀 있고 let은 재할당이 가능합니다. 또 var는 호이스팅되면서 undefined로 초기화되는 반면, letconst는 호이스팅은 되지만 선언문이 실행되기 전까지는 쓸 수 없습니다(이걸 TDZ, 일시적 사각지대라고 부릅니다).

const로 선언하면 값이 불변(immutable)이 되나요?

아닙니다. const는 바인딩 자체의 재할당만 막아줄 뿐입니다. 값이 객체나 배열이면 내부 내용은 얼마든지 바꿀 수 있어요. 예를 들어 const user = {}; user.name = 'Ada'는 아무 문제 없이 동작합니다. 진짜로 내용까지 못 바꾸게 하려면 Object.freeze를 쓰거나 별도 라이브러리가 필요합니다.

요즘 자바스크립트에서도 var를 써야 할 때가 있나요?

거의 없습니다. letconst가 스코프 규칙도 명확하고, 파싱 단계에서 잡아내는 버그도 더 많거든요. var를 마주칠 일은 대부분 레거시 코드베이스이거나, ES2015를 지원하지 않는 환경에서 돌아가야 하는 스크립트 정도입니다.

Coddy로 코딩 배우기

시작하기