この記事の目次
let・const・varの違いとは
JavaScriptで変数を宣言するキーワードは var、let、const の3つです。var は昔からある書き方で、let と const は ES2015(ES6)で追加されました。現在の実務では「基本は const、再代入が必要なときだけ let、var は新規コードでは使わない」が定番です。
違いを一言でまとめると、次の3点です。
varは関数スコープ、letとconstはブロックスコープconstは再代入できない(letとvarは再代入できる)letとconstは宣言前にアクセスするとエラーになる(varはundefinedになる)
基本(構文・仕組み)
| 項目 | var | let | const |
|---|---|---|---|
| スコープ | 関数 | ブロック {} |
ブロック {} |
| 再宣言 | できる | できない | できない |
| 再代入 | できる | できる | できない |
| 宣言前の参照 | undefined |
ReferenceError | ReferenceError |
| 初期化 | 省略可 | 省略可 | 必須 |
「ブロック」とは if、for、{ } で囲まれた範囲のことです。var はブロックを無視して関数全体に効くため、意図しない場所から値が見えてしまいます。
const price = 1000; // 再代入しない値
let count = 0; // 後で変わる値
count = count + 1;
サンプルコード(実行結果つき)
スコープの違い
function f() {
if (true) {
var a = 1;
let b = 2;
const c = 3;
}
console.log(typeof a, typeof b, typeof c);
}
f(); // number undefined undefined
if の外から var の a だけが見えています。let・const はブロックの外では存在しないため、typeof は undefined を返します。
巻き上げ(ホイスティング)
console.log(typeof v1); // undefined
var v1 = 1;
console.log(l1); // ReferenceError: Cannot access 'l1' before initialization
let l1 = 1;
var は宣言が先頭に引き上げられ、値だけが後から入ります。let・const も宣言自体は巻き上げられますが、初期化されるまでの区間(TDZ:一時的デッドゾーン)で触るとエラーになります。バグに早く気づけるので、エラーになる側が安全です。
ループとクロージャ
const r1 = [];
for (var i = 0; i < 3; i++) { r1.push(() => i); }
console.log('var:', r1.map(f => f())); // [ 3, 3, 3 ]
const r2 = [];
for (let j = 0; j < 3; j++) { r2.push(() => j); }
console.log('let:', r2.map(f => f())); // [ 0, 1, 2 ]
var は全ての関数が同じ i を共有するため全部 3 になります。let は反復ごとに新しい束縛が作られるので、期待どおり 0, 1, 2 になります。
constの注意:中身は変えられる
const o = { n: 1 };
o.n = 2; // OK: { n: 2 }
o = 3; // TypeError: Assignment to constant variable.
const が固定するのは「変数と値の結びつき」であり、オブジェクトの中身ではありません。中身も固定したいときは Object.freeze() を使いますが、浅い凍結である点に注意してください(オブジェクトのコピー も参照)。
動作確認(検証環境と結果)
- 環境: Node.js v24.19.0(Windows 11)、
nodeで上記コードを実行 - 結果: 上記コメントの出力と一致(
number undefined undefined、[ 3, 3, 3 ]、[ 0, 1, 2 ]、各エラーメッセージ) - 再宣言
let x=1; let x=2;はSyntaxError: Identifier 'x' has already been declaredを確認 - 検証ログ:
C:Tempks2techtestsjs-let-const-var.md - ブラウザでの実行は未検証(挙動は仕様で定められているため同じ結果になる想定です)
let・const・varの違いの注意点
constは「不変」ではなく「再代入不可」です。配列やオブジェクトのpush・プロパティ変更は可能です。- 通常のスクリプト(ES Modules 以外)のトップレベルで
var宣言するとwindowのプロパティになりますが、let・constはなりません。 letをforの先頭で宣言すると反復ごとに別の束縛になります。外で宣言したletを使い回す場合は別物です。- 古いブラウザ(Internet Explorer など)は
let・constに非対応です。現在は多くのサイトでサポート対象外になっていますが、案件の対応ブラウザは確認してください。
let・const・varの違いでよくあるミス
- 何でも
letで宣言してしまい、どの変数が変わるのか読みにくくなる。 constで宣言した配列に再代入しようとしてエラーになる(arr = [...]はNG、arr.push()はOK)。switchのcase内でletを宣言して、同じブロックで名前が衝突する。caseごとに{ }で囲むと回避できます。- 宣言前に関数を呼んでしまい、ReferenceError になる。
ブラウザ対応・バージョン
let・const は主要ブラウザの現行版で利用できます。詳細な対応バージョンは各MDNページの「ブラウザーの互換性」で確認してください(上記 sources 参照)。
let・const・varの違いのチェックリスト
- [ ] 再代入しない変数は
constにした - [ ] 再代入する変数だけ
letにした - [ ] 新規コードに
varを書いていない - [ ] ループ内の非同期処理・コールバックで変数が共有されていない
- [ ]
constのオブジェクトを「完全に固定」と誤解していない
let・const・varの違いのFAQ(よくある質問)
Q. varはもう使ってはいけませんか?
A. 動作はしますが、スコープが広く巻き上げの挙動も紛らわしいため、新規コードでは let・const を勧めます。既存コードの保守では var を見かけるので、読めることは必要です。
Q. 毎回constにするのは面倒では?
A. 先に const で書き、再代入が必要になったときに let へ変えるだけです。ESLintの prefer-const ルールで自動検出もできます。
Q. 関数の中だけで使う定数も大文字にすべき?
A. 慣習として、モジュール全体で使う固定値は MAX_COUNT のように大文字にすることがありますが、必須ではありません。チームの規約に従ってください。
筆者の見解(let・const・varの違い)
私見では、const を基本にする習慣は、コードの読みやすさに直結すると考えます。変数が再代入されないと分かれば、読む側は「この値は途中で変わらない」と安心して追えるためです。一方で、const をオブジェクトの不変化と誤解する初心者が多いので、教えるときは「再代入できないだけ」と繰り返し伝えるのが良いと思います。
