作成日:2025/4/23,最終更新日:2025/5/6

JavaScript / TypeScriptの変数と定数について

変数と定数に関する解説

この記事では、変数と定数とは何かについて詳しく解説します。


1. 変数と定数とは?

解説: 変数と定数は、プログラム上でデータ入れるための箱のことです。
この箱には名前をつけて(変数宣言)、中身(データ)を1つ入れることができます。
以下に例を示します。

// 変数の宣言
let a = 1;       // a という変数に 1 を入れる
let b = 3;       // b という変数に 3 を入れる

// 定数の宣言
const c = 5;      // c という定数に 5 を入れる
const d = 7;      // d という定数に 7 を入れる

変数 (let) は、あとから中身を変更(再代入)できますが、
定数 (const) は、あとから中身を変更(再代入)できません

// 代入時の挙動の違い
let x = 10;
x = 20;     // OK

const y = 30;
y = 40; // エラー(再代入できない)

ちなみに、意外と知らない人も多いですが、constで定義した配列やオブジェクトの中身は変更できます!
このあたりは、最初に戸惑うポイントなので、ぜひおさえておきましょう!

// 配列やオブジェクトの中身の変更例
// 配列の例
const list = [1, 2, 3];
list.push(4);           // OK 配列の中身の一部の変更は、できます
list[0] = 5;            // OK 配列の中身の一部の変更は、できます
list = [5, 6];          // NG 配列を丸々代入は、できません

// オブジェクトの例
const table = [
{ x: 10, y: 20 },
{ x: 30, y: 40 },
];

table[0].x = 50;                 // OK オブジェクトの中身の一部の変更は、できます
table = [ { x: 100, y: 200 } ];  // NG オブジェクトを丸々代入は、できません

基本的には「const」を使用して、必要な時に「let」を使用するのが安全な書き方と言えます。


2. varについて

解説: varは、letと同じJavaScriptで使用できる変数のことです。
ただ、letとは違う良くない点がたくさんあるため、現在では、使用しないことが推奨されます。
※ES2015で、「let」や「const」が導入されるまでは、「var」しか使えませんでした。

1つ目の理由は、変数の宣言を忘れても自動的にグローバル変数になってしまう点にあります。

// 宣言の省略例
greet = "Hello World!";    // 変数の宣言を省略
console.log(greet);        // 結果 : Hello World!
上記の様に1行目において変数宣言は省略されているものの、「greet」に値が代入された時点で、 暗黙的にvarによる宣言がされたとみなされます。
しかし、省略した場合はすべてグローバル変数(どこでも代入可能な変数)になるという性質があるため、省略は避けるべきでしょう。
// グローバル変数とは?
scope = "global";       // varやletを付けずに変数を使用している

function value() {
  scope = "local";      // グローバル変数の値を上書きしてしまう
  console.log(scope);
}

value();               // 結果 : local
console.log(scope);    // 結果 : local
関数内で変数を使用すると、本来はローカル変数(その関数内だけの変数)として扱われるため他に影響をおよぼしませんが、 今回は、「var」を省略して変数を使用しているため、グローバル変数の更新として扱われてしまう事になります。
そのため、上記の例は、関数内外どちらの変数scopeを参照してもlocalという値が出力されることになっています。

2つ目の理由は、変数名の重複が許可されてしまう点にあります。
// 変数名の重複を許してしまう
var value1 = 1;
var value1 = 2;       // 変数valueの値の更新

console.log(value1);  // 結果 : 2

let value2 = 1;
let value2 = 2;       // 同名の変数のため宣言が許可されない

console.log(value2);  // 結果 : エラー
varでは同名の変数の宣言は許可され、変数valueの値が2に更新されます。
しかし、letの場合は同じ名前の変数を再宣言しようとすると、実行時に「変数〇〇はすでに宣言されています」というエラーが発生します。
つまり、varは、別の個所で変数宣言しても問題なく使用できてしまい、意図しない値の変更が行われることで、エラーを発生させる原因となってしまうのです。

3つ目の理由は、ブロックスコープを認識してくれない点にあります。
// ブロックスコープの認識
if (true) {
  var value1 = 1;    // ブロックスコープは認識されていない
}
console.log(value1); // 結果 : 1


if (true) {
  let value2 = 2;    // ブロックスコープは認識されている
}
console.log(value2); // 結果 : エラー
従来のJavaScriptには、他の言語と違ってブロックスコープという概念が存在しませんでした。
そのため、ブロック内で「var」を使って変数の宣言を行った場合、ブロックスコープは認識されず、value1はコード全体で参照可能となるため、1が出力されます。
しかし、「let」はブロックスコープを認識してくれるため、value2の参照できる範囲はブロック({})内のみとなるため、グローバル変数として参照するとエラーが表示されます。
※「const」も同様にブロックスコープを認識してくれます。

3. 命名規則とNGワード(予約語)

解説: 変数と定数の名前に使用できる文字は、以下の通りです。

英字「a~z、A~Z」
数字「0~9」
_(アンダースコア)

上記の文字(アルファベット・数字・アンダースコア)のみを使用していれば、変数名や関数名として自由に命名できます。
ただし、数字から始まる名前は文法上認められておらず、構文エラー(SyntaxError)になります。
○:array1
✕:1array

JavaScriptでは、変数や関数名に使用できない「予約語」が多数定義されています。以下はその代表的な例です。
予約語は、JavaScript内で何らかの他の処理に使用している単語で、これを命名に使用するとエラーとなります。

通常の予約語


以下を命名の際に、誤って使わないように気を付けましょう!

予約語用法の説明
await非同期関数内でPromiseの結果を待つ ※only inside async functions and modules
breakループやswitch文から抜け出す
caseswitch文の中で条件を指定する
catch例外が投げられた時に処理をキャッチする
classクラスを宣言する
const再代入不可能な定数を宣言する
continueループの次の反復へ進む
debuggerデバッグ中に実行を一時停止する
defaultswitch文で、いずれのcaseにも一致しない場合の処理を定義する
deleteオブジェクトのプロパティを削除する
dodo...whileループを制御する
elseif文の条件が偽の場合に実行する
enum将来的な拡張のために予約されているが、JavaScriptでは未使用
exportモジュールから値や関数をエクスポートする
extendsクラスが他のクラスを継承する
false論理値の偽(false)を表す
finally例外の有無に関わらず実行される処理を定義する
forループ処理を制御する
function関数を宣言する
if条件が真の場合に実行される
importモジュールから値や関数をインポートする
in左辺のプロパティが右辺のオブジェクト内に存在するかを確認する
instanceofオブジェクトが特定のクラスのインスタンスかを判定する
new新しいインスタンスを生成する
nullnull値を表す
return関数から値を返す
super親クラスのコンストラクタやメソッドを参照する
switch複数の条件分岐を管理する
this現在のオブジェクトを参照する
throw例外を投げる
true論理値の真(true)を表す
try例外が発生する可能性のあるコードブロックを試行する
typeof値の型を返す
var変数を宣言する
void式の結果を無視してundefinedを返す
while条件が真の間ループする
withスコープチェーンにオブジェクトを一時的に追加する
yieldジェネレータ関数内で中断し値を外部に送出する

strict modeのみの予約語


JSには strict mode(厳格モード)というものがあります。
これを有効にすると、予約語が追加されます(以下の語が命名に使えなくなる)。
※公式の分類では strict mode 時にのみ予約語とされていますが、let はstrict modeでなくても予約語のように扱われ、命名に使えません。

予約語用法の説明
letブロックスコープの変数を宣言する
staticクラスの静的メソッドやプロパティを定義する
implementsクラスが特定のインターフェースを実装することを宣言する(使用されていないが予約されている)
interfaceインターフェースを定義する(使用されていないが予約されている)
packageコードをモジュールとしてグループ化する(使用されていないが予約されている)
privateクラスのプライベートメンバーを定義する(使用されていないが予約されている)
protectedクラスの保護されたメンバーを定義する(使用されていないが予約されている)
publicクラスの公開メンバーを定義する(使用されていないが予約されている)

予約語に含まれない意外なもの


以下は、文法上は予約語ではないため命名に使用できますが、JavaScriptの構文やブラウザの挙動に影響することがあるため、実務上は避けた方が無難です。

予約語用法の説明
as型変換の際にエイリアス名を定義する(TypeScriptで使われる)
async非同期関数を定義する
fromモジュールの特定の部分をインポートする際に使用する
getオブジェクトのゲッター関数を定義する
metaimport.metaはモジュールの情報を提供する(新しいJavaScript機能)
offor...ofループで使用され、反復可能オブジェクトの値をループする
setオブジェクトのセッター関数を定義する
targetイベントのターゲットを指す(主にDOMで使用される)

予約語 留意事項

await / yield
この2種は特殊で、「特定のケース以外で使用可能」な予約語です。
例1:命名 yield はジェネレーター関数の中でのみ使用不可となります。(→ジェネレーター関数について詳細はこちら
例2:命名 await はAsync関数の中でのみ使用不可となります。
// ジェネレーター関数の中では使用不可
function* iterate (i) {
  const yield = 0;           // SyntaxError: Unexpected identifier 'yield'
  yield i;
  yield i += 1;
}

const it = iterate(0);
console.log(it.next());    // 結果 : 出力されない

// Async関数の中では使用不可
async function test() {
  const await = 'hello';
  console.log(await);
}
test();        // 結果 : SyntaxError: Unexpected identifier 'await'
どちらも、特定の関数の外であれば命名に使用可能です。
// ジェネレーター関数の外では使用可能
const yield = 'yield!'   // OK
console.log(yield)       // 結果 : `yield!`

// Async関数の外では使用可能
function notAsync() {
  const await = 123;     // OK
  console.log(await);    // 結果 : 123
}
ただし、勘違いをしてしまう可能性があるため、実用上は避けた方が安全です。
let
const / let / var のうち、letは strict mode でない場合に限り関数名や変数名として使用可能です。
ただし、実用上は避けた方が安全です。
// letの使用例
function let() {
  return 'function let!'
}
console.log(let()) // 結果 : 'function let!'

var let = 'var let!'
console.log(let) // 結果 : 'var let!'

Math / Date は予約されていない
// Mathの例
console.log(Math.max(1, 2, 3)) //出力:3

const Math = 'const Math!';      // ここで、「Math」は「const Math!」と上書きしている
console.log(Math)                // 結果 : 'const Math!'  ←左のように、ただの「定数」という形になる
console.log(Math.max(1, 2, 3))   // 結果 : TypeError: Math.max is not a function
                                 // ↑上の様子からObjectの「Math」としての機能は使えなくなっていることが分かる
このように命名に使用できてしまう組み込みオブジェクトMathですが、予約語として保護されていないため、上書きが可能です。
Dateも同様です。万が一の上書きが起こり得ます。注意しないといけませんね。


4. なぜconstを使うのが基本なのか?

解説: JavaScript / TypeScript では、変数を宣言する方法が3つあります。

// JavaScriptにおける変数と定数の宣言方法の種類
const age = 25;              // 定数
let name = "太郎";           // 変数
var message = "こんにちは";   // 変数

この中で、基本的にconstを使用する事が推奨されているのは、以下の理由からです。


理由①:意図しない再代入を防げます。
const は「定数」なので、一度値を入れたら変更できません。
これにより、「間違えて上書きしてしまった!」というバグを防ぐことができます。

// 意図しない再代入を防ぐ例
const pi = 3.14;
pi = 3.14159; // 再代入しようとするとエラーになる(変更できない = 値が変わらないので安全!)

理由②:読みやすく、意図が明確になります。
const を使うことで、「この値は変わらないんだな」とコードを読む人にも伝わります。
→ 読みやすく、保守もしやすいコードになります。


理由③:const でも必要なことはだいたいできます。
後述しますが、配列やオブジェクトの中身は変更可能です。
そのため「const じゃ足りない」という場面は実はほとんどありません。

// 配列やオブジェクトの中身は変更可能な例
const fruits = ["りんご", "バナナ"];
fruits.push("ぶどう"); // 値の追加OK!(エラーにならない)

結論:まずは const。必要なら let。
開発現場でも「まずは const。再代入が必要なときだけ let に変更」という方針が一般的です。
このルールで書くと、安全でバグの少ないコードになります。


5. 再宣言と再代入の違い

解説: 変数や定数を扱うときに知っておきたいのが「再宣言」と「再代入」の違いです。
この2つは似た言葉に見えますが、実際の意味や挙動はまったく異なります。


再代入(さいだいにゅう) → 一度作った変数に、新しい値を入れなおすこと
再宣言(さいせんげん) → 同じ名前の変数をもう一度作成しようとすること


// 再代入の例
let name = "太郎";
name = "花子";  // ← 再代入(OK)

// 再宣言の例
let name = "太郎";
let name = "花子";  // ← 再宣言(エラーになってしまいます。)

各キーワードごとの対応表

宣言方法再代入再宣言
constできない ❌できない ❌
letできる ✅できない ❌
varできる ✅できる ✅

const は、「再代入」も「再宣言」もできないという、最も安全な宣言方法です。
let は、再代入はできるけど、再宣言はできないというルールです。
var は、どちらもできてしまうため、バグの原因になりやすいです。

何度も書いていますが、やはり「var」は使うべきではありません。笑

6.constでも中身は変更できる(オブジェクト / 配列)

解説: 「constは定数なので、変更はできないはず!」……そう思いますよね。
実を言うと、私もそう思っていました。
しかし、実際には、オブジェクトと配列の中身は変更可能なんです。

// オブジェクトの中身変更の例
const user = {
  name: "太郎",
  age: 20
};

user.age = 21;           // OK:プロパティの変更
user.city = "東京";      // OK:新しいプロパティの追加

ただし、下記のようにconst で宣言した“変数”そのものを上書きすることはできません
正確には「定数」なので、再代入できないのです。

user = { name: "花子" };  // エラー:定数への再代入となるため不可

配列も同様に中身の変更は、可能です。

// 配列の中身変更の例
const colors = ["赤", "青"];

colors.push("緑");    // OK:要素の追加
colors[0] = "黄色";   // OK:要素の変更

これも、「colorsという変数の参照先」は変わっていないため、エラーにはなりません。


ポイント整理:
  • 1. const で宣言したオブジェクトや配列の中身は変更できます。
  • 2. ただし、constで宣言した変数自体を別の値に置き換えるのは、「定数への再代入」になるためNGです。

余談:
「constで宣言した変数」って何だよ、定数じゃないの?と思った方、いますよね。
わかります。私も頭に「?」が浮かびました……。
じゃあなぜこういう表現になるかというと、次のような背景があるんです。

  • 1. JavaScriptでは const 宣言で作った値も 「定数」ではあるが、“変数”でもある(technicalには「定数扱いの変数」)
  • 2. 公式仕様(ECMAScript)でも "const declarations create read-only named variables"(読み取り専用の名前付き変数)と記載されている

というわけで、納得できるかどうかはともかく、「そういうものなんだな」と軽く受け止めてもらえればOKです。

7. Object.freeze() を使った完全な固定方法(応用)

解説: 前の章で、constで宣言したオブジェクトや配列の中身は変更できると説明しました。
では「中身も含めて完全に変更できないようにしたい」場合は、どうすればよいでしょうか?

そんなときに使えるのが、Object.freeze() という便利なメソッドです。

// オブジェクトを完全に固定する例
const config = {
  debug: true
};

Object.freeze(config);      // 中身を変更されたくないオブジェクトに使用します

config.debug = false;       // ❌ エラーにはならないが、変更されません
console.log(config.debug);  // 結果 : true(のまま)

Object.freeze()は、オブジェクトの中身を読み取り専用にします。
書き換えようとしても無視され、実際の値は変わりません。…できればエラーになってほしいところですね。笑

ただし、注意点もあります。

// ネストされた中の値は固定されない例
const user = {
  name: "太郎",
  options: {
    darkMode: true    // ネスト(入れ子)された内容
  }
};

Object.freeze(user);

user.options.darkMode = false;       // ✅ これは変更されてしまいます
console.log(user.options.darkMode);  // 結果 : false

上のように、Object.freeze()は浅い固定(shallow freeze)しか行いません。
ネストされたオブジェクトの中身まで固定したい場合は、Object.freeze()を使用して再帰的な関数(自身を呼び出して処理を繰り返す関数)を作成する必要があります。

TypeScriptでは型の力で「Readonly」を使い、開発時点で「これは変更しないでね」と警告を出すこともできます。
こちらについても、今後詳しく解説します!

まとめ:
・constは「変数(参照先)を固定」する
・Object.freeze()は「中身(プロパティの値)も固定」する
 → ただし、ネストされた構造には注意が必要!


補足. ジェネレーター関数とは?

解説: JavaScriptのジェネレーター関数とは、一時停止 (yield) と再開ができる特殊な関数のことです。 通常の関数は1回呼ばれたら一気に最後まで実行されますが、ジェネレーターは途中で一時停止して値を返すことができるのが特徴です。

// ジェネレーター関数の例
function* example() {
  yield 1;
  yield 2;
  yield 3;
}

const it = example();

console.log(it.next()); // { value: 1, done: false }
console.log(it.next()); // { value: 2, done: false }
console.log(it.next()); // { value: 3, done: false }
console.log(it.next()); // { value: undefined, done: true }
「function*」でジェネレーター関数を定義し、その中で「yield」は値を一時的に返して処理を中断します
「.next()」 で中断位置から再開します

なぜ const yield = 0; でエラーが出るのかについて
yield はジェネレーター関数の中では、特殊なキーワードとして扱われます。
ジェネレーター関数の中では yield は上記の例のようにチェックポイントとして働きます。
そのため、下記のようにジェネレーター関数内で、変数などの命名に使用すると構文エラーが出てしまうというわけです。
// ジェネレーター関数の例
function* gen() {
  const yield = 1; // 結果 : SyntaxError(構文エラー)
}