JavaScript | ES6+ 文法:モジュール – モジュールスコープ

JavaScript JavaScript
スポンサーリンク

モジュールスコープとは何か(まずイメージから)

モジュールスコープは、
「そのモジュール(=1ファイル)の中だけで有効な“自分専用の世界”」 のことです。

ES6 モジュール(import / export を使うファイル)は、
それぞれが「自分だけのスコープ(変数の有効範囲)」を持ちます。

// a.js
const secret = "a の中だけで使う";

// b.js
const secret = "b の中だけで使う";
JavaScript

この2つの secret は、別々のモジュールスコープにいるので、
互いにぶつかりませんし、相手からも見えません。

ここが重要です。
ES6 モジュールでは、ファイルの先頭で書いた変数も関数も、デフォルトでは「グローバル」ではなく「そのモジュールの中だけ」 です。
「勝手に window や global に漏れない」仕組みになっています。

「グローバル汚染しない」モジュールの世界

普通の script とモジュール script の違い

昔の <script>(type=”module” ではない)では、
ファイルのトップレベルで varfunction を書くと、グローバルに飛び出していました。

<script>
  var x = 1;           // window.x として公開される
  function hello() {}  // window.hello として公開される
</script>
HTML

しかし ES モジュールの場合:

<script type="module">
  const x = 1;
  function hello() {}
</script>
HTML

この xhellowindow に乗りません
その <script type="module"> の中だけで有効です。

ここが重要です。
モジュールは自動的に「自分だけのスコープ」を持ち、グローバルを汚さない。
他のファイルと変数名がかぶっても平気。
外に出したいものだけ export で明示的に出す。

これがモジュールスコープの基本的なメリットです。

モジュールのトップレベル変数は、そのモジュールの中だけ

// math.js(モジュール)
const internalValue = 42;

export function add(a, b) {
  return a + b + internalValue;
}
JavaScript
// main.js(モジュール)
import { add } from "./math.js";

console.log(add(1, 2)); // 1 + 2 + 42

console.log(internalValue); // エラー:見えない
JavaScript

internalValuemath.js のモジュールスコープに閉じ込められていて、
main.js からは直接触れません。

モジュールスコープと export / import の関係

export しない限り、他のモジュールから見えない

モジュールスコープの中で宣言したものは、
export しないかぎり、そのモジュールの中だけで完結します。

// util.js
const secret = "これは util.js の中だけ";

export function show() {
  console.log("show が呼ばれました");
}
JavaScript
// main.js
import { show } from "./util.js";

show();              // OK
console.log(secret); // エラー:secret は export されていない
JavaScript

設計としては、

  • モジュールスコープの中に「内部専用の変数・関数」を書く
  • 外に見せたいものだけに export を付ける

という形になります。

ここが重要です。
モジュールスコープ+export/import の組み合わせで、
「モジュールの中」と「外に見せる窓口」をきれいに分けられる

これはクラスのプライベートフィールドやメソッドと同じ「カプセル化」の感覚です。

export されたものだけが「モジュールの外向き API」

// user.js
const prefix = "[User]";         // 内部専用

export function createUser(name) {
  return { name };
}

export function printUser(user) {
  console.log(prefix, user.name);
}
JavaScript
// main.js
import { createUser, printUser } from "./user.js";

const u = createUser("Alice");
printUser(u);

// prefix に触ることはできない
// console.log(prefix); // エラー
JavaScript

このように、「外に使ってほしいもの」= export された関数・値がモジュールの API になり、
それ以外はモジュールスコープの中で閉じた実装になります。

モジュールスコープとトップレベル this / global の違い

モジュールのトップレベルの this は undefined

普通の script では、トップレベルの thiswindow(ブラウザ)や global(Node)を指していました。

// 非モジュールスクリプトの場合
console.log(this === window); // true になることが多い
JavaScript

しかし ES モジュールでは違います。

// モジュール(type="module" や .mjs 等)
console.log(this); // undefined
JavaScript

これは、「モジュールは自分だけのスコープで動いている」ためです。

ここが重要です。
モジュール内で「this を使ってグローバルにアクセスする」という発想は捨ててよく、
必要なら明示的に windowglobalThis を使う

モジュールスコープは、暗黙のグローバル参照を減らす方向の設計です。

本当にグローバルに触りたい場合

どうしてもブラウザ全体のグローバルに触りたい場合は windowglobalThis を使います。

// main.js(モジュール)
globalThis.appVersion = "1.0.0";

console.log(globalThis.appVersion); // 1.0.0
JavaScript

ただし、モジュールの利点(スコープの分離)を活かすためにも、
グローバルに直接書き込むのは最小限にするのが設計としてはおすすめです。

モジュールスコープと「即時関数」との関係

昔は IIFE(即時関数)でスコープを作っていた

ES6 モジュールがない頃、「グローバル汚染を防ぐ」ためにこう書くことが多かったです。

(function () {
  const x = 1;
  function foo() {
    console.log(x);
  }

  window.foo = foo; // 必要なものだけグローバルへ
})();
JavaScript

関数をその場で実行して、その中だけのスコープを作るテクニック(IIFE)です。

モジュールは「自動で IIFE してくれている」ようなもの

ES モジュールでは、これが最初から言語レベルで用意されています。

// module.js
const x = 1;
export function foo() {
  console.log(x);
}
JavaScript

この xfoo はモジュールスコープに閉じ込められて、
foo だけが export を通して外に出されます。

ここが重要です。
ES モジュールは「ファイルごとに自動でスコープを作ってくれる IIFE」のようなもの
開発者は余計なパターンを書かずに済み、export / import で意図をはっきり書けます。

具体例でモジュールスコープに慣れる

例1:同じ名前の変数を別ファイルで使っても衝突しない

// a.js
const count = 1;
export function showA() {
  console.log("A の count:", count);
}
JavaScript
// b.js
const count = 100;
export function showB() {
  console.log("B の count:", count);
}
JavaScript
// main.js
import { showA } from "./a.js";
import { showB } from "./b.js";

showA(); // A の count: 1
showB(); // B の count: 100
JavaScript

a.jscountb.jscount は、
それぞれのモジュールスコープにいるので、互いに影響しません。

例2:内部ロジックをモジュールスコープに閉じ込める

// password.js
const SALT = "static-salt";

function hash(text) {
  return `hash(${text}+${SALT})`;
}

export function createPasswordHash(rawPassword) {
  return hash(rawPassword);
}

export function verifyPassword(rawPassword, hashed) {
  return hash(rawPassword) === hashed;
}
JavaScript
// main.js
import { createPasswordHash, verifyPassword } from "./password.js";

const hash = createPasswordHash("secret");
console.log(verifyPassword("secret", hash)); // true

// SALT や hash に直接触ることはできない
// console.log(SALT);      // エラー
// hash("xxx");            // エラー(import していないし、export もされていない)
JavaScript

SALThash はモジュールスコープの中だけで完結していて、
外からは createPasswordHashverifyPassword という API でしか触れません。

例3:モジュールスコープ内で「状態を持つ」モジュール

// idGenerator.js
let currentId = 0; // モジュールスコープ内の状態

export function nextId() {
  currentId++;
  return currentId;
}
JavaScript
// main.js
import { nextId } from "./idGenerator.js";

console.log(nextId()); // 1
console.log(nextId()); // 2
console.log(nextId()); // 3
JavaScript

currentIdidGenerator.js の中だけで増え続ける状態として存在し、
外からは直接いじれません。

ここが重要です。
モジュールスコープをうまく使うと、「グローバル変数っぽい便利さ」と「外から直接触れない安全さ」を両立できる
このパターンは「シングルトンなサービス」的な設計にもよく使われます。

まとめ

モジュールスコープの核心は、
「ES6 モジュールごとに、自動で“そのファイルだけのスコープ”が用意されていて、export しない限り外から見えない」 という点です。

押さえておきたいポイントを整理すると:

モジュール(import / export を使うファイル)は、ファイル単位で独立したスコープを持つ
トップレベルで宣言した変数・関数は、デフォルトではグローバルではなく「そのモジュール専用」
外に見せたいものだけを export し、それ以外はモジュールスコープの中に隠す
トップレベルの this はグローバルではなく undefined になる(暗黙のグローバル参照を避ける設計)
昔の IIFE(即時関数)のような「わざとスコープを作るテクニック」を、言語レベルで置き換えたものと考えられる

まずは、小さなファイルを 2 つ作って、
それぞれに同じ名前の変数や関数を書いてみてください。
ES6 モジュールとして動かしたとき、
それぞれが自分のモジュールスコープを持っていて、お互いに干渉しないことが体感できるはずです。

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