変数

データに名前をつけて保存し、プログラム中で参照 / 変更できる仕組み

プログラミング基礎
変数」の技術書を見る →

変数とは

変数 (Variable) は、データに名前をつけて保存する仕組みである。プログラムの中で値を記憶し、後から参照したり変更したりできる。

宣言と代入

変数を使うには、まず宣言 (名前の登録) を行い、次に代入 (値の格納) を行う。多くの言語では宣言と代入を同時に行える。

// 宣言と代入を同時に行う
const name = "Alice";
let age = 15;

// 代入のみ (let で宣言済みの変数)
age = 16;

const と let の使い分け

TypeScript / JavaScript の宣言キーワードは const / let / var の 3 つで、実務で書くのは前の 2 つだ。

キーワード再代入再宣言スコープ宣言前に参照すると
const不可不可ブロックReferenceError
let不可ブロックReferenceError
var関数undefined

原則として const を使い、再代入が必要な場合だけ let を使う。var を避ける理由は好みではなく挙動にある。var はブロックを無視して関数全体をスコープとし、さらに宣言が関数の先頭へ巻き上げられる (ホイスティング) ため、宣言より前の行で参照してもエラーにならず undefined が返ってくる。letconst はブロックに入った時点で名前の存在自体は登録されるが、宣言に到達するまでは参照が禁止された状態 (TDZ、一時的デッドゾーン) に置かれ、宣言前に触ると ReferenceError で止まる (この扱いを巻き上げと呼ぶかは用語として揺れがある)。

console.log(v); // undefined (var は初期化前でも参照できてしまう)
var v = 1;

console.log(l); // ReferenceError: Cannot access 'l' before initialization
let l = 1;

書き間違いが undefined として静かに下流へ流れるか、その行で例外になるかの差であり、後者のほうが原因の特定が圧倒的に速い。

なお「再代入 (入っている値を入れ替える)」と「再宣言 (同じ名前をもう一度宣言する)」は別の話だ。let は再代入はできるが、同じスコープでの再宣言は構文エラーになる。var は再宣言も通るので、離れた行で同名の変数を宣言してしまっても気づけない。

型と変数

静的型付け言語では、変数に格納できるデータの種類 (型) が決まっている。

const count: number = 42;       // 数値のみ
const message: string = "hello"; // 文字列のみ
const active: boolean = true;    // 真偽値のみ

TypeScript は型推論が働くため、明示的な型注釈を省略しても型が決まる。ただし推論結果は宣言キーワードで変わる。let は後から別の数値を代入できるので number と広めに推論されるが、const は再代入されないことが確定しているため、値そのものを型とするリテラル型に絞り込まれる。

let total = 42;   // number と推論される
const count = 42; // 42 というリテラル型に推論される

この差はユニオン型を要求する関数で表に出る。"dark" | "light" を受け取る関数には const mode = "dark" はそのまま渡せるが、let mode = "dark"string へ広がるため型エラーになる。値を固定する意図があるなら const で宣言しておくほうが、型の面でも情報が残る。

スコープ

変数には有効範囲 (スコープ) がある。関数の中で宣言した変数は、その関数の外からは参照できない。

function greet() {
  const msg = "hello"; // この関数の中だけで有効
  console.log(msg);
}
// console.log(msg); // ❌ エラー: msg は関数の外では参照できない

ブロック ({}) の中で letconst で宣言した変数も、そのブロックの外からは参照できない。

命名規則

変数名はコードの読みやすさに直結する。以下の慣習が広く使われている。

規則用途
camelCaseuserName変数、関数
PascalCaseUserProfileクラス、型
UPPER_SNAKEMAX_RETRY定数

名前は「何が入っているか」がわかるようにつける。xtmp のような曖昧な名前は避け、userAgeitemCount のように具体的にする。

ミュータブルとイミュータブル

const の「再代入不可」は、名前がどのオブジェクトを指すかを固定するという意味に限られる。指している先の中身は自由に書き換わるため、const にしたのだから値は変わらないと思い込むと足をすくわれる。

const list = [1, 2, 3];
list.push(4);     // OK: 配列の中身は変えられる
// list = [9];    // エラー: 別の配列を指し直すことはできない

const frozen = Object.freeze([1, 2, 3]);
// frozen.push(4); // TypeError: 要素を追加できない

中身まで固定したいときは Object.freeze を使う。ただしこれは最上位の 1 階層だけを凍結する浅い処理で、入れ子のオブジェクトには効かない。Object.freeze({ a: { b: 1 } }) としても a.b は書き換えられてしまう。深く固定したいなら各階層へ再帰的に適用するか、値を書き換えず新しい値を作って返す書き方に寄せる。

書き換えを禁じておくと「どこで値が変わったのか」を追う作業が消えるため、既定を const + 作り直しに置き、可変にする箇所だけを意識して選ぶのが扱いやすい。

他言語との比較

再代入を禁じる書き方は言語ごとに異なる。

言語再代入を禁じる書き方再代入できる書き方
TypeScriptconstlet
Rustlet (既定で不変)let mut
Python構文としては用意されていないすべての変数
Javafinal通常の宣言

Rust は既定が不変で、明示的に mut をつけない限り再代入できない。安全な側を何も書かずに選べる形にしてある点が、他言語との設計思想の差になる。Python には再代入を禁じる構文がないため、変えない値は MAX_RETRY のように大文字の名前にして意図を伝える慣習で運用する。

この表はいずれも再代入の可否であって、値の中身が不変かどうかは別の軸だ。Java の final も参照先の差し替えを防ぐだけで、リストへの要素追加は止められない。逆に Python の文字列やタプルは、変数の側に何も書かなくてもオブジェクト自体が不変である。

よくある間違い

次の 4 つは、書いた本人が気づきにくく、症状が出る場所も原因から離れやすい。

間違い問題対策
宣言前に参照let / const は ReferenceError、var は undefined が流れる宣言と初期化をまとめて書く
グローバル変数の乱用依存関係が不明瞭スコープを狭く保つ
意味のない名前可読性の低下具体的な名前をつける
const なのに中身が変わる固定されるのは参照先だけ値を作り直す方針にする (Object.freeze は 1 階層のみ)

変数の設計と命名の考え方は関連書籍に詳しい。

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

関連用語

関連する記事