404 Not Found

404 Not Found


nginx

JavaScript クラスとモジュール

プロジェクトが大きくなると、バラバラの関数や変数だけでは不十分になります。組織化されたコード構造が必要です。クラスはオブジェクト指向パターンを提供し、モジュールはファイルレベルのコード分離を提供します。 together で、モダン JS プロジェクトの基盤を形成します。


クラス構文

クラスはオブジェクトを作成するためのテンプレートです。コンストラクタ関数のシンタシュガーです。

HTML
<script>
class ClassName {
  constructor(params) {
    this.property = params;
  }
  method() {
    // ...
  }
}
</script>

例:Student クラスの作成

HTML
<div id="output" style="padding: 10px; border: 1px solid #ccc;"></div>

<script>
const output = document.getElementById('output');

class Student {
  constructor(name, age, grade) {
    this.name = name;
    this.age = age;
    this.grade = grade;
  }

  introduce() {
    return '私の名前は' + this.name + 'です。' + this.age + '歳で、' + this.grade + '年生です。';
  }

  study(subject) {
    return this.name + 'は' + subject + 'を勉強しています。';
  }
}

const s1 = new Student('Alice', 12, '6');
const s2 = new Student('Bob', 11, '5');

output.textContent = s1.introduce() + '\n' + s2.introduce() + '\n' + s1.study('算数');
</script>
▶ 試してみよう
💡 class は本質的にはコンストラクタ関数 + プロトタイプのシンタシュガーです。new Student() は古い new function Student() と同じように動作しますが、構文がよりクリーンで直感的です。


プロパティとメソッド

インスタンスプロパティとメソッド

constructor 内で this.xxx で定義されたプロパティとメソッドはインスタンスに属します。各オブジェクトは独自のコピーを持ちます。

静的メソッド

static キーワードで定義されたメソッドはクラス自体に属し、インスタンスには属しません。ClassName.method() で呼び出します。

ゲッターとセッター

getset キーワードを使って「仮想プロパティ」を定義します。読み書き時に自動的に実行される関数です。

HTML
<div id="output" style="white-space: pre; font-family: monospace; padding: 10px; border: 1px solid #ccc;"></div>
<script>
const output = document.getElementById('output');

class Person {
  constructor(firstName, lastName) {
    this.firstName = firstName;
    this.lastName = lastName;
  }

  get fullName() {
    return this.firstName + ' ' + this.lastName;
  }

  set fullName(value) {
    const parts = value.split(' ');
    this.firstName = parts[0];
    this.lastName = parts[1];
  }
}

const p = new Person('John', 'Doe');
output.textContent = 'fullName: ' + p.fullName + '\n';
p.fullName = 'Jane Smith';
output.textContent += '変更後: ' + p.fullName;
</script>

例:静的メソッドとゲッター/セッター

HTML
<div id="output" style="white-space: pre; font-family: monospace; padding: 10px; border: 1px solid #ccc;"></div>

<script>
const output = document.getElementById('output');

class Circle {
  static count = 0;

  constructor(radius) {
    this.radius = radius;
    Circle.count++;
  }

  get area() {
    return Math.PI * this.radius * this.radius;
  }

  get diameter() {
    return this.radius * 2;
  }

  set diameter(value) {
    this.radius = value / 2;
  }

  static createUnit() {
    return new Circle(1);
  }
}

const c1 = new Circle(5);
const c2 = new Circle(10);
const c3 = Circle.createUnit();

output.textContent = '半径5の円:\n';
output.textContent += '  面積: ' + c1.area.toFixed(2) + '\n';
output.textContent += '  直径: ' + c1.diameter + '\n';
output.textContent += '  直径を20に設定:\n';
c1.diameter = 20;
output.textContent += '  新しい半径: ' + c1.radius + '\n\n';
output.textContent += '作成された円の合計: ' + Circle.count + '\n';
output.textContent += '単位円の半径: ' + c3.radius;
</script>
▶ 試してみよう

継承

extends で子クラスが親クラスのプロパティとメソッドを継承します。super で親のコンストラクタやメソッドを呼び出します。

例:継承のデモ

HTML
<div id="output" style="white-space: pre; font-family: monospace; padding: 10px; border: 1px solid #ccc;"></div>

<script>
const output = document.getElementById('output');

class Animal {
  constructor(name, sound) {
    this.name = name;
    this.sound = sound;
  }

  speak() {
    return this.name + 'の声: ' + this.sound;
  }
}

class Dog extends Animal {
  constructor(name, breed) {
    super(name, 'ワンワン');
    this.breed = breed;
  }

  fetch(item) {
    return this.name + 'が' + item + 'を取ってきました';
  }
}

class Cat extends Animal {
  constructor(name, indoor) {
    super(name, 'ニャー');
    this.indoor = indoor;
  }

  purr() {
    return this.name + 'がゴロゴロしています...';
  }
}

const dog = new Dog('レックス', '柴犬');
const cat = new Cat('ミケ', true);

output.textContent = dog.speak() + '\n';
output.textContent += dog.fetch('フリスビー') + '\n';
output.textContent += cat.speak() + '\n';
output.textContent += cat.purr();
</script>
▶ 試してみよう
💡 子クラスの constructor では、this を使う前に super() を呼び出す必要があります。親が初期化されるまで、子の this は存在しないためです。継承を学ぶ際に最も忘れやすいルールです。


ES モジュール

モジュールは JS のコード整理単位です。1つのモジュール = 1つのファイル。モジュール内の変数はデフォルトでプライベートで、明示的に export されたアイテムのみが外部からアクセスできます。

名前付きエクスポート / インポート

HTML
<script>
// math.js
export const PI = 3.14;
export function add(a, b) { return a + b; }

// app.js
import { PI, add } from './math.js';
</script>

デフォルトエクスポート / インポート

HTML
<script>
// logger.js
export default function log(msg) { console.log(msg); }

// app.js
import log from './logger.js';
</script>

HTML でモジュールを使う

script タグに type="module" を追加するだけです。

HTML
<script type="module">
  import { add } from './math.js';
  console.log(add(1, 2));
</script>
⚠️ モジュールファイルはブラウザによってキャッシュされ、同一オリジンポリシーの対象になります。ローカルで HTML ファイルを開くとモジュールの読み込みに失敗する可能性があります。HTTP サーバー経由で提供する必要があります。

例:単一ファイルモジュールシミュレーション(インラインモジュール)

実際のプロジェクトでは、モジュールは別々のファイルに分割する必要があります。ここでは type="module" を使って単一の HTML ファイル内でシミュレーションします。

HTML
<div id="output" style="padding: 10px; border: 1px solid #ccc;"></div>

<script type="module">
const output = document.getElementById('output');

const calculator = {
  add(a, b) { return a + b; },
  subtract(a, b) { return a - b; },
  multiply(a, b) { return a * b; },
  divide(a, b) { return b !== 0 ? a / b : 'ゼロでは割れません'; }
};

const formatter = {
  currency(value) { return '¥' + value.toFixed(2); },
  percent(value) { return (value * 100).toFixed(1) + '%'; }
};

const r1 = calculator.add(10, 20);
const r2 = calculator.multiply(5, 4);
const r3 = calculator.divide(10, 3);

output.textContent = '10 + 20 = ' + r1 + '\n';
output.textContent += '5 × 4 = ' + r2 + '\n';
output.textContent += '10 ÷ 3 = ' + formatter.currency(r3) + '\n';
output.textContent += '0.85 → ' + formatter.percent(0.85);

// 実際のプロジェクトではこのように分割します:
// calculator.js → export { calculator }
// formatter.js → export { formatter }
// main.js → import { calculator } from './calculator.js'
//           import { formatter } from './formatter.js'
</script>
▶ 試してみよう

📖 まとめ

  1. class はコンストラクタのシンタシュガー。constructor はイニシャライザで、メソッドはプロトタイプ上に定義される
  2. static メソッドはクラスに属し、インスタンスには属さない。ユーティリティやファクトリメソッドに最適
  3. get/set は読み書きをインターセプトする仮想プロパティを定義。バリデーションや計算に使う
  4. extends で継承を有効にし、子の constructor は最初に super() を呼び出す必要がある
  5. ES モジュールは export/import でコードの分離と再利用を実現。HTML では type="module" で有効に
  6. モジュールは機能別にコードを整理し、遅延読み込みを可能にし、グローバル名前空間の汚染を防止

❓ よくある質問

Q クラスとコンストラクタ関数の本当の違いは何ですか?
A 本質的な違いはありません。クラスはシンタシュガーです。ただし、クラスにはいくつかの特徴があります:new での呼び出しが必須(プレーン関数として実行不可)、メソッドは列挙不可、デフォルトで strict モード。構文がよりクリーンなため、クラスが推奨されます。
Q importrequire の違いは何ですか?
A import は ES モジュール構文で、コンパイル時に静的に解析されます。require は CommonJS(Node.js)で、ランタイムに動的に読み込まれます。ブラウザは ES モジュールのみをネイティブサポートします。import はトップレベルにある必要があり、require はどこにでも置けます。
Q デフォルトエクスポートと名前付きエクスポートを混在できますか?
A はい。1つのモジュールに1つのデフォルトエクスポートと複数の名前付きエクスポートを持つことができます:export default App; export const utils = {};。インポートは:import App, { utils } from './module.js'

📝 演習

  1. 基礎widthheight プロパティ、getArea()getPerimeter() メソッドを持つ Rectangle クラスを作成してください。
  2. 中級Rectangleget area() ゲッターと set area(value) セッターを追加してください(面積を設定すると幅/高さが比例調整される)。その後、Rectangle を継承する Square サブクラスを作成してください。
  3. チャレンジon(event, callback)off(event, callback)emit(event, data) メソッドを持つ EventManager クラスを設計し、シンプルなイベントシステムをシミュレートしてください(ヒント:イベントをコールバックの配列にマッピングするオブジェクトを使う)。
100%