JavaScript | ES6+ 文法:クラス構文 – class 構文

JavaScript JavaScript
スポンサーリンク

class 構文とは何か(まずイメージを掴む)

class 構文は、
同じような性質と振る舞いをもつオブジェクトを量産するための設計図を書くための文法」です。

「ユーザー」「商品」「敵キャラ」みたいな“種類”を表す型を作り、
その型から new で具体的なオブジェクト(インスタンス)を作ります。

class User {
  constructor(name) {
    this.name = name;
  }

  greet() {
    console.log(`こんにちは、${this.name} です`);
  }
}

const u1 = new User("Alice");
const u2 = new User("Bob");

u1.greet(); // こんにちは、Alice です
u2.greet(); // こんにちは、Bob です
JavaScript

ここが重要です。
class は「新しい仕組み」ではなく、従来の「コンストラクタ関数+prototype」の書き方を、読みやすくした“文法シュガー”です。
でも初心者目線では「オブジェクトの設計図を書くための素直な構文」と思ってOKです。


class の基本構成(constructor とメソッド)

一番シンプルな class の形

典型的な class は、こういうパーツでできています。

class User {
  // ① コンストラクタ(インスタンス生成時に一度だけ呼ばれる)
  constructor(name) {
    this.name = name; // インスタンスごとのプロパティ
  }

  // ② インスタンスメソッド(インスタンスに対する操作)
  greet() {
    console.log(`こんにちは、${this.name} です`);
  }
}
JavaScript

使うときは new を付けてインスタンスを作ります。

const user = new User("Alice");
user.greet();
JavaScript

ここが重要です。

  • constructor の中で this.xxx = ... したものが、「インスタンスが持つプロパティ」
  • greet() のように class の中に書いた関数が、「インスタンスメソッド」
  • new User(...) を呼ぶと、
    1. 空のオブジェクトが作られ
    2. そのオブジェクトの __proto__(プロトタイプ)が User.prototype にリンクされ
    3. constructor が呼ばれて this がそのインスタンスになる

という処理が裏で行われています(仕組みはそのくらいの理解で十分です)。


constructor と this の関係(ここはしっかり理解してほしい)

constructor の役割

constructor は、「インスタンスの初期状態を決める場所」です。

class Counter {
  constructor(initialValue = 0) {
    this.value = initialValue;
  }

  increment() {
    this.value++;
  }

  show() {
    console.log(this.value);
  }
}

const c = new Counter(10);
c.increment();
c.show(); // 11
JavaScript

this.value によって、「そのインスタンスだけが持つ値」をセットしています。
new Counter(10) するたびに、別々の value を持つカウンターが出来上がります。

this は「そのインスタンス自身」

メソッド内で this を使うと、「そのメソッドを呼び出したインスタンス」を指します。

const c1 = new Counter(1);
const c2 = new Counter(100);

c1.increment();
c1.show(); // 2

c2.increment();
c2.show(); // 101
JavaScript

c1.increment() の中の thisc1
c2.increment() の中の thisc2 を指します。

ここが重要です。
class のインスタンスメソッドでは、this は「そのインスタンス」を指す
アロー関数だと this の挙動が変わるので、メソッド定義には基本的に通常のメソッド構文を使います。


従来の書き方との比較(コンストラクタ関数 vs class)

旧スタイル(コンストラクタ関数+prototype)

class がなかった頃の書き方はこうでした。

function User(name) {
  this.name = name;
}

User.prototype.greet = function () {
  console.log(`こんにちは、${this.name} です`);
};

const u = new User("Alice");
u.greet();
JavaScript

新スタイル(class 構文)

同じことを class で書き直すとこうなります。

class User {
  constructor(name) {
    this.name = name;
  }

  greet() {
    console.log(`こんにちは、${this.name} です`);
  }
}

const u = new User("Alice");
u.greet();
JavaScript

ここが重要です。
意味はほぼ同じで、「prototype にメソッドを追加している」ことも変わりません。
ただし class のほうが「設計図」として読みやすく、ミスも減りやすいので、
新規コードでは基本的に class 構文を使うのが一般的です。


インスタンスメソッドと静的メソッド(static)の違い

インスタンスメソッド:インスタンスに対する処理

さきほど書いた greet() のようなメソッドは、インスタンスに対して呼び出します。

class User {
  constructor(name) {
    this.name = name;
  }

  greet() {
    console.log(`Hi, ${this.name}`);
  }
}

const u = new User("Alice");
u.greet(); // インスタンスに対して呼ぶ
JavaScript

static メソッド:クラス自体に対する処理

static を付けたメソッドは、「インスタンスではなくクラス自体にぶら下がるメソッド」です。

class User {
  constructor(name) {
    this.name = name;
  }

  greet() {
    console.log(`Hi, ${this.name}`);
  }

  static fromObject(obj) {
    return new User(obj.name);
  }
}

const data = { name: "Bob" };

// static メソッドはクラス名から呼ぶ
const u = User.fromObject(data);
u.greet();
JavaScript

ここが重要です。

  • インスタンスメソッド:u.greet() のようにインスタンスから呼ぶ。this はそのインスタンス。
  • static メソッド:User.fromObject() のようにクラスから呼ぶ。this はクラス(User)を指す。

「インスタンス1つ1つに紐づく処理」→ インスタンスメソッド
「クラスに関するユーティリティ的な処理」→ static メソッド
という整理で考えると混乱しにくいです。


継承(extends)を使ってクラスを拡張する

基本的な継承の書き方

extends を使うと、「既存のクラスをベースに新しいクラスを作る(継承する)」ことができます。

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

  speak() {
    console.log(`${this.name} が何かを喋った`);
  }
}

class Dog extends Animal {
  bark() {
    console.log(`${this.name}「わん!」`);
  }
}

const d = new Dog("ポチ");
d.speak(); // ポチ が何かを喋った(親クラスのメソッド)
d.bark();  // ポチ「わん!」(Dog 独自メソッド)
JavaScript

DogAnimal を継承しているので、Animal のメソッド speak() をそのまま使えます。

子クラスでコンストラクタを追加する(super)

親クラスに constructor がある場合、子クラスで自分の constructor を定義するときは super(...) を呼ぶ必要があります。
super(...) は「親クラスの constructor を呼ぶ」ものです。

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

  speak() {
    console.log(`${this.name} が何かを喋った`);
  }
}

class Dog extends Animal {
  constructor(name, breed) {
    super(name);        // 親の constructor を呼ぶ
    this.breed = breed; // Dog 独自のプロパティ
  }

  info() {
    console.log(`${this.name} (${this.breed})`);
  }
}

const d = new Dog("ポチ", "柴犬");
d.speak(); // ポチ が何かを喋った
d.info();  // ポチ (柴犬)
JavaScript

ここが重要です。

  • 子クラスで constructor を定義する場合、必ず最初に super(...) を呼ばないといけない
  • super(...) を呼ぶことで、「親クラス側の初期化(name をセットするなど)」が実行される

初心者のうちは、「子コンストラクタを書くときは、まず super(...) を書く」と覚えておけばOKです。


クラスの中でできること(プロパティ・メソッドのバリエーション)

フィールド(プロパティ)の宣言(簡易版)

最近の仕様では、class の中で「プロパティ(フィールド)」を直接宣言する書き方も使われます(環境による対応差はあります)。

class Counter {
  value = 0; // インスタンスフィールド(constructor で this.value = 0 と書くのと似た意味)

  increment() {
    this.value++;
  }
}
JavaScript

まだ全部の環境で完全に同じように動くとは限らないので、
まずは「constructor の中で this.xxx = ... する」スタイルをしっかり身につけ、
フィールド宣言はそこから一歩踏み込んだ便利機能と捉えておくと安心です。

ゲッター / セッター(getter / setter)

プロパティの読み書きに「一手間挟みたい」ときは、get / set を使ったアクセサも定義できます。

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

  get fullName() {
    return `${this.firstName} ${this.lastName}`;
  }

  set fullName(value) {
    const [first, last] = value.split(" ");
    this.firstName = first;
    this.lastName  = last;
  }
}

const u = new User("Alice", "Smith");
console.log(u.fullName); // getter を通して計算された値

u.fullName = "Bob Brown"; // setter が呼ばれる
console.log(u.firstName); // Bob
console.log(u.lastName);  // Brown
JavaScript

ここが重要です。
fullName は「メソッド」ではなく、「プロパティのように見えるけど中で処理を挟んでいる」もの。
設計として「プロパティアクセスの裏側で処理を行いたい」ときに使います。


よくある勘違いと落とし穴(重要ポイント)

クラスのメソッドをアロー関数にしないほうがいい理由

クラスの中で、インスタンスメソッドをアロー関数で書くと this の挙動が変わります。

class Bad {
  constructor() {
    this.value = 1;
  }

  // よくない例(用途を分かっていればアリだが、初心者には非推奨)
  method = () => {
    console.log(this.value);
  };
}
JavaScript

これ自体は最近の仕様で許される書き方ですが、
アロー関数は「自分の this を持たない」ため、継承や prototype ベースの動きと絡むとややこしくなります。

初心者のうちは、

  • クラスのインスタンスメソッドは method() { ... } の形
  • this に関して複雑なことをしたいときだけ、慎重にアロー関数を検討

というルールにしておくと安全です。

インスタンスを new し忘れる

class を普通の関数のように呼ぶとエラーになります。

class User {}

User(); // TypeError: Class constructor User cannot be invoked without 'new'
JavaScript

必ず new User() のように new を付けて呼びます。
これは「class はコンストラクタ関数のラッパだが、new なしでは使えないようにしてある」ためです。


例題で理解を固める

例1:簡単な BankAccount クラス

class BankAccount {
  constructor(owner, balance = 0) {
    this.owner = owner;
    this.balance = balance;
  }

  deposit(amount) {
    this.balance += amount;
  }

  withdraw(amount) {
    if (this.balance < amount) {
      console.log("残高不足です");
      return false;
    }
    this.balance -= amount;
    return true;
  }

  show() {
    console.log(`${this.owner} の残高: ${this.balance} 円`);
  }
}

const acc = new BankAccount("Alice", 1000);
acc.deposit(500);
acc.withdraw(300);
acc.show(); // Alice の残高: 1200 円
JavaScript

例2:継承を使った Shape クラス

class Shape {
  constructor(color = "black") {
    this.color = color;
  }

  describe() {
    console.log(`この図形の色は ${this.color} です`);
  }
}

class Circle extends Shape {
  constructor(radius, color) {
    super(color);
    this.radius = radius;
  }

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

  describe() {
    // 親クラスの describe も呼びたい場合
    super.describe();
    console.log(`半径は ${this.radius}、面積は ${this.area()} です`);
  }
}

const c = new Circle(5, "red");
c.describe();
JavaScript

例3:static メソッドで工場メソッドを書く

class User {
  constructor(name, admin = false) {
    this.name = name;
    this.admin = admin;
  }

  static createAdmin(name) {
    return new User(name, true);
  }

  describe() {
    console.log(`${this.name} (${this.admin ? "管理者" : "一般ユーザー"})`);
  }
}

const u1 = new User("Alice");
const u2 = User.createAdmin("Bob");

u1.describe(); // Alice (一般ユーザー)
u2.describe(); // Bob (管理者)
JavaScript

まとめ

class 構文の核心は、
「同じ型のオブジェクト(インスタンス)を、設計図にもとづいてわかりやすく量産する」 ことです。

押さえておきたいポイントは次のあたりです。

  • class X { constructor(...) { ... } method() { ... } } が基本形
  • new X(...) でインスタンスを作り、this はそのインスタンスを指す
  • インスタンスごとのデータは this.xxx に持たせる
  • インスタンスに対する操作はインスタンスメソッド、クラス全体に対する便利関数は static メソッド
  • extendssuper でクラスを継承し、共通部分を親クラスにまとめられる
  • 従来の「コンストラクタ関数+prototype」を読みやすくしたものだと理解しておく

最初は小さなクラスからで構いません。
「関数+オブジェクト」の組み合わせで書いていたものを、少しずつ class に書き換えてみるところから慣れていくと、
クラス構文への苦手意識がかなり和らぎます。

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