【実務・中級編】 変数のライフサイクル(生成・初期化・代入) – JavaScript実践ガイド

フロントエンドの最前線で戦う皆さん、お疲れ様。

「`var`を使わずに`const`と`let`を使え」——これは新人の頃に誰もが叩き込まれる教訓だよね。でも、なぜそうすべきなのか、JavaScriptエンジンが内部で変数をどう「産み落とし、育てているのか」というライフサイクルまで理解しているエンジニアは、意外と少ない。

今日は、ECMAScriptの仕様書を読み解くような堅苦しい話ではなく、「ブラウザの頭の中」を覗き込むような感覚で、変数の生成から初期化、代入までの真実を紐解いていこう。

—

1. 変数のライフサイクルは「3つのフェーズ」で理解する

JavaScriptの変数は、宣言からアクセス可能になるまでに、実は3つの明確なステップを踏んでいる。

1. 生成 (Creation Phase / Declaration): スコープ内に変数を登録する。メモリを確保する準備段階。
2. 初期化 (Initialization): 変数に`undefined`を割り当て、メモリ上の場所を確定させる。
3. 代入 (Assignment): 開発者が書いた実際の値を書き込む。

この中で、`var`, `let`, `const`がどう振る舞うかが、皆さんのコードの「安定性」を左右するんだ。

var の場合:生成と初期化が「同時」に走る

`var`は非常に寛容だ。宣言する前にアクセスしてもエラーにならない(`undefined`が返る)。これは、生成と初期化がホイスティング(巻き上げ)時に同時に行われ、即座に`undefined`で埋められるからだ。

console.log(myVar); // undefined (エラーにならないのが曲者)
var myVar = “Hello”;

現場でこれがバグの温床になるのは言うまでもないよね。「変数が存在しているのに中身が空」という状態を許容してしまうからだ。

let / const の場合:死の領域「TDZ」の誕生

`let`と`const`は現代のJS開発における守護神だ。これらは、生成(宣言)と初期化の間に「Temporal Dead Zone(一時的死域:TDZ)」という強力な防壁を作る。

  • 生成: スコープの先頭で変数を認識する。
  • 初期化: コードがその行に到達するまで待機する。

TDZの間にアクセスすると、JavaScriptエンジンは容赦なく`ReferenceError`を吐く。 これが「宣言前の使用を禁ずる」という強力なガードレールになり、プログラミングの曖昧さを排除してくれる。

—

2. 実務で差が出る「const」の深淵

「`const`は定数だから書き換えられない」という認識は半分正解で、半分は現場レベルでは不十分だ。正確には、`const`は「値(参照先)」を固定する。

オブジェクトや配列を`const`で定義した場合、中身の書き換えは可能だよね。これはエンジンのメモリ管理において、「メモリのアドレス」を固定しているだけで、その先にあるデータ構造まではロックしていないからだ。

const user = { name: “Alice” };

// OK: プロパティの変更は可能。参照先は変わっていないから。
user.name = “Bob”;

// NG: 再代入は不可。メモリ上の参照先を書き換えようとするため。
// user = { name: “Charlie” }; // TypeError!

この挙動を理解した上で、意図的に「イミュータブル(不変)」にしたいなら `Object.freeze()` を併用するのが、シニアエンジニアの嗜みというものだ。

—

3. なぜ「let/const」だけで書くべきなのか

最後に、チーム開発におけるベストプラクティスを共有しておく。僕らが`var`を捨てた理由は単純だ。

1. スコープの明確化: `var`は関数スコープだが、`let/const`はブロック(`{}`)スコープだ。if文やfor文の中で閉じられるため、予期せぬ変数の汚染を防げる。
2. 安全性(TDZ): 宣言前にアクセスしてバグるという「JavaScript特有の事故」を、ランタイムエラーとして即座に検知できる。
3. 可読性: 「これは再代入される変数か、定数か?」をコードから読み取れる。コードはコンピュータに読ませるものだが、それ以上に「未来の自分やチームメイト」に意図を伝えるためのものだ。

実践的なTips: 綺麗なコードのための習慣

最後に、現場ですぐ使える「変数のライフサイクルを意識した書き方」のサンプルを置いておくよ。

/

  • ベストプラクティス例:
  • 1. 基本は const を使い、再代入が必要な場合のみ let を使う。
  • 2. 意図的にスコープを区切り、変数のライフサイクルを短くする。

/
{
const API_ENDPOINT = “https://api.example.com”; // 定数は大文字、またはcamelCaseで

// 再代入が必要なカウンターなどは let で最小スコープに閉じ込める
let retryCount = 0;

const fetchData = async () => {
try {
// 処理…
} catch (e) {
retryCount++; // スコープの外側から参照せず、必要な場所で管理
}
};
}

—

結び

変数の生成から代入まで、そのライフサイクルを理解することは、単に「エラーを避ける」だけじゃない。「メモリと実行コンテキストをどう設計するか」というアーキテクチャの入り口に立っているということなんだ。

「なんとなく動く」コードから、「なぜこれで安全なのかを説明できる」コードへ。皆さんの書くコードが、より強固で美しいものになることを期待しているよ。

何かまた壁にぶつかったら、いつでも聞きに来てくれ。エンジニア同士、泥臭く積み上げていこう。

コメント

タイトルとURLをコピーしました