はじめに
「letとvarって結局どう違うの?」——多くの開発者が一度はこの疑問にぶつかります。ある日、varで書いたコードが思った通りに動かず、letに変えてみたら直った…でもその理由は分からない。
この記事では、“なんとなく使っている”状態から脱却し、JavaScriptの変数宣言に自信を持てるようになることを目指します。
letとvarを理解するメリット
-
予期せぬバグを防げる
-
コードの可読性と保守性が向上する
-
モダンなJavaScriptの基礎が身につく
歴史的背景
JavaScriptが最初に登場した1995年当初、変数宣言に使えるのはvarのみでした。しかし、以下のような問題点が多く報告されていました:
-
グローバル汚染:varで宣言された変数はグローバルオブジェクト(windowなど)に追加されるため、意図せず既存の変数を上書きしてしまうことがある
-
スコープの曖昧さ:varは関数スコープであり、ブロックごとの制御ができない
-
ホイスティングによる予測困難なバグ:宣言だけが巻き上げられ、初期化が後になるためバグを生みやすい
これらの課題を解決するため、ES6(2015年)でletとconstが導入されました。これにより、ブロックスコープや一時的デッドゾーン(TDZ)など、より直感的で安全なコードを書く仕組みが整備されました。
varの特徴と挙動を理解する
varの主な特徴:
-
関数スコープ
-
ホイスティング(巻き上げ)
-
再宣言・再代入が可能
コード例:ホイスティングの罠
結論:宣言が巻き上げられ、初期化前にアクセスしてもエラーにならずundefinedになる
console.log(score); // 出力: undefined ← 変数scoreは宣言されているが、初期化前
var score = 10; // 初期化はこのタイミング
実行イメージ:
var score;
console.log(score); // undefined
score = 10;
function test() {
if (true) {
var message = 'Hello'; // varは関数スコープに属する
}
console.log(message); // 出力: 'Hello' ← ブロック外でもアクセス可能
}
test();
letの特徴とモダンなJavaScriptでの役割
letの主な特徴:
-
ブロックスコープ
-
一時的デッドゾーン(TDZ)
-
再代入は可能だが、再宣言は不可
コード例:TDZの挙動
結論:宣言前にアクセスするとエラーになる(安全な挙動)
console.log(age); // ❌ ReferenceError: Cannot access 'age' before initialization
let age = 25; // 初期化タイミング以降でなければアクセス不可
実行イメージ:
スコープ内に変数は存在しているが、初期化前のアクセスは禁止(TDZ)
function testLet() {
if (true) {
let greeting = 'Hi'; // このブロック内でのみ有効
}
console.log(greeting); // ❌ ReferenceError ← ブロックスコープの外なので参照不可
}
testLet();
実務で気をつけたい!varからlet/constへの移行ポイント
よくある落とし穴と注意点
forループ内のクロージャ
結論:letを使えば意図通りの値を保持できる
for (var i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
} // 出力: 3, 3, 3
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
} // 出力: 0, 1, 2
なぜこうなる?
-
varは関数スコープなので、ループ内で同じiを共有するため、ループ後の値が出力される
-
letはブロックスコープで、各ループごとに新しいiが定義されるため、想定通りの挙動になる
グローバル変数の扱い
結論:varはwindowオブジェクトに属し、let/constは属さない
var x = 10;
console.log(window.x); // 出力: 10 ← グローバルオブジェクトに属する
let y = 20;
console.log(window.y); // 出力: undefined ← windowのプロパティではない
なぜこうなる?
-
varで宣言した変数は、グローバルスコープであればwindowオブジェクトのプロパティとなる
-
letとconstはブロックスコープで、windowオブジェクトに紐付かない安全設計になっている
letとvarのおすすめ用途パターン(視覚ガイド)
┌──────────────┬────────────────────────────┐
│ 使用ケース │ 推奨されるキーワード │
├──────────────┼────────────────────────────┤
│ ループカウンタ │ let(スコープ限定で安全) │
│ 一度だけ使う値 │ const(再代入不可で意図が明確) │
│ 古いコードの保守 │ var(無理に変えない方が安全な場合)│
│ 再代入が必要な変数 │ let(変更を許容する) │
└──────────────┴────────────────────────────┘
constとの違いも軽くおさらい
constはletと同様にブロックスコープですが、再代入が不可であり、意図の明確化や安全性の向上に役立ちます。
const name = 'Alice';
name = 'Bob'; // ❌ TypeError
const user = { age: 25 };
user.age = 26; // ✅ OK(オブジェクトの中身は変更可能)
この記事で解決できるようになったこと
-
letとvarのスコープやホイスティングの違いを理解できた
-
TDZや再宣言不可の特性を把握し、安全な変数宣言ができるようになった
-
実務での使い分けポイントと、移行時の注意点を学べた
-
constとの違いも簡潔に理解し、現代JSの変数宣言の全体像が見えた
まとめ
JavaScriptのletとvarの違いを理解することは、バグを減らし、コードを堅牢にする基礎力となります。
-
var:関数スコープ。古いコードやレガシー対応に限って使用。
-
let:ブロックスコープ。再代入が必要な場合に使う。
-
const:再代入不要な定数に使う。デフォルトの選択肢として推奨。
この違いを使いこなすことで、モダンJavaScriptの開発効率と安全性は格段に向上します。
【外部リンク】
【内部リンク】
JavaScriptでコードが書けない本当の理由と“順序設計”のすすめ
JavaScriptでよく発生するエラー「ReferenceError」「SyntaxError」などの原因と対策を具体例付きで解説
JavaScriptのAjax完全入門|fetchの使い方・CORSエラーの対処・非同期通信の基本を解説
JavaScriptの配列・JSON・map・Map:データ操作術
TypeError: Cannot read propertyの意味と回避術【初心者向け】