【JavaScript】var, let, constの違いと使い分けを初心者向けに徹底解説

【JavaScript】var, let, constの違いと使い分けを初心者向けに徹底解説

Amazonのアソシエイトとして、ITナレッジライフは適格販売により収入を得ています。

記事の文字数:3700

JavaScriptの変数宣言(var, let, const)の違いを比較表とコード例で分かりやすく解説。スコープ、再宣言、再代入のルールに加え、「巻き上げ」やTDZ、よくあるバグ例まで詳しく説明します。


更新履歴


お役立ちツール



JavaScript学習者にお勧めの本

JavaScriptで開発を始める際、最初に出会うのが変数宣言です。かつては var しかありませんでしたが、ES6(ES2015)以降、 letconst が登場し、現在はこれらを適切に使い分けることがデファクトスタンダードとなっています。

「どれを使えばいいの?」「巻き上げって具体的にどういうこと?」という疑問を解決するために、本記事ではそれぞれの特性を整理し、現場で推奨される使い分けについて解説します。

[!IMPORTANT] 本記事のポイント

  • 基本は const を使い、再代入が必要な場合のみ let を選ぶ
  • var はスコープや巻き上げの挙動が直感的ではなく、バグの原因になりやすいため、新規開発では原則使用しない
  • 「巻き上げ」と「TDZ(一時的デッドゾーン)」の仕組みを理解することで、予期せぬバグを防ぐ

1. 【比較表】var, let, const の違いと使い分け

まずは、各宣言方法の主な違いを比較表で確認しましょう。

項目varletconst
概要旧来の変数宣言再代入可能な変数再代入できない変数
スコープ関数スコープブロックスコープブロックスコープ
再宣言××
再代入×
巻き上げ〇 (undefined)△ (TDZ)△ (TDZ)
推奨度

[!TIP] 現代のJavaScript開発では、「まずは const を使い、再代入が必要な場合のみ let を使う」 というのが鉄則です。var は歴史的経緯から存在する宣言方法であり、モダンな新規開発ではほとんど使用されません。ただし、古いライブラリやレガシーコードでは現在でも見かけることがあります。

2. 各宣言方法(var / let / const)の特徴と具体例

var の特徴と問題点

var はJavaScriptの初期から存在する宣言方法ですが、現代の開発では、スコープや巻き上げの挙動が原因でバグを生みやすい宣言方法とされています。

特徴:関数スコープと再宣言

var には「ブロックスコープ」という概念がなく、スコープは常に一番近い「関数単位」となります。また、同じスコープ内で何度でも再宣言が可能です。

// 【問題点1】再宣言を許容してしまう
var user = "Tanaka";
var user = "Sato"; // エラーにならず上書きされる
// 【問題点2】ブロックスコープを無視(スコープの汚染)
if (true) {
var score = 100;
}
console.log(score); // 100 (ブロックの外からアクセスできてしまう)

この「どこからでも見えてしまう」性質は、大規模なコードベースにおいて変数の競合や、予期せぬ場所での値の書き換えを引き起こすリスクがあります。

let の特徴と使い方

let は ES6 (ES2015) で導入された、安全性を重視した変数宣言方法です。

特徴:ブロックスコープと再宣言の禁止

let{ }(中括弧)で囲まれた「ブロック」内でのみ有効です。また、同じスコープ内での再宣言を禁止しているため、うっかり同じ名前の変数を作ってしまうミスを防げます。

注目点:ループ内での挙動

let の最大の強みの一つが、for ループ内での挙動です。

// let の場合:期待通り 0, 1, 2 と出力される
for (let i = 0; i < 3; i++) {
setTimeout(() => console.log(i), 100);
}
// var の場合:すべて 3 と出力されてしまう(クロージャが同じ変数を参照するため)
for (var j = 0; j < 3; j++) {
setTimeout(() => console.log(j), 100);
}

let を使うことで、非同期処理やコールバック関数においても変数の値を正しく保持できるようになります。

const の特徴と使い方

const は再代入できない変数を宣言するための仕組みです。変数そのものの参照は変更できませんが、オブジェクトや配列の中身を変更することは可能です。現代のJavaScriptでは、「変数はまず const で作る」 のが開発の鉄則です。

特徴:再代入の禁止

一度代入した値を書き換えることができません。これにより、コードを読む人はその変数が何を示しているかを常に信頼できるようになります。

注意点:オブジェクトと配列の「操作」

const は「変数そのものへの再代入」を禁止しますが、オブジェクトや配列の「中身(要素)」を書き換えることは可能です。

const colors = ["red", "blue"];
colors.push("green"); // OK! (中身の追加)
console.log(colors); // ["red", "blue", "green"]
// colors = ["white"]; // TypeError! (参照自体の入れ替えは不可)

[!TIP] オブジェクトの中身(プロパティ)まで完全に変更不可能にしたい場合は、Object.freeze() などのメソッドを併用する必要があります。
ただし Object.freeze()浅い凍結(shallow freeze) のため、ネストしたオブジェクトまでは保護されない点に注意が必要です。 例えば次のような場合、ネストされたオブジェクトのプロパティは変更できてしまいます。

const obj = {
user: { name: "Tanaka" }
};
Object.freeze(obj);
obj.user.name = "Sato"; // 変更できてしまう

3. 巻き上げ(Hoisting / ホイスティング)の仕組みとまとめ

JavaScriptの変数宣言には、コードの実行前に「スコープの先頭に持ち上げられる」ように見える巻き上げ(Hoisting / ホイスティング) という性質があります。

var の巻き上げ(undefined になる)

var は宣言が最上部に持ち上げられますが、値の初期化(代入)は実際の宣言行で行われます。そのため、宣言前にアクセスしてもエラーにはならず、中身が空である undefined が返ってきてしまいます。

console.log(x); // undefined (エラーにならないが混乱を招く)
var x = 5;

let / const の巻き上げと TDZ (一時的デッドゾーン)

letconst もスコープ生成時に変数は登録されますが、宣言行で初期化されるまで 未初期化状態(TDZ: Temporal Dead Zone) に置かれます。この期間に変数へアクセスすると ReferenceError が発生します。

TDZ とは?

TDZは、スコープの開始から初期化(宣言行への到達)までの間、その変数にアクセスできない「禁止区域」のことです。 この期間に変数へアクセスすると ReferenceError が発生します。

なぜ「Temporal(一時的)」なのか

TDZは単なるコードの記述順だけで決まるわけではなく、実際の実行順序 に基づいて判断されます。宣言行よりも物理的に「下」に書かれたコードであっても、宣言より「前」に実行される場合はエラーになります。

// 関数定義は物理的に「上」にあるが…
function showMessage() {
console.log(message);
}
// ここで実行すると message はまだ宣言されていない(TDZ内)
// showMessage(); // ReferenceError: Cannot access 'message' before initialization
let message = "Hello!";
showMessage(); // ここならOK! ("Hello!")

このように、TDZのおかげで「初期化されていない変数へのアクセス」という論理的なミスを、実行時に即座にエラーとして検出できるようになっています。

4. 実践:変数宣言にまつわる「よくあるバグ」とその対策

ここでは、JavaScript開発で実際に起こりやすいバグの例と、その回避策を紹介します。

① var による変数の「意図しない再宣言」

大規模なコードや長い関数内で var を使うと、すでに定義済みの変数をうっかり再宣言して上書きしてしまうことがあります。letconst であればエラーとして通知されるため、このミスを未然に防げます。

var status = "pending";
// ...(長い処理)...
var status = "success"; // ❌ 再宣言してもエラーにならず、値が上書きされる
let mode = "light";
// ...
let mode = "dark"; // ✅ エラー:Identifier 'mode' has already been declared

② 宣言キーワードの付け忘れ(グローバル汚染)

constlet を付け忘れて変数に代入すると、非 strict mode の環境では JavaScriptはそれを自動的に「グローバル変数」として扱います。 ただし、'use strict' や ES Modules(モダンな環境)では、この書き方は ReferenceError: is not defined となり実行時エラーになります。

function setUser() {
userName = "Tanaka"; // ❌ 宣言キーワードがないため、グローバル変数になる
}
setUser();
console.log(globalThis.userName); // "Tanaka" (意図せずどこからでもアクセス可能に)

対策: 常に const または let を使用してください。また、モダンな環境(ES Modules)や 'use strict'; 指定下では、この書き方はエラーになります。

③ const オブジェクトの「中身」が書き換わる

const は「再代入」を禁止するだけで、オブジェクトや配列の「プロパティや要素」の変更までは禁止しません。「constだから安心」と油断していると、関数に渡した先で元データを書き換えられてしまう副作用に繋がります。

const config = { theme: "dark" };
function resetTheme(options) {
options.theme = "light"; // ❌ const だが、プロパティは変更できてしまう
}
resetTheme(config);
console.log(config.theme); // "light" (元の値が書き換わった)

なお const は「値を変更できない」のではなく 「変数の参照(binding)が再代入できない」 という意味です。

対策: 完全に変更を禁止(イミュータブルに)したい場合は、 Object.freeze() を活用するか、スプレッド構文などによるコピーを活用しましょう。

5. 実務における「使い分け」の判断基準

現場で迷わないための、具体的な使い分けルールを整理します。

① 原則として const をデフォルトにする

「後から値を書き換える必要がない」変数がほとんどです。const を使うことで、その変数がプログラムの途中で意図せず書き換わらないことが保証され、コードの可読性と保守性が飛躍的に向上します。

② 再代入が必要な場合のみ let を使う

以下のケースでは let を使用します。

  • ループカウント: for (let i = 0; i < 10; i++)
  • フラグや集計値: 途中で true/false を切り替えたり、数値を加算したりする場合
  • 初期値が空の変数: if 文などの条件によって後から値を代入する場合

var は一切使用しない

現代のJavaScript(ES6以降)において、 var を使うメリットはありません。レガシーコードの修正でない限り、新規のコードで var を書くのは避けましょう。

④ スコープは最小限にする

変数は、使う直前で宣言するようにしましょう。関数の冒頭で全ての変数(特に let)を宣言してしまうと、どこで値が変わるかを追いかけるのが大変になります。必要なブロック内でのみ有効なスコープを作るのがベストプラクティスです。

6. まとめ:変数を適切に使い分けるために

バグの少ないクリーンなコードを書くために、JavaScriptの変数宣言を正しく理解することは第一歩です。

  • 基本の優先順位は constletvar
  • var はスコープや巻き上げの挙動が分かりにくく、バグの原因になりやすい
  • まず const を使い、再代入が必要な場合のみ let を選ぶ

この記事はお役に立ちましたか?



JavaScript学習者にお勧めの本


以上で本記事の解説を終わります。
よいITライフを!
目次

記事を評価

Thanks!
Scroll to Top