ここでは、初心者がハマりやすい典型的なバグを「再現→原因→対策」の流れで紹介します。
ケース1:プロトタイプでオブジェクトを共有してしまう
🧩 状況
全インスタンスが同じ配列・オブジェクトを参照してしまうパターン。
function Person(name) {
this.name = name;
}
// ❌ NG: prototype に配列を直接置く
Person.prototype.hobbies = [];
const alice = new Person("Alice");
const bob = new Person("Bob");
alice.hobbies.push("reading");
console.log(bob.hobbies); // ["reading"] ← えっ!?
JavaScript🔍 原因
hobbies は全インスタンスで同じ配列を 共有参照 している。
プロトタイプにあるオブジェクト型(配列・オブジェクト)は「1個しかない」から、誰かが変更すると全員に影響。
✅ 対策
配列やオブジェクトなど可変のデータは、prototype ではなく「各インスタンス内」に作る。
function Person(name) {
this.name = name;
this.hobbies = []; // インスタンスごとに新しく作る
}
JavaScriptケース2:this の参照が意図せず変わる
🧩 状況
メソッドを変数に取り出して呼び出すと this が失われる。
const user = {
name: "Alice",
greet() {
console.log("Hello, " + this.name);
}
};
const greetFn = user.greet;
greetFn(); // "Hello, undefined"(またはエラー)
JavaScript🔍 原因
this は「呼び出し方」で決まる。greetFn() はどのオブジェクトからも呼ばれていないため、this が undefined になる(strict mode)か、window になる。
✅ 対策
bindでthisを固定する
const greetFn = user.greet.bind(user);
greetFn(); // Hello, Alice
JavaScript- またはアロー関数で
thisを外側に固定する
const user = {
name: "Alice",
greet: () => console.log("Hello, " + this.name) // ただし、class 内では非推奨
};
JavaScriptケース3:for...in がプロトタイプのプロパティまで列挙する
🧩 状況
意図しないプロパティが出てくる。
const parent = { shared: true };
const child = Object.create(parent);
child.own = true;
for (const key in child) {
console.log(key);
}
// 出力:
// own
// shared ← prototype 上のプロパティまで!
JavaScript✅ 対策
自分のプロパティだけを扱うようにする:
for (const key in child) {
if (child.hasOwnProperty(key)) {
console.log(key);
}
}
JavaScriptあるいは最近は Object.keys() や Object.entries() を使う方が安全。
ケース4:delete しても prototype 側の値が見えてしまう
🧩 状況
「削除したのに、まだ残ってる?」という現象。
const parent = { value: 10 };
const child = Object.create(parent);
child.value = 5;
delete child.value;
console.log(child.value); // 10 ← prototype 側の value が見える
JavaScript🔍 原因
delete は「自分のプロパティを消す」だけ。
その後、プロトタイプチェーンを辿って parent.value が見つかるから表示される。
✅ 対策
hasOwnProperty() を確認して「自分の値かどうか」を判別する:
console.log(child.hasOwnProperty("value")); // false → 親の値を参照中
JavaScriptケース5:継承時に constructor を失う
🧩 状況
Object.create で継承したときに、constructor が上書きされる。
function Animal(name) {
this.name = name;
}
Animal.prototype.sayHi = function() {
console.log(this.name);
};
function Dog(name) {
Animal.call(this, name);
}
Dog.prototype = Object.create(Animal.prototype); // ← ここで constructor 消失
Dog.prototype.bark = function() {
console.log("Woof!");
};
const dog = new Dog("Pochi");
console.log(dog.constructor === Dog); // false(Animalになってしまう)
JavaScript✅ 対策
constructor を手動で復元する。
Dog.prototype.constructor = Dog;
JavaScriptケース6:クラス継承でも同じような落とし穴(共有データ)
ES6 class でも、プロトタイプの仕組みを使っているため似た罠がある。
class Person {
static shared = [];
constructor(name) {
this.name = name;
}
}
const a = new Person("Alice");
const b = new Person("Bob");
Person.shared.push("data");
console.log(a.shared); // undefined
console.log(Person.shared); // ["data"] ← 静的プロパティだから共有!
JavaScriptもしインスタンスごとに別のデータを持ちたいなら、this.shared = [] を constructor の中で定義する。
ケース7:プロトタイプチェーンが長すぎてパフォーマンス低下
あまりに深いチェーンを作ると、プロパティ探索コストが増える。
これは「バグ」ではないが、思い通りに高速に動かない原因になりうる。
const base = { baseProp: 1 };
const level1 = Object.create(base);
const level2 = Object.create(level1);
const level3 = Object.create(level2);
const level4 = Object.create(level3);
console.log(level4.baseProp); // base まで4段も探索
JavaScript現代のエンジンでは最適化されることも多いが、あまり深い継承構造は避けよう。
総まとめ:バグ防止の黄金ルール
| ルール | 意味 |
|---|---|
| 可変データは prototype に置かない | 全インスタンスで共有されてしまう |
this に依存する関数は bind or arrow | 呼び出し方で壊れる |
| 列挙時は hasOwnProperty チェック | prototype の値が混ざる |
| constructor を手動で復元 | Object.create 継承で失われる |
| プロトタイプチェーンは浅く | 深すぎる継承構造は遅くて複雑 |

