JavaScript 入門

JavaScript 変数宣言

JavaScriptでは、特定のキーワードを使用して変数(Variable)を宣言します。それが varlet、そして const です。

これら3つのキーワードにはそれぞれ独自の特性があり、変数の振る舞いに影響を与えます。これには、スコープ(どこからアクセスできるか)や、その値が変更可能かどうかが含まれます。

1. JavaScriptにおける変数の理解

1.1 変数とは何か?

本質的に、JavaScriptにおける変数は、値を保持しているメモリ上の場所に付けられた「シンボル名(名前)」です。

ラベルが貼られた「箱」を想像してください。箱の中に異なるアイテム(値)を入れることができ、中身はいつでも自由に変更できます。箱に貼られたラベル(変数名)によって、プログラム全体でその特定のアイテムを参照できるようになります。

例えば、誰かに挨拶するとき、「こんにちは、Alex!」や「こんにちは、Sarah!」と言うかもしれません。ここで「Alex」や「Sarah」は変化する情報の断片であり、「こんにちは、!」は固定された部分です。変数は、プログラムが「Alex」や「Sarah」を保存し、それを挨拶文の中に挿入することを可能にします。

// 'userName' という名前の変数に、文字列 "Alex" を格納
let userName = "Alex";
console.log("こんにちは、" + userName + "!"); // 出力: こんにちは、Alex!

// 'userName' に別の値を格納
userName = "Sarah";
console.log("こんにちは、" + userName + "!"); // 出力: こんにちは、Sarah!

1.2 なぜ変数が必要なのか?

変数が不可欠である理由はいくつかあります:

  • データの保存: ユーザー入力、計算結果、設定情報など、プログラムが情報を「記憶」しておくことを可能にします。
  • コードの動的化: 同じ値を繰り返し書くのではなく、変数を使用します。値が変わった場合(ユーザーのスコアなど)、変数の値を一度更新するだけで、その変数を参照しているすべてのコードに新しい値が適用されます。
  • 可読性の向上: 変数に意味のある名前(x ではなく userName など)を付けることで、自分自身や他の開発者にとってコードの内容が理解しやすくなります。
  • 再利用性: 一度データが変数に保存されれば、スクリプト内で何度も再利用でき、その値を再入力する手間が省けます。

2. var、let、const による変数宣言

JavaScriptは、変数を宣言するために3つのキーワードを提供しています:varlet、そして const です。これらはすべて変数を作成するために使用されますが、スコープ、ホイスティング(Hoisting/巻き上げ)再代入再宣言の可否において顕著な違いがあります。これらの違いを理解することは、モダンなJavaScript開発において極めて重要です。

2.1 var キーワード(旧式/レガシー)

ES6(ECMAScript 2015)が導入される前、var はJavaScriptで変数を宣言する唯一の方法でした。現在でも使用可能ですが、var の挙動は予期せぬ問題を引き起こす可能性があるため、より安全で予測可能な letconst が導入されました。モダンなJavaScript開発では、通常 var の使用は推奨されません。

2.1.1 宣言と代入

var 変数を宣言し、オプションで値を代入できます。

var oldSchoolName; // 宣言:変数 'oldSchoolName' が作成され、値は undefined になる
oldSchoolName = "Gandalf"; // 代入:'Gandalf' が 'oldSchoolName' に格納される
console.log(oldSchoolName); // 出力: Gandalf

var favoriteColor = "blue"; // 一行で宣言と代入を行う
console.log(favoriteColor); // 出力: blue

2.1.2 関数スコープ (Function Scope)

var で宣言された変数は関数スコープを持ちます。これは、その変数が宣言された関数内のどこからでもアクセスできることを意味します。たとえ if 文や for ループなどのブロック内で宣言されていても関係ありません。関数の外で宣言された場合、それはグローバル変数となり、スクリプトのどこからでもアクセス可能になります。

var globalMessage = "グローバルスコープからの挨拶!";

function greetingFunction() {
    var functionMessage = "関数スコープからの挨拶!";
    
    if (true) {
        var blockMessage = "関数内のブロックからの挨拶!";
        console.log(functionMessage); // アクセス可能
        console.log(blockMessage); // アクセス可能
    }
    
    console.log(functionMessage); // アクセス可能
    console.log(blockMessage); // ここでもアクセス可能!(驚くかもしれません)
}

greetingFunction();
console.log(globalMessage); // アクセス可能
// console.log(functionMessage); // エラー: functionMessage は未定義 (関数スコープの外)
// console.log(blockMessage);    // エラー: blockMessage は未定義 (関数スコープの外)

blockMessageif ブロックの外、かつ greetingFunction の内部で依然としてアクセス可能である点は、var の関数スコープにおける大きな特徴です。

2.1.3 var のホイスティング (Hoisting)

var で宣言された変数は、そのスコープの最上部へ「ホイスティング(巻き上げ)」されます。JavaScriptエンジンはコードを実行する前に var による宣言を処理します。ただし、宣言のみが巻き上げられ、代入は巻き上げられません。代入前に変数にアクセスすると、値は undefined になります。

console.log(myVar); // 出力: undefined
var myVar = 10;
console.log(myVar); // 出力: 10

// JavaScript 内部では以下のように処理されています:
// var myVar;             // 宣言がトップに巻き上げられる
// console.log(myVar);    // この時点で myVar は存在するが代入されていないため 'undefined'
// myVar = 10;            // ここで代入が行われる
// console.log(myVar);

2.1.4 var による再宣言と再代入

var 変数は、同じスコープ内でエラーなく簡単に再宣言および再代入が可能です。この柔軟性は便利なこともありますが、意図せず変数を上書きしてしまう原因となり、デバッグを困難にします。

var studentName = "Alice";
console.log(studentName); // 出力: Alice

var studentName = "Bob"; // 再宣言 (var の場合は合法)
console.log(studentName); // 出力: Bob

studentName = "Charlie"; // 再代入 (これも合法)
console.log(studentName); // 出力: Charlie

モダンな開発においては、関数スコープや再宣言の許可といった落とし穴を避けるため、var の使用を避けるのがベストプラクティスです。

2.2 let キーワード(モダンな慣習)

ES6 で導入された let は、値が変更される可能性のある変数を宣言するための推奨される方法です。var が抱えていた多くの欠点を解決しています。

2.2.1 宣言と代入

var と同様に、let 変数を宣言して代入できます。

let gameScore; // 宣言
gameScore = 0; // 代入
console.log(gameScore); // 出力: 0

gameScore = 100; // 再代入 (let では合法)
console.log(gameScore); // 出力: 100

let playerName = "PlayerOne"; // 宣言と代入
console.log(playerName); // 出力: PlayerOne

2.2.2 ブロックレベルスコープ (Block Scope)

let で宣言された変数はブロックスコープを持ちます。これは、変数が定義された中括弧 {} の中、あるいはその中にネストされたブロック内でのみアクセス可能であることを意味します。これは var の関数スコープと比較して大きな改善であり、意図しない変数のリーク(漏洩)を防ぎ、コードをより予測可能にします。

let globalValue = 50;

if (true) {
    let blockScopedValue = 20;
    console.log(globalValue);       // アクセス可能
    console.log(blockScopedValue);  // このブロック内ではアクセス可能
}

console.log(globalValue);       // アクセス可能
// console.log(blockScopedValue); // エラー: blockScopedValue は未定義 (ブロックスコープの外)

このブロックスコープの挙動は、コードの異なる部分で同じ変数名を使用した際の衝突を避けるために極めて重要です。

2.2.3 let のホイスティング(と一時的死区)

let 変数もホイスティングされますが、var とは挙動が異なります。宣言は巻き上げられますが、初期化はされません。そのため、宣言前に let 変数にアクセスしようとすると、JavaScriptは ReferenceError をスローします。スコープの開始から変数宣言までのこの期間は、一時的死区(Temporal Dead Zone, TDZ)と呼ばれます。

// console.log(itemCount); // ReferenceError: 初期化前に 'itemCount' にアクセスできません
let itemCount = 5;
console.log(itemCount); // 出力: 5

TDZ は、変数を使用する前に必ず宣言することを強制する安全メカニズムであり、var でよく見られた「意図しない undefined」を防ぎます。

2.2.4 再宣言の禁止、再代入の許可

同じスコープ内で、let 変数を再宣言することはできません。しかし、新しい値を再代入することは可能です。これにより、誤った上書きを防ぎつつ、動的なデータ更新を許可するというバランスが保たれています。

let productPrice = 9.99;
console.log(productPrice); // 出力: 9.99

// let productPrice = 12.50; // エラー: 識別子 'productPrice' は既に宣言されています
// これにより、意図しない再宣言を防ぎます。

productPrice = 12.50; // 再代入 (合法)
console.log(productPrice); // 出力: 12.50

2.3 const キーワード(モダンな慣習における定数)

同じく ES6 で導入された const は、定数(Constant)を宣言するために使用されます。const 変数は宣言時に値を初期化する必要があり、その値を後から変更(再代入)することはできません。

2.3.1 宣言と強制的な代入

const 変数は宣言と同時に値を代入しなければなりません。そうしないとエラーになります。

// const PI; // エラー: const 宣言に初期化子がありません
const PI = 3.14159; // 宣言と同時に代入
console.log(PI); // 出力: 3.14159

const WEBSITE_URL = "https://www.example.com";
console.log(WEBSITE_URL); // 出力: https://www.example.com

2.3.2 ブロックレベルスコープ (Block Scope)

let と同様に、const 変数もブロックスコープを持ちます。定義された {} 内でのみ有効です。

const appName = "MyCoolApp";

if (true) {
    const adminUser = "Admin001";
    console.log(appName);    // アクセス可能
    console.log(adminUser);  // このブロック内ではアクセス可能
}

console.log(appName);    // アクセス可能
// console.log(adminUser); // エラー: adminUser は未定義

2.3.3 const のホイスティング(と一時的死区)

const 変数もホイスティングされますが、let と同様に TDZ の影響を受けます。宣言前にアクセスすることはできません。

// console.log(MAX_ATTEMPTS); // ReferenceError
const MAX_ATTEMPTS = 3;
console.log(MAX_ATTEMPTS); // 出力: 3

2.3.4 再宣言および再代入の禁止

const の最大の特徴は、一度代入されると再代入ができない(および同スコープ内での再宣言もできない)ことです。プログラムの実行中に変更されるべきではない固定値に最適です。

const TAX_RATE = 0.07;
console.log(TAX_RATE); // 出力: 0.07

// TAX_RATE = 0.08; // エラー: 定数への代入です
// これにより、固定値の不慮の変更を防ぎます。

2.3.5 重要なニュアンス:const とオブジェクト・配列

const は変数自体の再代入を防ぎますが、オブジェクトや配列の中身までイミュータブル(不変)にするわけではないという点に注意が必要です。

const 変数がオブジェクトや配列を保持している場合、オブジェクトのプロパティを変更したり、配列の要素を追加・削除したりすることは可能です。変数が指しているメモリ上の場所(参照先)は変わりませんが、その場所にあるデータの内容は変更できます。

const userProfile = {
    name: "Jane Doe",
    age: 30
};
console.log(userProfile); // 出力: { name: 'Jane Doe', age: 30 }

userProfile.age = 31; // 合法: オブジェクトのプロパティは変更可能
console.log(userProfile); // 出力: { name: 'Jane Doe', age: 31 }

// userProfile = { name: "John Doe", age: 25 }; // エラー: 定数への代入
// 変数 'userProfile' 自体を別のオブジェクトに差し替えることはできません。

3. var、let、const のどれを選ぶべきか?

現代のJavaScript開発における選択基準は非常にシンプルです:

  1. 常に const を優先する: デフォルトで const を使用してください。初期化後に値を変更する必要がない場合、const が最も安全で明確な選択です。これは他の開発者(そして将来の自分)に対して「この値は固定である」という意図を伝えます。
  2. 再代入が必要な場合のみ let を使用する: ループのカウンタ、スコアの更新、一時的な計算結果など、後で値を更新することが分かっている場合にのみ let を使用します。
  3. var は避ける: スコープやホイスティングが混乱を招くため、var はレガシー(過去の遺物)と見なされます。古いコードで見かけることはありますが、新しい開発では letconst を使いましょう。

3.1 特徴まとめ表

特徴varletconst
スコープ関数スコープブロックスコープブロックスコープ
再宣言可能不可不可
再代入可能可能不可
ホイスティングあり (undefined 初期化)あり (TDZ発生)あり (TDZ発生)
初期化任意任意必須

4. 実践的な例とデモンストレーション

実務で varletconst がどのように使い分けられるか、いくつかのシナリオで確認しましょう。

4.1 示例 1:ループカウンタでの let(ブロックスコープの確認)

ループ(詳細は後のモジュールで学びます)において、let のブロックスコープは非常に有用です。let で宣言された変数 i は、for ループの中括弧内でのみ存在します。

// ループカウンタとして let を使用
for (let i = 0; i < 3; i++) {
    console.log("現在のイテレーション (ループ内): " + i);
}
// console.log("現在のイテレーション (ループ外): " + i); // ReferenceError: i は未定義

もし var を使った場合:

// var を使用した場合 (古い方法)
for (var j = 0; j < 3; j++) {
    console.log("現在のイテレーション (var ループ内): " + j);
}
console.log("現在のイテレーション (var ループ外): " + j); // 出力: 3
// var だと 'j' がループの外に漏れ出し、アクセスできてしまいます。

4.2 示例 2:設定値には const

アプリの設定や実行中に変わるべきではない値には const を使います。

const APP_TITLE = "マイ・すごい・アプリ";
const API_KEY = "sk-xxxxxxxxxxxxxxxxxxxx";
const DEFAULT_LANGUAGE = "ja";

console.log(`${APP_TITLE} へようこそ!`);
// API_KEY = "new-key"; // エラーを防いでくれる

4.3 示例 3:ユーザー入力や可変状態には let

状態(ステータス)や一時的な計算など、変化が予想される値には let を使います。

let userStatus = "オンライン";
console.log("初期状態: " + userStatus);

userStatus = "離席中"; // ユーザーの操作で状態が変わる
console.log("更新後の状態: " + userStatus);

4.4 示例 4:ホイスティングのデモ(なぜ var は問題なのか)

var の巻き上げがどのように undefined を引き起こし、バグを隠蔽するかを示します。

function processData() {
    console.log("宣言前のデータ:", data); // 出力: undefined (巻き上げにより存在はしている)
    
    var data = "重要データ";
    console.log("宣言後のデータ:", data); // 出力: 重要データ
    
    var result = 10;
    if (true) {
        var result = 20; // 関数スコープのため、外側の result を上書きしてしまう
    }
    console.log("ブロック外の結果:", result); // 出力: 20
}
processData();

let を使ったモダンな書き方:

function processDataModern() {
    // console.log(modernData); // ReferenceError (安全)
    
    let modernData = "重要データ";
    
    let result = 10;
    if (true) {
        let result = 20; // 別のブロックスコープなので新しい変数として扱われる
        console.log("ブロック内の結果:", result); // 出力: 20
    }
    console.log("ブロック外の結果:", result); // 出力: 10 (上書きされない!)
}
processDataModern();

5. 結論

本稿では、変数宣言のための3つのキーワード varletconst について学びました。

  • var はレガシーなキーワードであり、関数スコープや寛容な再宣言・再代入といった特性が予期せぬ動作を招くことがあります。
  • let は、再代入が必要な変数のための現代的な選択肢です。ブロックスコープを提供し、変数の漏洩を防ぎ、安全なコード記述を助けます。
  • const は、初期化後に値を変更しない変数に最適なキーワードです。ブロックスコープを持ち、再代入を禁止します。

原則として、常に const を使用し、どうしても値の更新が必要な場合にのみ let を使うように心がけることで、より堅牢でメンテナンス性の高いJavaScriptコードを書くことができます。