JavaScriptの基礎 - 変数宣言

2026年2月18日 (水) 20:41時点におけるWiki (トーク | 投稿記録)による版 (ページの作成:「== 概要 == JavaScriptにおける変数宣言には、<code>var</code>、<code>let</code>、<code>const</code> の3つのキーワードが存在する。<br> <br> <code>var</code> はJavaScript誕生当初から存在するキーワードであり、関数スコープまたはグローバルスコープで変数を宣言する。<br> 一方、<code>let</code> と <code>const</code> は2015年に策定されたES2015 (ES6) で新たに導入されたキーワー…」)
(差分) ← 古い版 | 最新版 (差分) | 新しい版 → (差分)

概要

JavaScriptにおける変数宣言には、varletconst の3つのキーワードが存在する。

var はJavaScript誕生当初から存在するキーワードであり、関数スコープまたはグローバルスコープで変数を宣言する。
一方、letconst は2015年に策定されたES2015 (ES6) で新たに導入されたキーワードであり、ブロックスコープを持つ。

これら3つのキーワードは、スコープ、ホイスティング (変数の巻き上げ)、再宣言・再代入の可否という3つの観点で大きく異なる。
var はブロックスコープを持たないため、ループや条件分岐の内部で宣言した変数が外部に漏れ出し、予期しない動作を引き起こす問題がある。
また、宣言前のアクセスが undefined を返すという挙動も、バグの温床となりやすい。

letconst はこれらの問題を解消するために設計されており、現代的なJavaScript開発ではこの2つを用いることが標準となっている。
原則として const をデフォルトの選択肢として使用し、再代入が必要な場面でのみ let を使用することが広く推奨されている。

var は新規開発では使用せず、既存のレガシーコードを読み解く時の知識として理解しておくことが求められる。


let

基本的な使用方法

let はブロックスコープを持ち、再代入可能な変数を宣言するキーワードである。
初期値なしで宣言することも、宣言と同時に初期値を設定することも可能である。

 let name;
 let name = value;
 let name1 = value1, name2 = value2;


使用例を以下に示す。

 let count = 0;
 count = 1;           // 再代入可能
 console.log(count);  // 1
 
 let message;
 message = "Hello, World!";
 console.log(message);  // "Hello, World!"


再代入と再宣言

let は再代入が可能であるが、同一スコープ内での再宣言は SyntaxError となる。

 let x = 1;
 x = 2;           // OK : 再代入は可能
 console.log(x);  // 2
 
 let x = 3;       // SyntaxError: Identifier 'x' has already been declared


ただし、異なるブロックスコープでは同じ名前の変数を宣言できる。

 const x = "outer";
 {
    const x = "inner"; // OK : 別のブロックスコープ
    console.log(x);    // "inner"
 }
 console.log(x);       // "outer"


ブロックスコープ

let はブロックスコープを持つ。
ifforwhile{} 等のブロック内で宣言した変数は、そのブロック外からアクセスできない。

 function letTest() {
    let x = 1;
    {
       let x = 2;       // 別のブロックスコープの変数
       console.log(x);  // 2
    }
    console.log(x);     // 1
 }
 
 for (let i = 0; i < 3; i++) {
    console.log(i);     // 0, 1, 2
 }
 
 console.log(i);        // ReferenceError: i is not defined


また、let はトップレベルで宣言してもグローバルオブジェクト (globalThis) のプロパティにはならない。

 var globalVar = "var";
 let globalLet = "let";
 
 console.log(globalThis.globalVar);  // "var"
 console.log(globalThis.globalLet);  // undefined



const

基本的な使用方法

const はブロックスコープを持ち、再代入不可の定数を宣言するキーワードである。

const は宣言と同時に必ず初期値を指定しなければならない。
初期化を省略すると SyntaxError となる。

 const name = value;
 
 const PI = 3.14159;
 const MAX_SIZE = 100;
 const APP_NAME = "MyApp";
 
 const FOO;  // SyntaxError: Missing initializer in const declaration


再代入の禁止

const で宣言した変数への再代入は TypeError となる。

 const PI = 3.14159;
 PI = 3.14;   // TypeError: Assignment to constant variable.


同一スコープ内での再宣言も SyntaxError となる。

 const y = 1;
 const y = 2;  // SyntaxError: Identifier 'y' has already been declared


constとオブジェクト (参照の不変性)

const が保証するのは変数への参照の不変性のみである。
参照先のオブジェクトや配列の中身 (プロパティや要素) は変更できる。

 const obj = {
    name: "Alice",
    age: 30
 };
 
 obj.name = "Bob";  // OK : プロパティの変更は可能
 obj.age  = 31;     // OK
 
 obj = { name: "Charlie" };  // TypeError - 再代入は不可
 
 const arr = [1, 2, 3];
 arr[0] = 99;       // OK : 要素の変更は可能
 arr.push(4);       // OK : メソッドの呼び出しは可能
 arr = [4, 5, 6];   // TypeError : 再代入は不可


Object.freeze

オブジェクトの中身を変更させたくない場合は、Object.freeze() を使用する。
Object.freeze() を適用すると、プロパティの追加・削除・変更がすべて禁止される。

ただし、Object.freeze() は浅いフリーズ (Shallow Freeze) であるため、ネストされたオブジェクトや配列は保護されない。

 const frozenObj = Object.freeze({
    name: "Alice",
    age: 30
 });

 frozenObj.name = "Bob";       // 失敗 (strict mode では TypeError)
 frozenObj.newProp = "value";  // 失敗 : プロパティの追加も不可
 console.log(frozenObj.name);  // "Alice"
 
 // 浅いフリーズの制限: ネストされたオブジェクトは変更可能
 const obj = Object.freeze({
    level1: {
       level2: "value"
    }
 });
 
 obj.level1.level2 = "newValue";  // OK : ネストされたオブジェクトは保護されない
 console.log(obj.level1.level2);  // "newValue"


ネストされたオブジェクトも含めて完全に不変にするには、再帰的にフリーズする深いフリーズ (Deep Freeze) を実装する。

 function deepFreeze(object) {
    const propNames = Reflect.ownKeys(object);
 
    for (const name of propNames) {
       const value = object[name];
 
       if ((value && typeof value === "object") || typeof value === "function") {
          deepFreeze(value);
       }
    }
 
    return Object.freeze(object);
 }
 
 const deepFrozenObj = deepFreeze({
    level1: {
       level2: "value"
    }
 });
 
 deepFrozenObj.level1.level2 = "newValue"; // 失敗
 console.log(deepFrozenObj.level1.level2);  // "value"


ブロックスコープ

const のスコープは、let と同様にブロックスコープである。

 const MY_FAV = 7;
 
 if (MY_FAV === 7) {
    const MY_FAV = 20; // 新しいブロックスコープ内の別の変数
    console.log(MY_FAV); // 20
 }
 
 console.log(MY_FAV); // 7



var

基本的な使用方法

var は関数スコープまたはグローバルスコープで変数を宣言するキーワードである。

ES2015以前から存在する古いキーワードであり、全てのWebブラウザで広くサポートされている。

 var name;
 var name = value;
 var name1 = value1, name2 = value2;


var は同一スコープ内での再宣言が可能であり、エラーにはならない。

 var a = 1;
 var a = 2;       // エラーにならない
 console.log(a);  // 2


関数スコープ

var は関数スコープを持つ。

ブロック (ifforwhile 等) は、var のスコープを形成しない。

ブロック内で宣言した var 変数は、そのブロックの外からもアクセスできる。

 function functionScope() {
    if (true) {
       var x = "inside";
    }
    console.log(x); // "inside" - if ブロック外でもアクセス可能
 }
 functionScope();
 
 for (var i = 0; i < 3; i++) {
    console.log(i); // 0, 1, 2
 }
 console.log(i); // 3 - ループ外でもアクセス可能


また、トップレベルでの var 宣言はグローバルオブジェクト (globalThis) のプロパティとして追加される。

 var x = 1;
 console.log(globalThis.x);  // 1
 
 delete x;                   // 削除できない (strict modeでは、TypeError)
 console.log(globalThis.x);  // 1


varの問題点

var の使用はいくつかの問題を引き起こすため、現代的なJavaScript開発では使用が推奨されない。

ループでのクロージャ問題を以下に示す。
var はブロックスコープを持たないため、ループ内でクロージャを使用すると、全ての関数が同じ変数を参照してしまう。

 // 問題のあるコード (varを使用)
 var funcs = [];
 for (var i = 0; i < 3; i++) {
    funcs.push(function() {
       console.log(i);
    });
 }
 
 funcs[0]();  // 3 (期待値: 0)
 funcs[1]();  // 3 (期待値: 1)
 funcs[2]();  // 3 (期待値: 2)
 
 // 解決策: letを使用
 let funcs2 = [];
 for (let i = 0; i < 3; i++) {
    funcs2.push(function() {
       console.log(i);
    });
 }
 
 funcs2[0]();  // 0
 funcs2[1]();  // 1
 funcs2[2]();  // 2


変数の巻き上げによる予期しない動作を以下に示す。
var はホイスティングにより undefined で初期化されるため、宣言前にアクセスしてもエラーにならず、デバッグが困難になる。

 function unexpected() {
    console.log(x); // undefined (エラーにならない)
    if (false) {
       var x = 5;   // このブロックは実行されないが、宣言は巻き上げられる
    }
    console.log(x); // undefined
 }


グローバルオブジェクトへのプロパティ追加を以下に示す。
トップレベルの var 宣言はグローバルオブジェクトにプロパティとして追加されて、グローバル汚染を引き起こす可能性がある。

 var message = "global";
 console.log(window.message);  // "global" : ブラウザ環境
 
 let safe = "no pollution";
 console.log(window.safe);     // undefined



スコープの比較

下表に、letconstvar のスコープの違いを示す。

スコープ比較表
キーワード スコープの種類 ブロック内での宣言 グローバルオブジェクトへの追加
var 関数スコープ / グローバルスコープ ブロック外からアクセス可能 あり
let ブロックスコープ ブロック外からアクセス不可 なし
const ブロックスコープ ブロック外からアクセス不可 なし


スコープの挙動を示すコード例を以下に示す。

 // var: 関数スコープ
 function testVar() {
    if (true) {
       var x = "var";
    }
    console.log(x); // "var": アクセス可能
 }
 
 // let: ブロックスコープ
 function testLet() {
    if (true) {
       let y = "let";
    }
    console.log(y);  // ReferenceError: y is not defined
 }
 
 // const: ブロックスコープ
 function testConst() {
    if (true) {
       const z = "const";
    }
    console.log(z);  // ReferenceError: z is not defined
 }



ホイスティング

ホイスティング (変数の巻き上げ) とは、JavaScriptエンジンがプログラムの実行前に変数・関数・クラスの宣言を処理する仕組みのことである。

変数の使用箇所よりも前に宣言が処理されるが、varlet / const では挙動が大きく異なる。

varのホイスティング

var の宣言は、関数またはグローバルスコープの先頭に巻き上げられ、自動的に undefined で初期化される。
そのため、宣言前に変数にアクセスしてもエラーにはならず、undefined が返される。

 console.log(x);  // undefined (エラーではない)
 var x = 5;
 console.log(x);  // 5


上記のコードは、JavaScriptエンジンによって内部的に以下のように処理される。

 var x;           // ホイスティング: undefinedで初期化
 console.log(x);  // undefined
 x = 5;           // 代入
 console.log(x);  // 5


let / constのホイスティングとTDZ

letconst も技術的にはホイストされるが、undefined では初期化されない。
代わりに、ブロックの開始から宣言文に到達するまでの間、Temporal Dead Zone (TDZ : 一時的デッドゾーン) と呼ばれる状態に置かれる。

TDZの期間中に変数にアクセスすると ReferenceError が発生する。

 // letのTDZ
 {
    console.log(x); // ReferenceError: Cannot access 'x' before initialization
    let x = 5;
 }
 
 // constのTDZ
 {
    console.log(y); // ReferenceError: Cannot access 'y' before initialization
    const y = 10;
 }
 
 // varとの比較
 {
    console.log(z); // undefined (エラーではない)
    var z = 15;
 }


TDZは、外側のスコープに同名の変数が存在する場合にも注意が必要である。
内側のブロックで let / const を宣言すると、そのブロック全体がTDZの影響を受ける。

 function test() {
    var foo = 33;
    if (foo) {
       let foo = foo + 55; // ReferenceError
       // 右辺の foo は内側のブロックの TDZ 内にあるためアクセスできない
    }
 }


下表に、ホイスティングの挙動比較を示す。

ホイスティング挙動比較表
キーワード ホイスティング 初期値 宣言前のアクセス
var あり undefined undefined を返す
let あり (TDZ) 初期化されない ReferenceError
const あり (TDZ) 初期化されない ReferenceError



let / const / var の比較

letconstvar の特性を総合的に比較した表を以下に示す。

let / const / var の総合比較表
特性 var let const
スコープ 関数 / グローバル ブロック ブロック
ホイスティング あり (undefined で初期化) あり (TDZ) あり (TDZ)
宣言前のアクセス undefined を返す ReferenceError ReferenceError
再代入 可能 可能 不可
再宣言 可能 不可 不可
初期化の省略 可能 可能 不可 (SyntaxError)
グローバルオブジェクトへの追加 あり なし なし
導入バージョン ES1 (初期) ES2015 (ES6) ES2015 (ES6)



推奨される使用方法

現代的なJavaScript開発では、以下の原則に従って変数宣言のキーワードを選択することが推奨される。

  • const をデフォルトとして使用する。
    変数が再代入される可能性がない場合は常に const を使用する。
    これにより、意図しない再代入を防ぎ、コードの意図を明確に表現できる。
    多くのスタイルガイド (Airbnb、Google等) でも const の優先使用が推奨されている。

  • 再代入が必要な場合のみ、let を使用する
    カウンタ変数、条件によって値が変わる変数等、再代入が必要な場合にのみ let を使用する。

  • var は使用しない。
    新規開発では var を使用しない。
    レガシーコードや古い環境向けのコードを読む際の知識として理解しておく。


推奨される使用例を以下に示す。

 // const をデフォルトとして使用
 const PI = 3.14159;
 const API_URL = "https://api.example.com";
 const config = {
    debug: true,
    timeout: 5000
 };
 
 // 再代入が必要な場面でletを使用
 let count = 0;
 while (count < 10) {
    count++;
 }
 
 let result;
 if (someCondition) {
    result = "value1";
 } else {
    result = "value2";
 }
 
 // ループのカウンタは let を使用
 for (let i = 0; i < arr.length; i++) {
    console.log(arr[i]);
 }


ESLintを使用している場合、no-var ルールで var の使用を禁止し、prefer-const ルールで const の優先使用を自動的に検出することができる。


関連情報