본문 바로가기

let, const와 블록 스코프

var의 함수 스코프·재선언·호이스팅 문제를 확인하고 let·const의 블록 스코프와 TDZ, 재할당 규칙을 익힙니다.

우리는 7장 자바스크립트 심화 II에서 비동기 처리, 모듈 시스템, 디자인 패턴까지 실무에서 자주 마주치는 주제를 폭넓게 다뤘습니다.

이번 절은 실행 컨텍스트와 클로저에서 다룬 이름의 유효 범위와 초기화를 선언 키워드에 적용합니다. 먼저 이름을 찾는 스코프와 그 이름에 값을 대입하는 시점을 구별해 봅시다.

다음 단계는 최신 표준 문법을 정확히 쓰는 것입니다.

자바스크립트는 ECMAScript 표준을 따라 매년 진화하고, 같은 기능도 예전 방식보다 더 안전하고 읽기 쉬운 문법으로 바뀌어 왔습니다.

특히 ES2015(ES6)는 전환점이었습니다.

단순 문법 추가가 아니라, 개발자가 오류를 줄이고 코드를 명확하게 작성하도록 언어의 방향을 바꿔 놓았습니다.

이 장에서는 ES2015 이후 핵심 문법을 다룹니다.

첫 주제는 var에서 let/const로 넘어가는 이유와 블록 스코프(Block Scope)입니다.

이번 장에서는 var의 한계를 짚고, let/const 특징과 스코프 개념을 중심으로 정리합니다.

let·const 바인딩 초기화와 재할당 — 상태 추적

같은 바인딩의 상태가 미초기화 TDZ→선언 초기화→재할당 시도에 따라 달라진다. let의 새 값과 const 오류 뒤 기존 값 유지, 바깥 동명 변수 미탐색을 추적한다.

let·const 바인딩 초기화와 재할당

STATE · WEB PLATFORM CONTRACT

let·const 바인딩 초기화와 재할당

선언 위치에서 초기화되기 전에는 읽을 수 없다. 초기화 이후 let은 재할당할 수 있고, const의 재할당 오류는 기존 값을 바꾸지 않는다.

이름 생성·초기화·두 재할당 경로스코프에 진입하면 이름이 생성되지만 아직 초기화되지 않습니다. 초기화 전 TDZ에서 읽으면 ReferenceError가 발생하며, 바깥의 동명 변수로 넘어가지 않습니다. 선언 평가 뒤에는 값을 읽을 수 있습니다. let의 재할당은 새 값을 저장한 뒤 읽기 상태로 돌아갑니다. const의 재할당은 TypeError를 던지지만 catch로 처리하면 기존 값을 읽을 수 있습니다. 모바일도 두 별도 경로와 반환 방향을 유지합니다. 객체 내부 변경은 이 바인딩 그림의 범위 밖입니다.스코프 진입이름 생성 · 아직 초기화 전TDZ읽으면 ReferenceError선언 평가 · 초기화let 초기값 없음: undefined현재 값 읽기 가능let 또는 constlet 재할당새 값 저장const 재할당 시도TypeError · 기존 값 유지오류를 catch하면 기존 값 읽기를 계속할 수 있음
이름 생성·초기화·두 재할당 경로스코프에 진입하면 이름이 생성되지만 아직 초기화되지 않습니다. 초기화 전 TDZ에서 읽으면 ReferenceError가 발생하며, 바깥의 동명 변수로 넘어가지 않습니다. 선언 평가 뒤에는 값을 읽을 수 있습니다. let의 재할당은 새 값을 저장한 뒤 읽기 상태로 돌아갑니다. const의 재할당은 TypeError를 던지지만 catch로 처리하면 기존 값을 읽을 수 있습니다. 모바일도 두 별도 경로와 반환 방향을 유지합니다. 객체 내부 변경은 이 바인딩 그림의 범위 밖입니다.스코프 진입이름 생성TDZ · 초기화 전ReferenceError선언 평가초기값 저장값 읽기 가능현재 값let 재할당새 값 저장letconst 재할당TypeError기존 값 유지const의 다른 경로catch 뒤 읽기 계속 가능
  1. scope 진입 → TDZ

    let·const binding은 생기지만 uninitialized라 declaration 전 읽기는 ReferenceError다.

  2. declaration initialization

    초기값을 평가해 저장하면 읽을 수 있다. 초기값 없는 let 선언은 undefined로 초기화한다.

  3. let

    재할당하면 새 값을 저장하고 같은 readable 상태로 돌아간다.

  4. const

    재할당 시도는 TypeError를 던진다. catch 뒤에도 기존 binding과 값은 그대로 readable이다.

초기화 완료 뒤에 읽기 가능

let은 재할당한 새 값을 읽는다. const의 재할당 시도는 오류를 던지지만 기존 값은 그대로 유지한다.

주황색 반환선은 let의 새 값 저장, 점선은 const 오류 이후 기존 값 유지를 뜻한다. TypeError는 catch로 처리할 수 있다. const가 가리키는 객체의 속성 변경은 별개다.

이 예의 조건

  • 원도표의 전체 구조와 설명은 위 관계도와 표에서 유지합니다. 아래 인쇄 표에도 원도표의 관계·조건·라벨·설명·주석을 보존합니다.
  • 두 관찰 칸의 위치는 고정입니다. 관찰 상태는 해당 순간의 값이며, 동작·이유는 그 조건에서의 판정과 변화 원인입니다. 독립 비교와 실패 분기는 별도 조건으로 읽고 모든 분기를 한 요청의 필수 실행 순서로 이어 읽지 않습니다.
관계도의 보충 설명
원도표의 전체 관계·조건·설명
구분내용
원도표 조건·주석scope 진입 → TDZ: let·const binding은 생기지만 uninitialized라 declaration 전 읽기는 ReferenceError다.
원도표 조건·주석declaration initialization: 초기값을 평가해 저장하면 읽을 수 있다. 초기값 없는 let 선언은 undefined로 초기화한다.
원도표 조건·주석let: 재할당하면 새 값을 저장하고 같은 readable 상태로 돌아간다.
원도표 조건·주석const: 재할당 시도는 TypeError를 던진다. catch 뒤에도 기존 binding과 값은 그대로 readable이다.
전체 상태와 변화 근거
장면현재 관찰값설명변화 이유
TDZbinding: uninitialized / 읽기: ReferenceError블록 이름은 있지만 선언 초기화 전이다같은 이름의 바깥 값을 대신 검색하지 않는다
초기화binding: 초기화됨 / 읽기: 현재 값선언을 실행해 값을 저장한다선언 실행 뒤에만 해당 binding을 사용할 수 있다
재할당 비교let: 새 값 / const: TypeError/기존 값 유지let은 새 값을 저장하고 const 재할당은 실패한다catch하는 확장 가정 없이는 오류 뒤 실행이 계속된다고 쓰지 않는다

let·const 바인딩 초기화와 재할당 — 상태 추적

같은 바인딩의 상태가 미초기화 TDZ→선언 초기화→재할당 시도에 따라 달라진다. let의 새 값과 const 오류 뒤 기존 값 유지, 바깥 동명 변수 미탐색을 추적한다.

let·const 바인딩 초기화와 재할당

STATE · WEB PLATFORM CONTRACT

let·const 바인딩 초기화와 재할당

선언 위치에서 초기화되기 전에는 읽을 수 없다. 초기화 이후 let은 재할당할 수 있고, const의 재할당 오류는 기존 값을 바꾸지 않는다.

이름 생성·초기화·두 재할당 경로스코프에 진입하면 이름이 생성되지만 아직 초기화되지 않습니다. 초기화 전 TDZ에서 읽으면 ReferenceError가 발생하며, 바깥의 동명 변수로 넘어가지 않습니다. 선언 평가 뒤에는 값을 읽을 수 있습니다. let의 재할당은 새 값을 저장한 뒤 읽기 상태로 돌아갑니다. const의 재할당은 TypeError를 던지지만 catch로 처리하면 기존 값을 읽을 수 있습니다. 모바일도 두 별도 경로와 반환 방향을 유지합니다. 객체 내부 변경은 이 바인딩 그림의 범위 밖입니다.스코프 진입이름 생성 · 아직 초기화 전TDZ읽으면 ReferenceError선언 평가 · 초기화let 초기값 없음: undefined현재 값 읽기 가능let 또는 constlet 재할당새 값 저장const 재할당 시도TypeError · 기존 값 유지오류를 catch하면 기존 값 읽기를 계속할 수 있음
이름 생성·초기화·두 재할당 경로스코프에 진입하면 이름이 생성되지만 아직 초기화되지 않습니다. 초기화 전 TDZ에서 읽으면 ReferenceError가 발생하며, 바깥의 동명 변수로 넘어가지 않습니다. 선언 평가 뒤에는 값을 읽을 수 있습니다. let의 재할당은 새 값을 저장한 뒤 읽기 상태로 돌아갑니다. const의 재할당은 TypeError를 던지지만 catch로 처리하면 기존 값을 읽을 수 있습니다. 모바일도 두 별도 경로와 반환 방향을 유지합니다. 객체 내부 변경은 이 바인딩 그림의 범위 밖입니다.스코프 진입이름 생성TDZ · 초기화 전ReferenceError선언 평가초기값 저장값 읽기 가능현재 값let 재할당새 값 저장letconst 재할당TypeError기존 값 유지const의 다른 경로catch 뒤 읽기 계속 가능
  1. scope 진입 → TDZ

    let·const binding은 생기지만 uninitialized라 declaration 전 읽기는 ReferenceError다.

  2. declaration initialization

    초기값을 평가해 저장하면 읽을 수 있다. 초기값 없는 let 선언은 undefined로 초기화한다.

  3. let

    재할당하면 새 값을 저장하고 같은 readable 상태로 돌아간다.

  4. const

    재할당 시도는 TypeError를 던진다. catch 뒤에도 기존 binding과 값은 그대로 readable이다.

초기화 완료 뒤에 읽기 가능

let은 재할당한 새 값을 읽는다. const의 재할당 시도는 오류를 던지지만 기존 값은 그대로 유지한다.

주황색 반환선은 let의 새 값 저장, 점선은 const 오류 이후 기존 값 유지를 뜻한다. TypeError는 catch로 처리할 수 있다. const가 가리키는 객체의 속성 변경은 별개다.

이 예의 조건

  • 원도표의 전체 구조와 설명은 위 관계도와 표에서 유지합니다. 아래 인쇄 표에도 원도표의 관계·조건·라벨·설명·주석을 보존합니다.
  • 두 관찰 칸의 위치는 고정입니다. 관찰 상태는 해당 순간의 값이며, 동작·이유는 그 조건에서의 판정과 변화 원인입니다. 독립 비교와 실패 분기는 별도 조건으로 읽고 모든 분기를 한 요청의 필수 실행 순서로 이어 읽지 않습니다.

관계도의 보충 설명

원도표의 전체 관계·조건·설명
구분내용
원도표 조건·주석scope 진입 → TDZ: let·const binding은 생기지만 uninitialized라 declaration 전 읽기는 ReferenceError다.
원도표 조건·주석declaration initialization: 초기값을 평가해 저장하면 읽을 수 있다. 초기값 없는 let 선언은 undefined로 초기화한다.
원도표 조건·주석let: 재할당하면 새 값을 저장하고 같은 readable 상태로 돌아간다.
원도표 조건·주석const: 재할당 시도는 TypeError를 던진다. catch 뒤에도 기존 binding과 값은 그대로 readable이다.
전체 상태와 변화 근거
장면현재 관찰값설명변화 이유
TDZbinding: uninitialized / 읽기: ReferenceError블록 이름은 있지만 선언 초기화 전이다같은 이름의 바깥 값을 대신 검색하지 않는다
초기화binding: 초기화됨 / 읽기: 현재 값선언을 실행해 값을 저장한다선언 실행 뒤에만 해당 binding을 사용할 수 있다
재할당 비교let: 새 값 / const: TypeError/기존 값 유지let은 새 값을 저장하고 const 재할당은 실패한다catch하는 확장 가정 없이는 오류 뒤 실행이 계속된다고 쓰지 않는다

var 키워드의 문제점 (복습 및 심화)

var는 함수 스코프를 따르며, 몇 가지 독특한 동작 때문에 개발자에게 혼란을 주거나 의도치 않은 버그를 발생시키기 쉬웠습니다.

함수 스코프 (Function Scope): var로 선언된 변수는 오직 함수 내부에서만 지역 변수로 취급되며, if 문이나 for 문과 같은 블록 내부에서는 지역 변수로 취급되지 않고 해당 함수의 스코프(또는 전역 스코프)에 속하게 됩니다.

if (true) {
    var x = 10;
    console.log(x); // 결과: 10
}
console.log(x); // 결과: 10 (if 블록 밖에서도 접근 가능)

function introduce() {
    var name = "김철수";
    console.log(name); // 결과: 김철수
}
introduce();
// console.log(name); // ReferenceError: name is not defined (함수 스코프)

함수 안의 name이 바깥으로 전달되는 것은 아닙니다. 다만 브라우저에는 window.name이라는 별도 이름이 이미 있으므로, 위 주석 줄을 해제했을 때 항상 ReferenceError라고 판단하면 안 됩니다. 함수 지역 변수와 주변 환경의 동명 값을 구별하세요.

변수 호이스팅 (Variable Hoisting): var로 선언된 변수는 선언부가 스코프의 최상단으로 끌어올려진 것처럼 동작합니다.

할당은 원래 위치에서 이루어지지만, 선언만 먼저 처리됩니다.

이 때문에 변수를 선언하기 전에 참조해도 오류가 발생하지 않고 undefined가 출력됩니다.

console.log(myVar); // 결과: undefined
var myVar = "hello";
console.log(myVar); // 결과: hello
// 실제로는 다음과 같이 동작합니다.
// var myVar;
// console.log(myVar);
// myVar = "hello";
// console.log(myVar);

이는 코드를 예측하기 어렵게 만들고 잠재적인 버그의 원인이 됩니다.

변수 재선언 허용: var는 동일한 이름의 변수를 여러 번 재선언해도 오류를 발생시키지 않습니다.

var myName = "Alice";
console.log(myName); // 결과: Alice

var myName = "Bob"; // 동일한 이름으로 재선언해도 에러 없음
console.log(myName); // 결과: Bob

이는 실수로 기존 변수를 덮어쓸 위험이 있어 대규모 프로젝트에서 버그를 유발할 수 있습니다.

이러한 var의 문제점들은 자바스크립트 개발을 어렵게 만드는 주요 원인이었습니다.


let 키워드: 새로운 변수 선언의 시작

let 키워드는 var의 문제점을 해결하기 위해 도입된 변수 선언 방식입니다.

같은 블록 코드를 var와 let으로 실행했을 때 블록 밖 접근 결과 비교

COMPARISON

var와 let: 블록 밖 접근 결과

선언 키워드만 바꾼 같은 예제다. var는 함수 안에 남고, let은 if 블록 밖에서 접근할 수 없다.

var

function compareVar() {
  if (true) {
    var count = 10;
  }
  console.log(count);
}

compareVar();

출력 10 — compareVar 함수 스코프의 바인딩이 if 블록 밖에도 남는다.

let

function compareLet() {
  if (true) {
    let count = 10;
  }
  console.log(count);
}

compareLet();

결과 ReferenceError — 바인딩은 선언된 블록 안에서만 유효하다.

비교 조건은 동일하며 선언 키워드만 다르다. 오류는 let의 결함이 아니라 블록 경계를 지키는 결과다.

블록 스코프 (Block Scope)

let으로 선언된 변수는 선언된 블록({}) 안에서만 유효합니다.

블록은 함수, if 문, for 문, while 문 등의 중괄호로 둘러싸인 영역을 의미합니다.

let y = 100; // 전역 스코프 또는 상위 블록 스코프

if (true) {
    let z = 20; // if 블록 내부에서만 유효
    console.log(z); // 결과: 20
    console.log(y); // 결과: 100 (상위 스코프 변수는 접근 가능)
}
// console.log(z); // ReferenceError: z is not defined (블록 밖에서 접근 불가)

for (let i = 0; i < 3; i++) {
    console.log(i); // 결과: 0, 1, 2
}
// console.log(i); // ReferenceError: i is not defined (for 블록 밖에서 접근 불가)

블록 스코프는 코드를 더 예측 가능하게 만들고, 변수 이름 충돌의 위험을 줄여줍니다.

변수 재선언 불가능

let은 동일한 스코프 내에서 같은 이름의 변수를 재선언하는 것을 허용하지 않습니다.

let fruit = "사과";
// let fruit = "바나나"; // SyntaxError: 'fruit' has already been declared

이는 개발자의 실수를 방지하고 코드의 견고성을 높여줍니다.

변수 호이스팅과 TDZ

let 변수도 호이스팅되지만, var와 다르게 초기화되기 전까지는 접근할 수 없습니다.

이 영역을 TDZ (Temporal Dead Zone, 시간상 사각지대)라고 부릅니다.

TDZ에 있는 변수에 접근하려고 하면 ReferenceError가 발생합니다.

console.log(myLetVar); // ReferenceError: Cannot access 'myLetVar' before initialization
let myLetVar = "hello";
console.log(myLetVar); // 앞의 TDZ 접근을 제거했을 때 hello; 원문 그대로는 앞의 예외로 도달하지 못함

TDZ는 개발자가 변수를 선언하기 전에 사용하는 실수를 방지하여 코드의 안정성을 높입니다.


const 키워드: 상수 선언

const 키워드는 let과 마찬가지로 블록 스코프를 따르며, 변수 호이스팅과 TDZ의 특성도 공유합니다.

하지만 가장 중요한 차이점은 상수(constant)를 선언할 때 사용된다는 것입니다.

즉, 초기화한 바인딩에 다른 값을 다시 대입할 수 없습니다. 그 값이 객체라면 객체 내부를 바꾸는 연산은 별개입니다.

재할당 불가능

const로 선언된 변수는 선언과 동시에 값을 할당해야 하며, 이후에는 재할당이 불가능합니다.

const PI = 3.14159;
console.log(PI); // 결과: 3.14159

// PI = 3.14; // TypeError: Assignment to constant variable. (재할당 불가능)

// const TAX_RATE; // SyntaxError: Missing initializer in const declaration (선언과 동시에 할당 필수)

객체/배열과 const

const가 재할당을 금지한다는 것은 같은 이름을 다른 값에 다시 연결할 수 없다는 의미입니다. 실제 메모리 주소를 확인하는 규칙으로 이해할 필요는 없습니다.

객체나 배열과 같은 참조 타입의 경우, const로 선언해도 객체/배열 내부의 프로퍼티나 요소는 변경할 수 있습니다.

const user = {
    name: "Jane",
    age: 25
};

user.age = 26; // 객체 내부의 프로퍼티 변경은 가능
console.log(user.age); // 결과: 26

// user = { name: "Mike", age: 30 }; // 하지만 객체 자체를 재할당하는 것은 불가능
// TypeError: Assignment to constant variable.

const colors = ["red", "green"];
colors.push("blue"); // 배열에 요소 추가는 가능
console.log(colors); // 결과: ["red", "green", "blue"]

// colors = ["yellow"]; // 배열 자체를 재할당하는 것은 불가능
// TypeError: Assignment to constant variable.

깊은 복사는 원본과 참조를 분리할 뿐 복사본의 변경을 막지는 않습니다. Object.freeze()도 기본적으로 해당 객체의 자체 속성만 동결하므로, 중첩 객체까지 동결하려면 별도의 처리가 필요합니다.


var vs let vs const

현대 자바스크립트 개발에서는 다음과 같은 규칙을 따르는 것이 일반적입니다.

  • 기본적으로 const를 사용합니다.
    • 변수의 값이 변경될 일이 없는 상수나, 참조할 객체/배열이 재할당될 일이 없는 경우에 사용합니다.
    • 이는 코드의 의도를 명확히 하고, 잠재적인 버그를 줄이는 데 큰 도움이 됩니다.
  • 재할당이 필요한 경우에만 let을 사용합니다.
    • 반복문 내의 카운터 변수, 조건에 따라 값이 변경될 수 있는 변수 등에 사용합니다.
  • var는 더 이상 사용하지 않는 것을 권장합니다.
    • 레거시 코드(오래된 코드)를 유지보수하는 경우가 아니라면 var를 새로운 코드에서 사용하는 것은 피하는 것이 좋습니다.
모범 사례
// 전역 설정값, 절대 변하지 않는 값
const API_URL = "http://localhost:4000";
const MAX_ITEMS_PER_PAGE = 20;

// 한 번 할당되면 변하지 않는 참조형 값 (내부 변경은 가능)
const config = {
    theme: "dark",
    version: "1.0.0"
};
config.theme = "light"; // 가능

let counter = 0; // 값이 증가해야 하므로 let
for (let i = 0; i < 5; i++) { // 반복문 카운터는 let
    counter += i;
}

if (someCondition) {
    let result = "Success"; // if 블록 내에서만 필요한 변수는 let
    console.log(result);
}
// console.log(result); // 에러

위 API_URL은 별도로 실행한 로컬 Mock 서버를 가정합니다. someCondition도 앞서 정의된 조건값이 있어야 하는 예시용 이름입니다.


예시 같은 이름이 바깥에 있어도 현재 블록의 선언이 그 이름을 가립니다. 아래 코드는 각각 새 파일이나 독립 실행 범위에서 실행하세요. catch는 예상한 오류 뒤에도 다음 줄을 확인하기 위한 것입니다.

const limit = 10;
{
    try {
        console.log(limit);
    } catch (error) {
        console.log(error.name);
    }
    let limit = 3;
    console.log(limit);
}
console.log(limit);

출력은 ReferenceError, 3, 10 순서입니다. 블록에 진입할 때 내부 limit 바인딩은 있지만 아직 초기화되지 않았습니다. 따라서 바깥의 10으로 넘어가 읽지 않습니다. let limit = 3을 지난 뒤에는 내부 값을 읽고, 블록을 나오면 바깥 이름을 다시 읽습니다. 호이스팅은 소스 줄을 실제로 이동시키는 작업이 아니라 이러한 이름 생성·초기화 규칙을 설명하는 표현입니다.

스스로 확인하기 let limit = 3;을 try 앞에 옮기고 나머지는 그대로 두면 세 번의 출력은 무엇인가요? 오류가 사라진 이유를 스코프 변경과 초기화 시점 변경 중에서 골라 설명해 보세요.

풀이 확인

3, 3, 10입니다. 블록 경계는 그대로지만 첫 읽기 전에 내부 이름이 초기화되었습니다. 바깥의 limit은 재할당되지 않았습니다.

기본값 없는 일반 let 선언은 선언 평가 때 undefined로 초기화됩니다. 반면 일반 const 선언에는 초기값이 필요합니다. for (const value of values)처럼 반복문이 매번 값을 제공하는 문법은 별도로 해석합니다.

선언과 초기화의 범위는 MDN const 설명에서 다시 확인할 수 있습니다.

ES6 선언 키워드 정리

핵심은 var 습관을 줄이고 블록 스코프 기반으로 상태 수명을 의도적으로 제어하는 것입니다.

재할당 필요 여부에 따라 const와 let을 선택하고 var는 기존 코드 해석에 한정하는 흐름

DECISION TREE

새 코드의 선언 키워드 선택

다른 값을 다시 대입해야 하는지 확인한다. 재할당이 없으면 const, 있으면 let을 선택한다.

바인딩 재할당 여부에 따른 선택새 코드에서 바인딩에 다른 값을 대입해야 하는지 판단합니다. 예 경로는 let, 아니오 경로는 const에서 끝납니다. 모바일은 결과를 세로로 배치하지만 let을 거쳐 const로 진행하는 선은 없습니다. 객체 프로퍼티 변경은 바인딩 재할당과 별개입니다. var는 기존 코드 해석에 연결합니다. 새 코드의 선언객체 내부 변경과 구별다른 값을 다시 대입?바인딩 재할당 여부let재할당이 필요한 경우const재할당이 없는 경우예아니오var는 기존 코드의 스코프·초기화를 해석할 때 확인
바인딩 재할당 여부에 따른 선택새 코드에서 바인딩에 다른 값을 대입해야 하는지 판단합니다. 예 경로는 let, 아니오 경로는 const에서 끝납니다. 모바일은 결과를 세로로 배치하지만 let을 거쳐 const로 진행하는 선은 없습니다. 객체 프로퍼티 변경은 바인딩 재할당과 별개입니다. var는 기존 코드 해석에 연결합니다.새 코드의 선언내부 변경과 구별다시 대입해야하는가?let재할당이 필요const재할당 없음예아니오: 다른 경로var: 기존 코드 해석
  • 재할당이 없다

    기본 선택은 const다.

  • 재할당이 필요하다

    let을 사용하고 변경 지점을 좁게 유지한다.

var는 새 코드의 선택지가 아니라 기존 코드의 스코프와 호이스팅을 해석할 때 확인한다. 객체의 속성 변경은 바인딩 재할당과 다르므로 const로도 가능하다.

이번 장에서는 자바스크립트에서 변수를 선언하는 새로운 표준인 let과 const 키워드, 그리고 그와 함께 도입된 블록 스코프(Block Scope) 개념에 대해 심도 있게 학습했습니다.

var는 함수 스코프, 호이스팅, 재선언 허용 때문에 예측하기 어려운 상황을 만들 수 있습니다.

let과 const는 블록 스코프와 TDZ를 통해 선언 위치와 사용 범위를 더 분명하게 드러냅니다.

현대 자바스크립트에서는 const를 기본으로 두고, 재할당이 필요한 경우에만 let을 사용합니다.

객체와 배열을 const로 선언해도 내부 값은 바뀔 수 있으므로, 재할당과 내부 변경을 구분해야 합니다.

마지막으로 선언 키워드를 실제 코드 리뷰에서 볼 때는 값의 변경 여부, 블록 밖 노출 여부, 초기화 시점을 함께 확인합니다.

const, let, var의 스코프, 재선언, 재할당, 선언 전 접근을 비교한 표

COMPARISON

const·let·var의 선언 경계

스코프, 재선언, 재할당, 선언 전 접근을 같은 축으로 비교한다. const의 제한은 객체 내부가 아닌 바인딩에 적용된다.

새 코드는 const를 기본으로 하고 재할당이 필요한 경우에만 let을 선택한다.
키워드 스코프 같은 스코프 재선언 재할당 선언 전 접근
const 기본 블록 불가 다른 값으로 바인딩 변경 불가 TDZ에서 ReferenceError
let 블록 불가 가능 TDZ에서 ReferenceError
var 함수 · 최상위는 전역/모듈 가능 가능 undefined로 읽힐 수 있음
const 기본
스코프 블록
같은 스코프 재선언 불가
재할당 다른 값으로 바인딩 변경 불가
선언 전 접근 TDZ에서 ReferenceError
let
스코프 블록
같은 스코프 재선언 불가
재할당 가능
선언 전 접근 TDZ에서 ReferenceError
var
스코프 함수 · 최상위는 전역/모듈
같은 스코프 재선언 가능
재할당 가능
선언 전 접근 undefined로 읽힐 수 있음
바인딩 고정 ≠ 객체 불변

const는 객체 내부를 불변으로 만들지 않는다. 바인딩을 다른 값으로 재할당하지 못하게 할 뿐이다.