JavaScript

In JavaScript, the global scope is the broadest level of access where variables and functions defined outside of any specific function or block can be referenced and modified from anywhere in the program.

グローバルスコープ(Global Scope)

JavaScriptにおけるスコープとは

JavaScriptにおける「スコープ(scope)」とは、変数や関数がアクセス可能な範囲のことを指します。スコープを正しく理解することで、意図した箇所で変数を使用できるように管理し、予期せぬ変数の上書きや衝突を防止できます。

スコープの大きな分類

グローバルスコープ (Global Scope)
プログラムのどこからでもアクセス可能なスコープ。
ローカルスコープ (Local Scope)
関数やブロック(コードの塊)内部に限定されるスコープ。

このページでは、スコープの中でも特に「グローバルスコープ (Global Scope)」にフォーカスしています。

グローバルスコープとは

定義

グローバルスコープは、JavaScriptコードのどの部分からでも直接アクセスが可能な最も広い範囲のスコープです。ブラウザでは「window」オブジェクト(またはselfframesなど)が、Node.jsなどの環境では「global」オブジェクトがグローバルオブジェクトとして存在しています。最新の仕様(ECMAScript 2020以降)では、環境に依存しないグローバルオブジェクトとして「globalThis」が用意されています。

ブラウザ
windowselfglobalThis
Node.js
globalglobalThis
Web Workers
selfglobalThis

グローバル変数とは

JavaScriptでは、グローバルスコープに定義された変数のことをグローバル変数と呼びます。グローバル変数は、プログラム内のどこでも参照・変更が可能です。

例:グローバル変数の宣言

JavaScript

var globalVar = "Hello, World!";

function showGlobalVar() {
console.log(globalVar); // "Hello, World!"
}

showGlobalVar();
console.log(globalVar); // 関数の外でもアクセス可能

このようにvarキーワードで宣言された変数は、関数やブロックの外側(トップレベル)で宣言されるとグローバルスコープに属します。

なぜグローバルスコープが重要か

どこからでもアクセス可能

グローバルスコープに置いた変数や関数は、アプリケーションのあらゆる場所からアクセスできるため、利便性が高いというメリットがあります。ただし、同時に「どこからでも変更可能」という意味でもあるため、変数の衝突や上書きが起こりやすい側面も存在します。

依存性のないコードの共通基盤

多数のモジュールや関数が共通して利用する定数や処理をグローバルスコープに配置することで、使い回しがしやすくなる場合もあります。しかし、複雑化したプロジェクトでは、グローバルスコープに要素を増やしすぎると保守が難しくなるため、注意が必要です。

変数宣言とグローバルスコープの関係

JavaScriptでは、変数を宣言するときに使用するキーワードとして、歴史的経緯からvarletconstの3種類があります。それぞれ挙動が少しずつ異なり、グローバルスコープへの影響度合いも変わります。

var

関数スコープ
varはブロックスコープではなく関数スコープを持つ。
再宣言が可能
同名の変数を同じスコープ内で再び宣言できてしまう。
巻き上げ(hoisting)
宣言がスコープの先頭に巻き上げられる挙動がある。
グローバルスコープへの登録
関数外でvarを使うと、自動的にグローバルオブジェクトのプロパティとして登録される。

例:トップレベルでのvar宣言

JavaScript

var name = "Alice";
console.log(window.name); // "Alice" (ブラウザ環境の場合)

このように、ブラウザ環境であればwindow(グローバルオブジェクト)のプロパティとしてアクセスできます。

let, const

ブロックスコープ
letconstはブロック({})単位でスコープが作られる。
再宣言が不可
同じスコープ内で同名の変数を再宣言できない。
巻き上げされるがTDZ(Temporal Dead Zone)が存在
変数の初期化前にアクセスできず、アクセスしようとするとエラーになる。
トップレベルで宣言しても自動的にグローバルオブジェクトのプロパティにはならない
varとの大きな違い。

例:トップレベルでのlet宣言

JavaScript

let greeting = "こんにちは";

console.log(greeting);       // "こんにちは"
console.log(window.greeting); // undefined (ブラウザ環境の場合)

greetingはグローバルスコープに定義されているものの、windowオブジェクトのプロパティにはならず、直接window.greetingでアクセスすることはできません。

グローバルスコープ利用の注意点

グローバルスコープは強力ですが、多用すると以下のような問題が生じる可能性があります。

名前の衝突 (Name Collision)

複数のスクリプトファイルが同一のグローバル変数名を使用している場合、どちらかが値を上書きしてしまう可能性があります。結果として意図しない動作を招く恐れがあります。

例:ライブラリ同士の衝突

HTML

<script src="libraryA.js"></script>
<script src="libraryB.js"></script>
<script>
	// libraryAやlibraryBがそれぞれ "appName" というグローバル変数を定義していた場合、
	// 後から読み込んだライブラリによって上書きされる。
</script>

テストやデバッグの難しさ

グローバル変数を多用していると、どこかで意図せず値を変更されている可能性が高く、テストを行う際に挙動の影響範囲を把握しづらくなります。

可読性や保守性の低下

グローバル変数が増えると、コードを読むだけではその変数がどこで定義され、どこから変更を受ける可能性があるか追いかけにくくなります。

グローバルスコープを管理する方法

即時実行関数 (IIFE: Immediately Invoked Function Expression)

昔からよく使われている方法として、即時実行関数を使ってローカルスコープを作り、グローバルスコープを汚染しないようにするやり方があります。モジュールシステムが普及する前には広く利用されていました。

JavaScript

(function() {
	var message = "Hello from IIFE";
	// message はこの関数内部でのみ有効
	console.log(message);
})();

// ここでは message は参照できない
// console.log(message); // Uncaught ReferenceError: message is not defined

名前空間オブジェクトを利用する

グローバルにただの変数を置くのではなく、特定のオブジェクトを1つだけ作り、そこに必要な変数や関数をまとめておくやり方です。これにより、グローバル名前空間の汚染を最小化できます。

JavaScript

// グローバルに MyApp というオブジェクトを定義
var MyApp = MyApp || {};

MyApp.config = {
	baseUrl: "/api",
	timeout: 5000
};

MyApp.utils = {
	formatDate: function(date) {
		// ...
	}
};

MyAppオブジェクトはグローバルに一つだけ置き、その中に必要なプロパティをまとめることで、意図しないグローバル変数の上書きを防ぎやすくなります。

ES6モジュール

現在の推奨は、ES6(ES2015)モジュールを使用してモジュールスコープを作ることです。モジュールを使うことで、ファイルごとにスコープを分離し、グローバルスコープを汚染しない構造を作ることができます。

例:ES6モジュール

moduleA.js

JavaScript

export const message = "Hello from moduleA";

export function greet() {
	console.log(message);
}

moduleB.js

JavaScript

import { message, greet } from "./moduleA.js";

console.log(message); // "Hello from moduleA"
greet(); // "Hello from moduleA"

このように、グローバルスコープを使わずに、明示的にimport/exportでモジュール同士の依存関係を管理できます。ビルド環境や最新のブラウザでは、モジュールが標準的にサポートされています。

グローバルオブジェクトとglobalThis

従来のグローバルオブジェクト

ブラウザ上
window
Node.js
global
Web Worker
self

これらは各環境固有の名前です。そのため、環境によってコードの書き分けが必要でした。

globalThisの登場

ECMAScript 2020では、環境に依存しない形でグローバルオブジェクトを参照できるglobalThisが導入されました。これにより、**「ブラウザかNode.jsかを気にせずに同一の書き方でグローバルオブジェクトを参照できる」**ようになりました。

JavaScript

globalThis.myGlobalValue = 42;

function showGlobalValue() {
	console.log(globalThis.myGlobalValue); // 42
}

showGlobalValue();

実践的な活用例

小規模スクリプトでの簡易的な利用

外部ライブラリや複雑な構造がない、小さなプログラムであれば、シンプルにグローバルスコープを活用しても問題ない場合があります。例えばHTML内に直接JavaScriptを書いて、ページ上の要素にアクセスしたり値を記録したりする程度であれば、大きなリスクはあまりありません。

HTML

<!DOCTYPE html>
<html>
<head>
	<meta charset="UTF-8">
	<title>小規模スクリプトの例</title>
</head>
<body>
	<h1 id="title">Hello</h1>
	<script>
		var pageTitle = document.getElementById("title");
		pageTitle.style.color = "red";
	</script>
</body>
</html>

大規模アプリケーション・ライブラリ開発

多くのファイルやライブラリが混在する大規模プロジェクトでは、グローバルスコープの乱用は避けるべきです。

JavaScript

// 例: ES6モジュールと名前空間を併用する
// config.js
export const AppConfig = {
	apiUrl: "https://example.com/api",
	timeout: 3000
};

// main.js
import { AppConfig } from "./config.js";

(function(global) {
	global.MyApp = global.MyApp || {};
	
	MyApp.init = function() {
		console.log("API:", AppConfig.apiUrl);
	};
})(globalThis);

MyApp.init();

上記のように、モジュールと名前空間を組み合わせることで、明示的に依存関係を管理しながらグローバル名前空間の汚染を最低限に抑えることができます。

上級者向けトピック

「use strict」とグローバル変数

"use strict"(厳格モード)を使用している場合、宣言していない変数に値を代入するとエラーになります。非厳格モードであれば、自動的にグローバル変数が生成されてしまいますが、厳格モードではそれが許可されなくなるため、予期せずグローバルスコープを汚染することを防止できます。

JavaScript

"use strict";
function test() {
	// x = 100; // ReferenceError: x is not defined
}

Shadowing(シャドーイング)

関数やブロックの内部で、グローバルと同名の変数を再宣言すると、グローバル変数が**シャドーイング(隠蔽)**されます。これは意図しないバグの原因になる場合もあるため、可読性を重視した変数名の付け方が大切です。

JavaScript

var foo = "global foo";

function doSomething() {
	let foo = "local foo"; 
	console.log(foo); // "local foo"
}
doSomething();
console.log(foo); // "global foo"

Windowプロパティとインラインイベントハンドラ

ブラウザ環境での特殊な例として、HTML要素のインライン属性で定義したイベントハンドラ(onclickなど)は、自動的にwindow(グローバルスコープ)に属する関数として参照されます。これもグローバル汚染につながりやすい慣習的な書き方のため、今ではなるべくHTML側に直接書かず、JavaScriptからDOM要素にイベントリスナーを登録する方法が推奨されています。

HTML

<!-- あまり推奨されない例 -->
<button onclick="myGlobalHandler()">Click me</button>
<script>
	function myGlobalHandler() {
		alert("Hello from global scope!");
	}
</script>

まとめ

JavaScriptのグローバルスコープはシンプルな反面、歴史的な理由や仕様上の細かな注意点がたくさん存在します。フロントエンドやバックエンドのいずれでも、グローバルスコープを理解し、適切に管理することが、大規模で安定したアプリケーションの開発・保守に繋がります。