グローバル変数の扱い
グローバル変数は「プログラム全体から参照できる変数」です。便利に見えますが、扱いを誤るとバグの温床になります。安全な使い方と、避けるためのテクニックを初心者向けに整理します。
何がグローバル変数か
- 定義: どの関数・ブロックにも属さず、全ファイルから参照できる変数。
- ブラウザ:
window(またはglobalThis)のプロパティとして存在。 - Node.js: モジュールごとにスコープが分かれるが、
globalThisは共通。
// ブラウザ
let count = 0; // グローバル
console.log(window.count); // 0
// Nodeでも globalThis にアクセス可
console.log(globalThis === window); // ブラウザなら true
JavaScriptグローバル変数のメリット・デメリット
- メリット:
- 共有: どこからでも参照できるため、設定値などを共有しやすい。
- デメリット:
- 衝突: 同名変数が上書きされやすい。
- 追跡困難: どこで変更されたか追いにくい。
- テスト困難: 状態が残るため、再現性が下がる。
- 隠れ依存: コードの依存関係が不透明になる。
例題:安易なグローバルで起こるバグ
// file A
let status = "idle";
function start() { status = "running"; }
// file B
function reset() { status = "idle"; } // 意図せず上書き
start();
reset();
console.log(status); // "idle"(Aの意図が壊れる)
JavaScript- 教訓: グローバルを共有すると、知らない場所の更新で挙動が壊れる。
暗黙的なグローバル(やってはいけない)
- 現象: 宣言なしの代入で勝手にグローバル変数になる(非strictモード)。
- 対策: かならず
let/constで宣言する。"use strict"を使う。
// 悪い例(非strict)
x = 10; // 宣言なし → 暗黙のグローバル
console.log(window.x); // 10
// 良い例(strict)
"use strict";
// x = 10; // エラー:宣言が必要
let x = 10; // 明示的に宣言
JavaScript安全な扱い方(ベストプラクティス)
- 最小限にする:
- ラベル: グローバルは「設定・定数・エントリポイント」など本当に必要なものだけ。
- 名前空間オブジェクトにまとめる:
- 衝突回避: ひとつのグローバルに集約し、その下にまとめる。
// 一つのグローバルに集約
const App = {
config: { apiBase: "https://example.com", timeout: 5000 },
state: { loggedIn: false },
start() { console.log("start"); }
};
// 参照
console.log(App.config.timeout);
App.state.loggedIn = true;
JavaScript- 即時関数/ブロックスコープで囲う:
- 漏れ防止: ファイル内の実装詳細を外へ出さない。
// ブロックで閉じる(ES6以降)
{
const SECRET = "token";
let cache = {};
// 外から見えない
}
// console.log(SECRET); // エラー
JavaScript- モジュールを使う(推奨):
- ES Modules:
type="module"でスクリプトを読み込むと、ファイルはモジュールスコープになり、トップレベル変数はグローバルに出ない。
- ES Modules:
<script type="module">
const local = 1; // グローバルにならない
export const answer = 42; // 必要なものだけ公開
</script>
JavaScript- 不変データは const + Freeze:
- 改変防止: 設定値などは不変にする。
const CONFIG = Object.freeze({
API: "https://example.com",
TIMEOUT: 5000
});
// CONFIG.TIMEOUT = 1000; // 変更できない
JavaScriptグローバル値の読み書きを明確にする
- 読み取り専用にする:
- 関数で提供: 直接代入させず、関数越しに操作。
const App = (() => {
let count = 0; // 外から直接触られない
return {
getCount() { return count; },
increment() { count++; },
reset() { count = 0; }
};
})();
App.increment();
console.log(App.getCount()); // 1
// App.count = 99; // 無効(外から見えない)
JavaScript使い分けの目安
- グローバルにして良いもの:
- 定数: バージョン、APIベースURL、UIテーマ名など。
- エントリ関数:
App.start()のような起動口。
- グローバルにしないもの:
- 可変状態: ログイン状態、カウンタ、キャッシュ。
- 内部用のユーティリティ: そのファイルで完結すべき関数や変数。
ミニ練習
- 問1: 暗黙のグローバルを防ぐように修正せよ。
// 悪い
message = "Hello";
// 良い
let message = "Hello";
JavaScript- 問2: 名前空間にまとめて衝突を避けるコードに書き換えよ。
// 悪い
let timeout = 3000;
function start() { /* ... */ }
// 良い
const App = {
timeout: 3000,
start() { /* ... */ }
};
JavaScript- 問3: 設定値を不変にして安全に参照せよ。
const CONFIG = Object.freeze({ retries: 3 });
function run() { console.log(CONFIG.retries); }
run();
JavaScriptまとめ
- グローバルは便利だが危険: 衝突・追跡困難・テスト性低下の原因になる。
- 暗黙的なグローバルは禁止: かならず
let/constと"use strict"。 - 最小限+名前空間+モジュール: グローバル汚染を避け、公開範囲をコントロールする。
- 不変化と関数越し操作: 直接代入させず、API化して安全に扱う。
この基礎を守るだけで、コードの安定性と可読性がぐっと上がります。次は「ローカルスコープ」「ブロックスコープ」と組み合わせて、変数の見える範囲をさらに整理していきましょう。
