チュートリアル

JavaScript基礎:クラス

JavaScript入門クラスオブジェクト指向
広告エリア

クラスとは

クラスはオブジェクトの設計図です。ES6で導入された構文糖衣で、内部的にはプロトタイプベースの継承を使っています。

クラスの定義

class User {
  // コンストラクタ(初期化処理)
  constructor(name, age) {
    this.name = name;
    this.age = age;
  }

  // メソッド
  greet() {
    return `こんにちは、${this.name}です`;
  }

  // ゲッター
  get profile() {
    return `${this.name} (${this.age}歳)`;
  }

  // セッター
  set profile(value) {
    const [name, age] = value.split(",");
    this.name = name;
    this.age = parseInt(age);
  }
}

// インスタンス化
const user = new User("太郎", 25);
console.log(user.name);      // "太郎"
console.log(user.greet());   // "こんにちは、太郎です"
console.log(user.profile);   // "太郎 (25歳)"

user.profile = "花子,30";
console.log(user.name);  // "花子"

プロパティ

パブリックプロパティ

class Product {
  // クラスフィールド(ES2022)
  name = "";
  price = 0;
  inStock = true;

  constructor(name, price) {
    this.name = name;
    this.price = price;
  }
}

const product = new Product("本", 1500);
console.log(product.inStock);  // true

プライベートプロパティ

#プレフィックスでプライベートにします(ES2022)。

class BankAccount {
  #balance = 0;  // プライベート

  constructor(initialBalance) {
    this.#balance = initialBalance;
  }

  deposit(amount) {
    if (amount > 0) {
      this.#balance += amount;
    }
  }

  withdraw(amount) {
    if (amount > 0 && amount <= this.#balance) {
      this.#balance -= amount;
      return amount;
    }
    return 0;
  }

  get balance() {
    return this.#balance;
  }
}

const account = new BankAccount(1000);
account.deposit(500);
console.log(account.balance);  // 1500
// console.log(account.#balance);  // エラー:外部からアクセス不可

静的プロパティとメソッド

インスタンスではなくクラス自体に属します。

class MathUtils {
  static PI = 3.14159;

  static add(a, b) {
    return a + b;
  }

  static multiply(a, b) {
    return a * b;
  }
}

// クラス名で直接アクセス
console.log(MathUtils.PI);         // 3.14159
console.log(MathUtils.add(1, 2));  // 3

// インスタンスからはアクセスできない
// const math = new MathUtils();
// math.add(1, 2);  // エラー

静的プライベート

class Counter {
  static #count = 0;  // 静的プライベート

  constructor() {
    Counter.#count++;
  }

  static getCount() {
    return Counter.#count;
  }
}

new Counter();
new Counter();
console.log(Counter.getCount());  // 2

クラス式

クラスは式としても定義できます。

// 無名クラス式
const User = class {
  constructor(name) {
    this.name = name;
  }
};

// 名前付きクラス式
const Product = class ProductClass {
  constructor(name) {
    this.name = name;
  }
};

const user = new User("太郎");
const product = new Product("本");

this の扱い

メソッド内の this

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

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

const user = new User("太郎");
user.greet();  // "こんにちは、太郎です"

// thisが失われるケース
const greetFn = user.greet;
// greetFn();  // エラー:this is undefined

アロー関数で this を保持

class Timer {
  constructor() {
    this.seconds = 0;
  }

  start() {
    // アロー関数は外側のthisを保持
    setInterval(() => {
      this.seconds++;
      console.log(this.seconds);
    }, 1000);
  }
}

const timer = new Timer();
timer.start();

bind でthisを固定

class Button {
  constructor(label) {
    this.label = label;
    // メソッドにthisをバインド
    this.handleClick = this.handleClick.bind(this);
  }

  handleClick() {
    console.log(`${this.label}がクリックされました`);
  }
}

const button = new Button("送信");
document.addEventListener("click", button.handleClick);

クラスフィールドでアロー関数

class Button {
  label = "ボタン";

  // アロー関数でthisを自動バインド
  handleClick = () => {
    console.log(`${this.label}がクリックされました`);
  };
}

instanceof と型チェック

class Animal {}
class Dog extends Animal {}

const dog = new Dog();

console.log(dog instanceof Dog);     // true
console.log(dog instanceof Animal);  // true
console.log(dog instanceof Object);  // true

// コンストラクタ名
console.log(dog.constructor.name);  // "Dog"

プロトタイプベースの理解

クラスは内部的にプロトタイプを使っています。

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

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

// 上記は以下と同等
function UserOld(name) {
  this.name = name;
}

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

// プロトタイプチェーン
const user = new User("太郎");
console.log(Object.getPrototypeOf(user) === User.prototype);  // true

実践例:商品カタログ

class Product {
  static #nextId = 1;

  #id;
  name;
  price;
  #stock;

  constructor(name, price, stock = 0) {
    this.#id = Product.#nextId++;
    this.name = name;
    this.price = price;
    this.#stock = stock;
  }

  get id() {
    return this.#id;
  }

  get stock() {
    return this.#stock;
  }

  get isAvailable() {
    return this.#stock > 0;
  }

  addStock(quantity) {
    if (quantity > 0) {
      this.#stock += quantity;
    }
  }

  sell(quantity = 1) {
    if (quantity <= this.#stock) {
      this.#stock -= quantity;
      return true;
    }
    return false;
  }

  toString() {
    return `[${this.#id}] ${this.name}: ¥${this.price} (在庫: ${this.#stock})`;
  }
}

// 使用例
const book = new Product("JavaScript入門", 2800, 10);
console.log(book.toString());  // "[1] JavaScript入門: ¥2800 (在庫: 10)"

book.sell(3);
console.log(book.stock);  // 7

const laptop = new Product("ノートPC", 120000, 5);
console.log(laptop.id);  // 2

まとめ

  • classでオブジェクトの設計図を定義
  • constructorで初期化処理
  • #プレフィックスでプライベート化
  • staticでクラスメンバーを定義
  • get/setでアクセサを定義
  • アロー関数やbindでthisを管理

次回は継承とオブジェクト指向について学びます。

広告エリア