varとletの違いって何?JavaScriptの変数宣言を完全理解

JavaScriptのvarとletの違いを初心者向けに分かりやすく解説。スコープの違い、ホイスティング、再宣言の仕組みを実例付きで学びましょう。

Learning Next 運営
21 分で読めます

みなさん、JavaScriptを書いていてこんな経験はありませんか?

「varとletって何が違うの?」「どっちを使えばいいか分からない」「なんでエラーが出るの?」

こんな悩み、実はとても多いんです。 varとletは両方とも変数を作るためのキーワードですが、実は大きな違いがあります。

この記事では、JavaScriptのvarとletの違いについて初心者向けに分かりやすく解説します。 スコープやホイスティングなど、ちょっと難しそうな概念も実例付きで理解していきましょう!

varとletの基本的な違いを知ろう

どちらも変数を作るためのキーワード

まずは基本的な使い方を見てみましょう。

// var による変数宣言
var name = "太郎";
var age = 25;
// let による変数宣言
let email = "taro@example.com";
let isActive = true;
console.log(name); // "太郎"
console.log(age); // 25
console.log(email); // "taro@example.com"
console.log(isActive); // true

この例だけ見ると、varもletも同じように動いているように見えますね。 でも実は、内部では大きな違いがあるんです。

主な違いをざっくり理解しよう

varとletの主な違いをまとめてみました。

特徴varlet
どこまで使える?関数全体ブロック内だけ
宣言前に使える?使える(undefinedになる)エラーになる
同じ名前で再宣言できるエラーになる

この表を見ながら、詳しく学んでいきましょう。

スコープの違いが一番重要

varは関数全体で使える

varで宣言した変数は、関数全体でアクセスできます。

function varExample() {
if (true) {
var message = "Hello from var";
console.log(message); // "Hello from var"
}
// ifブロックの外でも使える!
console.log(message); // "Hello from var"
}
varExample();

ifブロックの中で作ったmessageが、外でも使えているのが分かりますね。

function calculateTotal() {
var total = 0;
for (var i = 0; i < 5; i++) {
var currentValue = i * 10;
total += currentValue;
}
// ループの外でも変数が使える
console.log("最後のi:", i); // 5
console.log("最後のcurrentValue:", currentValue); // 40
console.log("合計:", total); // 100
}
calculateTotal();

ループが終わった後でもicurrentValueが使えています。

letはブロック内だけで使える

letで宣言した変数は、そのブロック({}で囲まれた範囲)内でしか使えません。

function letExample() {
if (true) {
let message = "Hello from let";
console.log(message); // "Hello from let"
}
// ifブロックの外では使えない
// console.log(message); // ReferenceError: message is not defined
}
letExample();

ブロックの外でmessageを使おうとするとエラーになります。

function calculateTotalLet() {
let total = 0;
for (let i = 0; i < 5; i++) {
let currentValue = i * 10;
total += currentValue;
}
// ループの外では変数にアクセス不可
// console.log("最後のi:", i); // ReferenceError
// console.log("最後のcurrentValue:", currentValue); // ReferenceError
console.log("合計:", total); // 100
}
calculateTotalLet();

ループ用の変数icurrentValueは、ループの中でしか使えません。

実際によくある問題

ボタンのクリックイベントでよく起こる問題を見てみましょう。

// varを使った場合の問題
for (var i = 0; i < 3; i++) {
setTimeout(function() {
console.log("varループ:", i); // 3, 3, 3(全部3になってしまう)
}, 100);
}
// letを使った正しい例
for (let j = 0; j < 3; j++) {
setTimeout(function() {
console.log("letループ:", j); // 0, 1, 2(期待通りの結果)
}, 200);
}

varだと全部同じ値になってしまいますが、letなら期待通りに動きます。

ホイスティングの違いを理解しよう

varは宣言前でも使える

ホイスティングとは、変数の宣言が自動的に上に持ち上げられる仕組みです。

// 実際のコード
console.log(varVariable); // undefined(エラーではない)
var varVariable = "Hello";
console.log(varVariable); // "Hello"

宣言前にvarVariableを使ってもエラーになりません。 これは、JavaScriptが内部で次のように解釈するからです。

// JavaScriptが内部で解釈する形
var varVariable; // undefined で初期化される
console.log(varVariable); // undefined
varVariable = "Hello";
console.log(varVariable); // "Hello"

変数の宣言だけが上に移動して、最初はundefinedになるんです。

letは宣言前に使うとエラーになる

letの場合は、宣言前にアクセスするとエラーになります。

// letの場合はエラーになる
// console.log(letVariable); // ReferenceError: Cannot access 'letVariable' before initialization
let letVariable = "Hello";
console.log(letVariable); // "Hello"

これは一時的なデッドゾーンという仕組みのためです。

function demonstrateDeadZone() {
console.log("関数開始");
// この範囲では x にアクセスできない(デッドゾーン)
// console.log(x); // ReferenceError
let x = 10;
console.log("初期化後のx:", x); // 10
}
demonstrateDeadZone();

変数が宣言されてから初期化されるまでの間は、アクセスできないエリアになっています。

再宣言の違いも重要

varは同じ名前で何度でも宣言できる

varなら、同じ変数名で何度でも宣言できます。

// varは再宣言が可能
var username = "太郎";
console.log(username); // "太郎"
var username = "花子"; // エラーにならない
console.log(username); // "花子"
var username = "次郎";
console.log(username); // "次郎"

エラーにはなりませんが、うっかり同じ名前を使ってしまう危険があります。

letは同じ名前での再宣言は不可

letでは、同じスコープ内で同じ名前の変数を再宣言するとエラーになります。

// letは再宣言不可
let email = "user@example.com";
console.log(email); // "user@example.com"
// let email = "another@example.com"; // SyntaxError: Identifier 'email' has already been declared
// ただし、値の変更は可能
email = "updated@example.com";
console.log(email); // "updated@example.com"

同じ名前での宣言はエラーになりますが、値の変更は問題ありません。

// 異なるブロック(スコープ)なら再宣言可能
function letRedeclaration() {
let message = "外側のメッセージ";
console.log("外側:", message); // "外側のメッセージ"
if (true) {
let message = "内側のメッセージ"; // 異なるスコープなので可能
console.log("内側:", message); // "内側のメッセージ"
}
console.log("外側(再度):", message); // "外側のメッセージ"
}
letRedeclaration();

異なるブロック内なら、同じ変数名でも宣言できます。

実践的な使い方を学ぼう

ループでの正しい使い方

配列の要素にイベントリスナーを追加する例です。

// タブメニューの作成例
function createTabMenu() {
let tabs = ["ホーム", "サービス", "お問い合わせ"];
for (let index = 0; index < tabs.length; index++) {
let button = document.createElement("button");
button.textContent = tabs[index];
// 各ボタンが正しいインデックスを保持
button.addEventListener("click", function() {
console.log(`${tabs[index]}が選択されました`);
console.log(`インデックス: ${index}`);
});
document.body.appendChild(button);
}
}

letを使うことで、それぞれのボタンが正しいインデックスを覚えてくれます。

条件分岐での変数管理

ユーザーの年齢に応じて処理を変える例です。

function processUserData(user) {
if (user.age >= 18) {
let accessLevel = "adult";
let permissions = ["read", "write", "delete"];
console.log(`アクセスレベル: ${accessLevel}`);
console.log(`権限: ${permissions.join(", ")}`);
if (user.role === "admin") {
let adminPermissions = [...permissions, "admin"];
console.log(`管理者権限: ${adminPermissions.join(", ")}`);
// adminPermissionsはこのブロック内でのみ有効
}
} else {
let accessLevel = "minor";
let permissions = ["read"];
console.log(`アクセスレベル: ${accessLevel}`);
console.log(`権限: ${permissions.join(", ")}`);
}
// accessLevelやpermissionsはここではアクセス不可
}
// 使用例
processUserData({ age: 25, role: "user" });
processUserData({ age: 16, role: "user" });
processUserData({ age: 30, role: "admin" });

ブロックごとに適切な変数を使い分けられます。

現代JavaScriptでの推奨事項

letとconstを使おう

現代のJavaScriptでは、varの代わりにletとconstを使うことが推奨されています。

// 推奨:letとconstの使用
function modernVariableDeclaration() {
// 変更されない値はconst
const API_URL = "https://api.example.com";
const MAX_RETRY_COUNT = 3;
// 変更される値はlet
let currentRetry = 0;
let response = null;
// varは使用しない(レガシーコード以外)
while (currentRetry < MAX_RETRY_COUNT) {
try {
console.log(`試行回数: ${currentRetry + 1}`);
// 何らかの処理
break;
} catch (error) {
currentRetry++;
console.log(`リトライ ${currentRetry}/${MAX_RETRY_COUNT}`);
}
}
}

constは値が変わらない場合、letは値が変わる場合に使います。

使い分けのルール

以下のルールに従うと良いでしょう。

// 良い例
function goodPractice() {
const users = ["太郎", "花子", "次郎"]; // 配列自体は変更しない
let selectedUser = null; // 値が変更される
for (const user of users) { // ループ変数も変更しない
if (user === "花子") {
selectedUser = user;
break;
}
}
console.log("選択されたユーザー:", selectedUser);
}
// 避けるべき例
function badPractice() {
var users = ["太郎", "花子", "次郎"]; // var は使わない
var selectedUser = null; // var は使わない
for (var i = 0; i < users.length; i++) { // var は使わない
if (users[i] === "花子") {
selectedUser = users[i];
break;
}
}
console.log("選択されたユーザー:", selectedUser);
}

一貫してletとconstを使うことで、より安全なコードが書けます。

よくある間違いと対策

varとletの混在は避けよう

同じコードでvarとletを混在させると混乱の原因になります。

// 悪い例:varとletの混在
function mixedDeclaration() {
var oldStyle = "古い書き方";
let newStyle = "新しい書き方";
// 一貫性がなく、混乱の原因となる
for (var i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
console.log(i, j);
}
}
}
// 良い例:一貫してletとconstを使用
function consistentDeclaration() {
const message = "一貫した書き方";
let counter = 0;
for (let i = 0; i < 3; i++) {
for (let j = 0; j < 3; j++) {
counter++;
console.log(`${message}: ${i}, ${j} (${counter})`);
}
}
}

統一された書き方にすることで、読みやすいコードになります。

ホイスティングを理解しよう

ホイスティングを正しく理解することで、予期しないエラーを防げます。

// 正しい理解と対策
function safeVariableUsage() {
// 変数は使用前に必ず宣言・初期化
const config = {
apiUrl: "https://api.example.com",
timeout: 5000
};
let result = null;
let error = null;
try {
result = processData(config);
} catch (e) {
error = e;
}
if (error) {
console.log("エラーが発生しました:", error.message);
} else {
console.log("処理結果:", result);
}
}
function processData(config) {
return "処理完了"; // 模擬処理
}

変数を使う前に必ず宣言と初期化を行うことで、安全なコードが書けます。

パフォーマンスのメリット

ブロックスコープによるメモリ効率

letのブロックスコープを活用することで、メモリを効率的に使えます。

// ブロックスコープを活用したメモリ効率の向上
function processLargeData() {
const data = new Array(1000).fill(0).map((_, i) => i);
// 前処理ブロック
{
let tempArray = data.map(x => x * 2);
let processedData = tempArray.filter(x => x % 10 === 0);
console.log("前処理完了:", processedData.length);
// tempArrayはここでメモリから解放される
}
// メイン処理ブロック
{
let result = data.reduce((sum, value) => sum + value, 0);
console.log("合計:", result);
// resultはここでメモリから解放される
}
console.log("処理完了");
}

必要な時だけ変数を作って、不要になったらすぐに解放されるようにできます。

まとめ

JavaScriptのvarとletの違いについて学んできました。

主な違いをもう一度おさらいしましょう。

  • スコープ: var(関数全体)vs let(ブロック内のみ)
  • ホイスティング: var(undefined初期化)vs let(デッドゾーン)
  • 再宣言: var(可能)vs let(エラーになる)

実践的な使い方も覚えておきましょう。

  • ループ処理ではletを使う
  • 条件分岐でのブロックスコープを活用
  • constとletを適切に使い分ける
  • varの使用は避ける

現代のベストプラクティスとして以下を推奨します。

  • 新しいコードではvarを使わない
  • 値が変わらない場合はconst
  • 値が変わる場合はlet
  • 一貫した書き方を心がける

varとletの違いを理解することで、より安全で読みやすいJavaScriptコードが書けるようになります。 最初は少し複雑に感じるかもしれませんが、慣れれば自然に使い分けられるようになりますよ。

ぜひ今度のプロジェクトで、letとconstを使ってみませんか?

関連記事