クラス

JS

クラスとは

ES6で導入されたクラスについて。

親クラス(スーパークラス)、子クラス、コンストラクタ、インスタンス・・などがあり、

クラスを使うと、コードのメンテナンスがしやすくなり、再利用性が上がるというメリットがあります。

// 親クラスの定義
class Animal {
    constructor(name) {
    this.name = name;
  }

  // 親クラスのメソッド
  sound() {
    console.log("Animal makes a sound");
  }
}

// 子クラスの定義
class Dog extends Animal {
    constructor(name, breed) {
    super(name); // 親クラスのコンストラクタを呼び出す
    this.breed = breed;
  }

  // 子クラスのメソッド
  sound() {
    console.log("Woof!");//親クラスのメソッドをオーバーライド
  }

  // 子クラスの独自のメソッド
  wagTail() {
    console.log("Dog wags its tail");
  }
}

// 親クラスのインスタンス作成
const animal = new Animal("Generic Animal");
console.log(animal.name); // "Generic Animal"
animal.sound(); // "Animal makes a sound"

// 子クラスのインスタンス作成
const dog = new Dog("Buddy", "Golden Retriever");
console.log(dog.name); // "Buddy"
console.log(dog.breed); // "Golden Retriever"
dog.sound(); // "Woof!"
dog.wagTail(); // "Dog wags its tail"

ゲッター、セッター

そしてゲッター、セッターについて。

class Circle {
  constructor(radius) {
    this._radius = radius;
  }

  // ゲッター
  get radius() {
    return this._radius;
  }

  // セッター
  set radius(newRadius) {
    if (newRadius > 0) {
      this._radius = newRadius;
    } else {
      console.error("Radius must be a positive number.");
    }
  }
}

const circle = new Circle(5);

// ゲッターを使用してプロパティにアクセスし、プロパティの値を取得します
console.log(circle.radius); // 出力: 5

// セッターを使用してプロパティに新しい値を設定します
circle.radius = 10; // 正常に動作します

// ゲッターを使用してプロパティの値を取得します
console.log(circle.radius); // 出力: 10

// セッターを使用してプロパティに負の値を設定しようとしますが、セッターが定義された条件に反しているためエラーが発生します
circle.radius = -5; // エラー: Radius must be a positive number.

// プロパティは変更されず、以前の値が維持されています
console.log(circle.radius); // 出力: 10

ゲッターだけを設定すると、プロパティの値を変更不可にできます。

class Circle {
  constructor(radius) {
    this._radius = radius;
  }

  get radius() {
    return this._radius;
  }

  // このようにセッターを提供しないことで、半径を読み取り専用にする
}

const circle = new Circle(5);
console.log(circle.radius); // プロパティの値を取得する
circle.radius = 10; // エラー: ゲッターのみが定義されているため、プロパティの値を変更できません

一方セッターは、条件を満たさない値がセットされないようにすることができます。

class Rectangle {
constructor(width, height) {
this._width = width;
this._height = height;
}

// セッター
set width(newWidth) {
if (newWidth > 0) {
this._width = newWidth;
} else {
console.error("Width must be a positive number.");
}
}
}

const rectangle = new Rectangle(5, 10);

rectangle.width = -5; // Width must be a positive number.

クロージャー

クロージャーとは、関数がスコープ外で呼び出された時に、元のスコープ内の変数を覚えている機能です。

function Animal(name) {
  let _name = name; // プライベート変数

  this.getName = function() {
    return _name;
  };

  this.setName = function(newName) {
    _name = newName;
  };
}

const dog = new Animal('Rover');
console.log(dog.getName()); // 出力: Rover
dog.setName('Spot');
console.log(dog.getName()); // 出力: Spot
console.log(dog._name); // 出力: undefined (外部からはアクセスできない)

クラスを使う場合は以下のようになります。

class Animal {
  constructor(name) {
    let _name = name; // プライベート変数

    this.getName = function() {
      return _name;
    };

    this.setName = function(newName) {
      _name = newName;
    };
  }
}

const cat = new Animal('Whiskers');
console.log(cat.getName()); // 出力: Whiskers
cat.setName('Tom');
console.log(cat.getName()); // 出力: Tom
console.log(cat._name); // 出力: undefined (外部からはアクセスできない)

最初のクラスの例と何が違うかというと、

// 親クラスの定義
class Animal {
    constructor(name) {
    this.name = name;
  }

この this.name = name; が let _name = name; になっている箇所が違います。

クラス構文ではプライベートフィールドとして # を使う方法が標準化されています。

class Animal {
  #name; // プライベートフィールド

  constructor(name) {
    this.#name = name;
  }

  getName() {
    return this.#name;
  }

  setName(newName) {
    this.#name = newName;
  }
}

const bird = new Animal('Tweety');
console.log(bird.getName()); // 出力: Tweety
bird.setName('Polly');
console.log(bird.getName()); // 出力: Polly
console.log(bird.#name); // エラー: プライベートフィールドに直接アクセスできない

プライベートフィールドにすることで、カプセル化ができます。

カプセル化とは、

データとそれに関連するメソッドを一つの単位にまとめ、その内部構造を外部から隠すこと。

よって、上の例ではbird.#nameは直接アクセスできないようになります。

カプセル化はES6のクラス構文や即時関数(IIFE)で使うことができます。