JavaScript:変数とスコープ(let/constの使い分け)
音声
目次
letとconst(再代入・再宣言)
letとconstの違いを理解すると、適切に使い分けられるようになります。
constは再代入が禁止で、letは再代入が可能です。
どちらも同じ名前で再宣言はできません(同じスコープ内)。
短い例を実行して違いを確かめると感覚がつかめます。
// 短い実例(CodePenのJS欄で試す)
// 同じスコープ内で再宣言
let a = 1;
//let a = 2; // SyntaxError
const b = 1;
//const b = 2; // SyntaxError
// 同じスコープ内で再代入
let c = 1;
c = 2; // 代入できる
const d = 1;
d = 2; // TypeError
貼り付け場所:CodePenのJS欄に貼り付けます。
スコープとブロックスコープ
まず、スコープとは「その変数名を参照できる有効範囲」のことです。どこからその変数を使えるか、という境界を表します。
ブロックスコープとは中括弧({ })内だけ有効な変数範囲で、let/constはこれを持ちます。
ifやforの中で宣言したlet/constは、そのブロックの外からは参照できません。つまり「ブロックの中で作った変数は、外では使えない」というルールです。
この性質のおかげで、処理の一部だけで使う一時変数を外に漏らさずに済み、別の場所で同じ名前を使っても衝突しにくくなります。
補足:ブロックが分かれていれば同じ名前を使える場合があります(外側と内側で別スコープになるため)。ただし、読み手が混乱しやすいので同名の使い回し(シャドーイング)は必要なときだけにします。
補足:ほぼ過去の遺物となっている var という変数宣言もあります。詳細の説明は省きますがスコープの仕組み(関数スコープ)などの影響で意図しない挙動になりやすいため、新規のコードでは基本的に var は使わず、let/const を使いましょう。
関数スコープとグローバル汚染
注意したいのは「宣言なし代入」です。関数内でも、foo = 1 のように let/const/var を付けずに代入すると、暗黙的にグローバル変数になり、他のスクリプトを壊しやすくなります(厳格モードの'use strict' ではエラーになります)。
また、var はブロックスコープではなく関数スコープのため、if や for の中で宣言しても関数全体から見えてしまい、意図しない範囲で使えてしまうことがあります。新規のコードでは基本的に let/const を使うのが安全です。
関数スコープは関数単位で有効範囲が決まるため、ローカルにするなら必ず let/const で宣言します。
参照型の落とし穴(constでも中身は変わる)
const は「変数に別の値を入れ直す(再代入)」ができない、という意味です。
ただし、const に入っているのがオブジェクトや配列のような「中身を持つ値」の場合、中身を書き換えることはできます。つまり「入れ物の付け替え」は禁止でも、「入れ物の中身の変更」は可能です。
この違いを知らないと「constなのに変わった」と感じるので、「変えられないのは変数そのもの(入れ替え)」だと覚えると混乱しにくいです。
安全な命名と整理のコツ
命名は何を表すか一目で分かるようにして、グローバル変数は避けます。接頭辞やキャメルケースで役割を出し、関数は動詞+目的語、変数は名詞にします。ファイル内で使う小さなヘルパー関数は名前空間(オブジェクト)にまとめると衝突が減ります。
用語メモ:
-
グローバル変数:どこからでも参照できる変数のことです(スクリプトの一番外側で作った変数など)。便利に見えますが、別の場所で同じ名前を使ってしまうと上書きや衝突が起きやすいので、できるだけ関数やブロックの中に閉じて使います。
-
キャメルケース:単語をつなげるときに、2語目以降の先頭を大文字にする書き方です。例:
userName、saveDraft、maxRetryCount。JavaScriptでは変数名や関数名でよく使われます。 -
ヘルパー関数:本筋の処理(メインの処理)を助けるための「小さな便利関数」です。たとえば「文字列を整形する」「入力チェックをする」「日付を表示用に変換する」など、何度も出てくる細かい処理を関数にして再利用します。
これまでの内容を整理したコード例
下記のコード例は、命名と整理の考え方を体感するためのミニ例です。
■ 動作
画面に「クリック」ボタンと表示欄を用意します。
-
ボタンを押すたびに、次の処理を行います。
countを 1 増やすuserNameを"B"に更新する表示欄を
count=..., user=...の形式で更新し直す条件付きで Console にメッセージを出す(動作確認用)
■ 整理ポイント
グローバルを増やさない:外に出る名前は
CounterAppだけにして、衝突しにくくします。状態をまとめる:
state(count と userName)に集約して、変数を散らばらせません。ヘルパー関数:
getEl()やrender()のような共通処理を関数にして再利用します。必要最小限だけ公開:外から呼べるのは
mount()だけにして、内部の変数や関数を隠します。
// 実例(CodePenのJS欄に貼る)
// 目的:グローバルを増やさず、命名と整理(まとめ方)を体感する例
const CounterApp = (function () {
// 固定値は変更しないので const
const MAX_COUNT = 3;
// プログラムの挙動(表示や処理の結果)に影響する、途中で変わる値をまとめる
// {} は「入れ物(オブジェクト)」を作るための記法
const state = { count: 0, userName: "A" };
// ---- ヘルパー関数(小さな便利関数)----
function getEl(id) {
return document.getElementById(id);
}
function render() {
getEl("out").textContent = "count=" + state.count + ", user=" + state.userName;
}
// ---- メインの処理(イベント処理)----
function onClick() {
// ブロックスコープ(ifの中だけの一時変数)
if (state.count < MAX_COUNT) {
const message = "クリックしました";
console.log(message);
}
// const のオブジェクトでも中身は更新できる
state.userName = "B";
// 値が変わるので count を更新(数値の更新は let ではなく state 内で行う)
state.count = state.count + 1;
render();
}
function mount() {
getEl("btn").addEventListener("click", onClick);
render();
}
// 外に公開するのは必要最小限にする(入口を絞って安全にする)
// ここでは mount 関数だけを返し、state など内部の値や関数は外から触れないようにする
// その結果、外側では CounterApp.mount() だけが呼べる
return { mount: mount };
})();
CounterApp.mount();
<!-- HTML欄に貼る(CodePenのHTML欄) -->
<button id="btn">クリック</button>
<div id="out">count=0, user=A</div>
実行方法は、CodePenにHTMLをHTML欄、JavaScriptをJS欄にそれぞれ貼り付けてRunボタンを押すか、ローカルならindex.htmlをブラウザで開きます。
確認方法は、ボタンをクリックして画面の表示が変わることを見ます。さらにブラウザのConsoleを開くと、ブロックスコープ内のconsole.log出力や、誤ったグローバルがあるとwindowオブジェクト上に出ることを確認できます。
よくあるミスは、宣言を忘れて代入すると暗黙のグローバルになることです(例: foo = 1)。原因は宣言漏れで、直し方は必ずlet/constで宣言することと、厳格モード('use strict')を使って検出することです。
使い分けの基準は、原則として「constをデフォルトにして、必要なときだけletにする」です。再代入しない固定値や参照はconst、ループやカウンタのように値を更新するものはletにします。配列やオブジェクトは構造の変更が多い場合でも参照自体を変えないならconstを使い、反復処理は要素操作ならmap/filter、インデックス操作が必要ならforやfor...ofを選びます。
注意(ここだけ)
varは関数スコープで巻き上げ(hoist)され、意図しない上書きが起きやすいので基本は避けます。constは「再代入禁止」なだけで、中身(オブジェクト/配列)の変更は可能です。凍結が必要ならObject.freezeを検討します(浅い凍結に注意)。グローバル(最上位)に変数を置くと名前衝突の原因になります。なるべく関数やモジュールの内側に閉じます。
要約
constは再代入が不可で、letは再代入が可能ですが、どちらも同一スコープ内での再宣言は不可です。
ブロックスコープにより、同名変数の衝突を避けやすくなります。
宣言を忘れるとグローバル汚染を招きやすいため、必ず宣言してから使うようにします。
constは参照を固定するだけなので、オブジェクトの中身は変更できる場合があります。
命名は意味が分かるようにし、constをデフォルトとして、必要な場合にだけletを使うようにします。