본문으로 건너뛰기

안동민 개발노트

본문 시작

let, const와 블록 스코프

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

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

이제 기본 웹 애플리케이션을 만들 수 있는 기반은 충분히 갖춰진 상태입니다.

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

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

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

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

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

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

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

var 습관에서 블록 스코프 기반 선언으로

이 절은 `var`의 함수 스코프, 호이스팅, 재선언 허용을 짚고 `let`과 `const`로 변수의 수명과 변경 가능성을 더 명확하게 표현하는 흐름으로 이어집니다.

  1. 먼저 확인
    `var`가 헷갈리는 이유

    `if`나 `for` 블록 안에서 선언해도 블록 밖에서 접근될 수 있고, 선언 전에 읽으면 오류 대신 `undefined`가 나올 수 있습니다.

  2. 다음 해결
    `let`은 블록 안에서만 유효

    `let`은 중괄호로 묶인 범위를 따르며 같은 스코프에서 같은 이름을 다시 선언하지 못하게 해 실수를 줄입니다.

  3. 마지막 선택
    `const`를 기본값으로 둔다

    재할당이 필요 없으면 `const`, 값이 바뀌어야 하면 `let`을 씁니다. 새 코드에서 `var`는 피하는 것이 현재 권장 흐름입니다.


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 (함수 스코프)

이는 다른 언어에서 블록 스코프가 일반적인 것과 달라 혼란을 야기했습니다.

변수 호이스팅 (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 스코프

MDX 예제의 핵심은 iffor 안에서 만든 이름이 어디까지 살아 있는지, 그리고 선언 전에 읽었을 때 어떤 오류가 드러나는지입니다.

  1. var · 블록 밖에서도 보일 수 있음

    if 블록은 var의 경계가 아닙니다.

    if (true) {
      var x = 10;
    }
    console.log(x); // 10

    함수 안에서 선언했다면 함수가 경계가 되고, 함수 밖 전역에서 선언했다면 블록을 빠져나와도 이름이 남습니다.

  2. let · 선언된 블록 안에서만 유효

    for 카운터도 반복문 밖으로 새지 않습니다.

    for (let i = 0; i < 3; i++) {
      console.log(i);
    }
    // console.log(i); ReferenceError

    블록 안에서 필요한 이름을 블록 안에 가두기 때문에 이름 충돌과 실수로 덮어쓰는 위험이 줄어듭니다.

블록 스코프 (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); // 결과: hello

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


키워드: 상수 선언

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.

객체나 배열 내부까지도 변경 불가능하게 만들려면, 깊은 복사(deep copy)를 사용하거나 Object.freeze()와 같은 메서드를 활용해야 합니다.


var vs let vs const

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

const 재할당 제한

`const`는 선언과 동시에 값을 넣어야 하고 재할당을 막습니다. 다만 객체나 배열의 내부 값 변경까지 자동으로 막는 것은 아닙니다.

  1. 불가능
    상수 이름에 새 값을 다시 넣기

    const PI = 3.14159; PI = 3.14; // TypeError `const TAX_RATE;`처럼 값을 빼고 선언하는 것도 허용되지 않습니다.

  2. 2
    3. 새 코드에서 `var` 지양

    레거시 유지보수가 아니라면 함수 스코프 습관을 줄입니다.

  • 기본적으로 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 서버 주소를 가정한 값입니다.


ES6 선언 키워드 정리

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

선언 키워드 선택 기준

키워드는 단순 문법이 아니라 스코프, 재할당 가능성, 예측 가능성을 드러내는 작은 설계 신호입니다.

  1. 기본값
    const

    참조를 바꾸지 않을 값에 먼저 사용합니다.

  2. 필요할 때
    let

    값이 흐름에 따라 바뀌어야 할 때만 사용합니다.

  3. 피하기
    var

    함수 스코프와 호이스팅 때문에 새 코드에서는 피합니다.

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

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

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

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

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

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

let·const·블록 스코프 선택

let과 const는 변수가 살아 있는 범위를 블록으로 좁혀 값 추적을 쉽게 만든다.

선택쓸 때장점주의점
const재할당이 필요 없는 이름의도가 안정적객체 내부 변경까지 막지는 않음
let반복 카운터나 단계적으로 바뀌는 값변경 지점이 드러남넓은 범위에서 재사용 금지
var기존 코드 이해용함수 스코프와 호이스팅 확인새 코드에서는 피함
Blockif, for, 중괄호 범위변수 수명 축소블록 밖 접근 기대 금지
TDZ선언 전 접근 금지 구간초기화 순서 버그 발견호이스팅과 접근 가능성을 혼동
let과 const 스코프

var의 함수 스코프와 호이스팅 혼선을 블록 스코프, TDZ, 바인딩 불변성으로 나누어 정리한다.

개념let/constvar와 차이확인 질문
Scope블록 단위로 살아 있음함수 단위로 살아 있음변수가 필요한 가장 작은 범위인가
Hoisting선언은 올려지지만 TDZ 접근 금지undefined로 접근 가능선언 전 읽기가 있는가
Reassignlet은 가능, const는 불가var는 가능정말 값이 바뀌어야 하는가
Object const바인딩만 고정객체 속성 변경 가능불변 데이터와 혼동하지 않는가
Loop반복마다 블록 스코프 생성var는 같은 binding 공유비동기 콜백에서 값이 꼬이지 않는가
let·const와 블록 스코프의 예측 가능성

let과 const는 var를 대체하는 취향 문제가 아니라 변수가 살아 있는 범위를 블록으로 제한해 값 추적을 단순하게 만든다.

문제var에서 생기는 혼선let/const의 대응좋은 기준
Function scope블록 밖에서도 값이 남음블록 밖 접근 차단필요한 위치에 선언
Hoisting선언 전 undefined 읽힘TDZ로 접근 오류초기화 전 사용 금지
Loop closure반복 콜백이 같은 변수 공유반복마다 새 binding비동기 반복 값 확인
Reassignment의도 없는 값 변경const로 재할당 방지변경 가능한 값만 let
Readability값 수명이 길어짐수명이 짧아짐선언과 사용 위치가 가까운가