読み込み中...
読み込み中...
読み込み中...
読み込み中...
読み込み中...
JavaScriptのオブジェクトは、他のオブジェクトへの内部リンク([[Prototype]])を持っています。あるオブジェクトのプロパティにアクセスしたとき、そのオブジェクト自身にプロパティが存在しなければ、[[Prototype]] リンクを辿って親オブジェクトを探索します。この連鎖をプロトタイプチェーンと呼びます。
プロトタイプチェーンの終端は Object.prototype で、その [[Prototype]] は null です。すべてのオブジェクトは最終的に Object.prototype に到達します。
| API | 用途 |
|---|---|
Object.getPrototypeOf(obj) | obj のプロトタイプを取得(推奨) |
Object.create(proto) | proto をプロトタイプとする新オブジェクトを作成 |
obj.hasOwnProperty(key) | 自身のプロパティかどうかを判定 |
obj.__proto__ | プロトタイプへのアクセス(非推奨・レガシー) |
プロトタイプチェーンの確認
// コンストラクタ関数とプロトタイプ
function Animal(name) {
this.name = name;
}
Animal.prototype.speak = function() {
return this.name + "が鳴いています";
};
const dog = new Animal("ポチ");
console.log(dog.speak()); // "ポチが鳴いています"
console.log(dog.hasOwnProperty("name")); // true(自身のプロパティ)
console.log(dog.hasOwnProperty("speak")); // false(プロトタイプのプロパティ)
// プロトタイプチェーンの確認
console.log(Object.getPrototypeOf(dog) === Animal.prototype); // true
console.log(Object.getPrototypeOf(Animal.prototype) === Object.prototype); // trueチャレンジ
Object.create を使って、base オブジェクトをプロトタイプとする child オブジェクトを作成してください。child に独自のプロパティ type を "derived" として追加してください。
ポイント
プロトタイプチェーンは、オブジェクトのプロパティ探索を親オブジェクトへ順に辿る仕組みです。Object.create でプロトタイプを明示的に設定でき、hasOwnProperty で自身のプロパティかどうかを判定できます。
前提知識
この章の内容を理解するには、JavaScript基礎コースのオブジェクトとJSONで学ぶオブジェクトリテラル、プロパティアクセス、分割代入の基本が前提となります。
ES6(ES2015)で導入された class 構文は、プロトタイプベースの継承をより直感的に書くための**シンタックスシュガー(構文糖衣)**です。内部的にはプロトタイプの仕組みが使われていますが、クラスベースの言語に慣れた開発者にとって読みやすいコードを書けます。
クラスの主な構成要素は以下の通りです。
| 要素 | 説明 |
|---|---|
constructor | インスタンス生成時に呼ばれる初期化メソッド |
| インスタンスメソッド | プロトタイプに追加されるメソッド |
static メソッド | クラス自体に属するメソッド(インスタンスからは呼べない) |
get / set | ゲッター・セッターによるプロパティアクセスのカスタマイズ |
| クラスフィールド | インスタンスプロパティの宣言(ES2022) |
ES6クラスの構成要素
class User {
// クラスフィールド(ES2022)
#loginCount = 0; // プライベートフィールド
constructor(name, email) {
this.name = name;
this.email = email;
}
// インスタンスメソッド(プロトタイプに追加される)
login() {
this.#loginCount++;
return `${this.name} がログインしました(${this.#loginCount}回目)`;
}
// ゲッター
get displayName() {
return `${this.name} <${this.email}>`;
}
// セッター
set displayName(value) {
const [name, email] = value.split(" <");
this.name = name;
this.email = email.replace(">", "");
}
// static メソッド
static fromJSON(json) {
const data = JSON.parse(json);
return new User(data.name, data.email);
}
}
const user = new User("田中", "tanaka@example.com");
console.log(user.login()); // "田中 がログインしました(1回目)"
console.log(user.displayName); // "田中 <tanaka@example.com>"
const user2 = User.fromJSON('{"name":"佐藤","email":"sato@example.com"}');
console.log(user2.name); // "佐藤"チャレンジ
Rectangle クラスを実装してください。コンストラクタで width と height を受け取り、area() メソッドで面積を、perimeter() メソッドで周囲の長さを返します。また static メソッド square(size) で正方形のインスタンスを生成できるようにしてください。
ポイント
ES6 の class 構文はプロトタイプベースの継承を直感的に書くためのシンタックスシュガーです。constructor、インスタンスメソッド、static メソッド、ゲッター/セッター、プライベートフィールド(#)を使ってクラスを構成します。
extends キーワードを使うと、既存のクラスを継承した新しいクラスを作成できます。子クラス(サブクラス)は親クラス(スーパークラス)のメソッドとプロパティをすべて引き継ぎます。
super キーワードには2つの用途があります。
| 用途 | 書き方 | 説明 |
|---|---|---|
| コンストラクタ内 | super(args) | 親クラスのコンストラクタを呼び出す(必須) |
| メソッド内 | super.method() | 親クラスのメソッドを呼び出す |
子クラスのコンストラクタでは、this を使う前に必ず super() を呼ぶ必要があります。これを忘れると ReferenceError が発生します。
extends による継承と super の使い方
class Shape {
constructor(color) {
this.color = color;
}
describe() {
return `${this.color}の図形`;
}
}
class Circle extends Shape {
constructor(color, radius) {
super(color); // 親の constructor を呼ぶ(必須)
this.radius = radius;
}
area() {
return Math.PI * this.radius ** 2;
}
// メソッドオーバーライド
describe() {
return `${super.describe()}(円、半径${this.radius})`;
}
}
const c = new Circle("赤", 5);
console.log(c.describe()); // "赤の図形(円、半径5)"
console.log(c.area()); // 78.539...
console.log(c instanceof Circle); // true
console.log(c instanceof Shape); // trueチャレンジ
Vehicle クラスを継承した ElectricCar クラスを実装してください。Vehicle は make(メーカー)と speed(速度、初期値0)を持ちます。ElectricCar は追加で batteryLevel(初期値100)を持ち、drive(km) メソッドをオーバーライドして速度を設定しつつバッテリーを km 分減らしてください。
ポイント
extends でクラスを継承し、子クラスのコンストラクタでは this を使う前に super() を呼ぶ必要があります。super.method() で親クラスのメソッドを呼び出してオーバーライドと組み合わせることで、機能を拡張できます。
JavaScriptは単一継承のみをサポートしています(1つのクラスは1つの親クラスしか持てない)。複数の機能を組み合わせたい場合、深い継承階層を作るのではなく、**ミックスイン(Mixin)やコンポジション(Composition)**を使うのが効果的です。
**Composition over Inheritance(継承よりコンポジション)**は、ソフトウェア設計の重要な原則です。
| アプローチ | 特徴 | 適しているケース |
|---|---|---|
| 継承 | 「is-a」関係。親の全機能を引き継ぐ | 明確な階層関係がある場合 |
| ミックスイン | 複数の機能を混ぜ合わせる | 横断的な機能を共有する場合 |
| コンポジション | 「has-a」関係。機能を組み合わせる | 柔軟な機能の組み合わせが必要な場合 |
深い継承階層は変更に弱く、親クラスの修正が予期しない影響を子クラスに与えます(脆い基底クラス問題)。コンポジションなら部品の差し替えが容易です。
Object.assign によるミックスインパターン
// ミックスインパターン
const Serializable = {
toJSON() {
return JSON.stringify(this);
},
fromJSON(json) {
return Object.assign(this, JSON.parse(json));
}
};
const Validatable = {
validate() {
for (const [key, value] of Object.entries(this)) {
if (value === null || value === undefined) {
return { valid: false, field: key };
}
}
return { valid: true };
}
};
// Object.assign でミックスインを適用
class Product {
constructor(name, price) {
this.name = name;
this.price = price;
}
}
Object.assign(Product.prototype, Serializable, Validatable);
const item = new Product("ノートPC", 89800);
console.log(item.toJSON()); // '{"name":"ノートPC","price":89800}'
console.log(item.validate()); // { valid: true }コンポジションによる機能の組み合わせ
// コンポジションパターン
function createLogger(prefix) {
return {
log(msg) { console.log(`[${prefix}] ${msg}`); },
error(msg) { console.error(`[${prefix}] ERROR: ${msg}`); }
};
}
function createStorage() {
const data = new Map();
return {
save(key, value) { data.set(key, value); },
load(key) { return data.get(key); }
};
}
// 機能を組み合わせてオブジェクトを構成
function createApp(name) {
return {
name,
...createLogger(name),
...createStorage()
};
}
const app = createApp("MyApp");
app.log("起動しました"); // "[MyApp] 起動しました"
app.save("config", { theme: "dark" });
console.log(app.load("config")); // { theme: "dark" }チャレンジ
コンポジションパターンを使って createUser 関数を実装してください。name を受け取り、greet() メソッド("Hello, <name>" を返す)と rename(newName) メソッド(名前を変更して "Renamed to <newName>" を返す)を持つオブジェクトを返してください。
ポイント
JavaScriptは単一継承のみサポートします。ミックスイン(Object.assign)やコンポジション(ファクトリー関数 + スプレッド構文)を使えば、複数の機能を柔軟に組み合わせられます。深い継承階層よりコンポジションが推奨されます。
実務での活用
共通の機能を持つオブジェクトを量産する場面——たとえばユーザー、商品、注文といったデータモデルの定義にクラスが使われます。extends による継承は便利ですが、複数の小さな機能を組み合わせるコンポジションの方が柔軟な設計になる場面も多いため、両方の手法を使い分けられると設計力が向上します。
用語