はじめに
普段、Javaばかりいじっていて、たまにフロントに触るとあれ?JavaScriptのスコープの範囲って。
忘れてしまうことがある。
で、気にしないとグローバルスコープに定義がされてしまうので、改めて整理し、残しておこうかと。
そもそもグローバルスコープとは?
グローバル変数とは?
グローバルオブジェクトは?
少しまとめ
どこからでもアクセスできるグローバルスコープ。
グローバルスコープで定義されている変数が、グローバル変数。
JavaScriptにおいてのグローバル変数とは、グローバルオブジェクトのプロパティ。
ウェブブラウザでは、例外あれど基本は、Windowがグローバルオブジェクトになる。
つまり、グローバル変数を新たに定義すると、グローバルオブジェクトのプロパティとして定義される。
グローバル汚染とは、このグローバルオブジェクトのプロパティを汚染(=使ってしまう)ってことかな?
そもそもグローバル変数を定義している意識は無いんだけど?
こんなHTMLとJavaScriptを作るだけで、実はグローバル変数を定義してしまっている。
「funcA」と「funcB」はグローバル変数として定義され、グローバルオブジェクトのプロパティとして定義がされている。
<!DOCTYPE html>
<html lang="ja">
<head>
<meta charset="UTF-8">
<title>Global Scope Test</title>
</head>
<body>
<h1>ボタンで JavaScript を実行</h1>
<button type="button" onclick="funcB()">fileB の funcB を実行する</button>
<hr>
<script src="fileA.js"></script>
<script src="fileB.js"></script>
</body>
</html>
function funcA() {
console.log("fileA.js の funcA が実行されました!");
}
function funcB() {
if (typeof funcA === "function") {
funcA();
}
}
よくよく考えると単純な話。
fileA.jsのfuncAは、fileB.jsのfuncBから呼べる。
「fileB.jsのfuncB → fileA.jsのfuncA」
当たり前と思うのですが、よくよく考えると、ファイルが、「fileB.js」「fileA.js」と分かれているのに、呼べてしまう。
これは、「funcA」と「funcB」がグローバル変数として定義されているから、アクセスが可能となっている。
また、HTMLの「onclick="funcB()"」で、funcBを呼べてしまうのも、グローバル変数に定義されているからと思われる。
<button type="button" onclick="funcB()">fileB の funcB を実行する</button>
グローバル汚染の何が怖い?
今の例で、初期開発者とは別の方が、「fileC.js」を作り、間違って「funcA」というメソッドを追加してしまった場合。
「funcA」を呼ぶと、「fileA.js」の「funcA」ではなく、「fileC.js」の「funcA」が呼ばれてしまう。
そうすると、「fileA.js」の「funcA」を呼び出していた機能の動きが変わってしまう。
詰まる所、「名前の衝突」
じゃあどうすれば良いの?
IIFE (即時実行関数式)を使った場合
まずは、スコープを正しく整理して、ファイルを整理する。
「fileAのfuncA」は、「fileBのfuncB」からしか呼ばれません。
なので、1つのファイルに集約します。
file.jsというのを作り、1つにまとめる。
function funcA() {
console.log("fileA.js の funcA が実行されました!");
}
function funcB() {
if (typeof funcA === "function") {
funcA();
}
}
このままだと、ファイルは1個だけど、グローバル変数には両方定義されている。
ここで、「IIFE (即時実行関数式)」を利用する。
一言で言うと、「funcA」と「funcB」を囲ってしまう。
(function () {
function funcA() {
console.log("fileA.js の funcA が実行されました!");
}
function funcB() {
if (typeof funcA === "function") {
funcA();
}
}
})();
こんな感じ。
こうすると、「funcA」「funcB」はグローバル変数に定義がされなくなります。
でも、そうすると、HTMLの「onclick="funcB()"」で、funcBが呼べなくなってしまいます。
なぜならグローバル変数に定義していないから。
どうするか?というと、
その1)funcBだけ、グローバル変数に定義
(function () {
function funcA() {
console.log("fileA.js の funcA が実行されました!");
}
function funcB() {
if (typeof funcA === "function") {
funcA();
}
}
// funcBのみグローバルに公開
window.funcB = funcB;
})();
グローバル変数というのは、グローバルオブジェクトのプロパティ。ウィンドウにおいてグローバルオブジェクトは、Window
というところから、グローバルオブジェクトに明示的に定義したい場合は、「window.プロパティ名=」で定義ができる。
その2)onlickを使わず、イベントリスナーを利用する。
ボタンにIDを付与して、
<button id="myButton" type="button">fileB の funcB を実行する</button>
即時関数内で「myButton」のクリックイベントに「funcB」を定義する。
(function () {
function funcA() {
console.log("fileA.js の funcA が実行されました!");
}
function funcB() {
if (typeof funcA === "function") {
funcA();
}
}
document.getElementById('myButton').addEventListener('click', funcB);
})();
ES Modules
これは、名前だけ知っていてグローバル汚染の文脈で使ったことなく、Geminiから教わったので早速試す。
IIFE (即時実行関数式)だと、1つのJSファイルが肥大化してしまいそうな気もしたので。
スクリプトを定義する際に「type="module"」を付与。
<script type="module" src="fileA.js"></script>
<script type="module" src="fileB.js"></script>
funcA側に「export」を付ける。
export function funcA() {
console.log("fileA.js の funcA が実行されました!");
}
funcB側は、importする。これで「funcA」を「fileB.js」からも呼べるようになる。
import { funcA } from "./fileA.js";
function funcB() {
// 他のファイルの関数を呼び出す
if (typeof funcA === "function") {
funcA();
}
}
とはいえ、このままだと、HTMLのonclickから「funcB」が呼べません。
「type="module"」を付けた時点で、グローバルスコープに定義されなくなるので。
なので、「window.funcB = funcB;」でグローバルスコープに定義すると、HTMLのonclickからアクセスが可能。
import { funcA } from "./fileA.js";
function funcB() {
// 他のファイルの関数を呼び出す
if (typeof funcA === "function") {
funcA();
}
}
window.funcB = funcB;
とはいえ、せっかくグローバルスコープを利用しないようにしているので、リスナーイベントを使うのが良さそう。
<button id="myButton" type="button">fileB の funcB を実行する</button>
import { funcA } from "./fileA.js";
function funcB() {
// 他のファイルの関数を呼び出す
if (typeof funcA === "function") {
funcA();
}
}
document.getElementById('myButton').addEventListener('click', funcB);
まとめ
なんとなくJavaScirptを書いていたけど、改めて言語化していくと、気になることが沢山出てきてしまった。
Window(頭文字が大文字)とwindow(頭文字が小文字)
あと、「prototype」、「class」
この辺りは、別途深堀理解しながら記事を書くとして。
「IIFE (即時実行関数式)」のみしか知らなかったけど、「ES Modules」を改めて理解。






