📝
JavaScriptの「prototype / this / class / extends」を1本で理解する
フロントエンド開発をしていると、当たり前のように使っているこれ👇
thisclassextendsprototype
正直「なんとなく動く」で使えてしまうけど、
デバッグで詰まるのはだいたいこのあたり。
この記事では、JavaScriptのオブジェクトモデルを
「プロトタイプ視点」でシンプルに整理する。
① まず結論:JSはクラスベースではない
JavaScriptは
👉 プロトタイプベース言語
クラスっぽく書けるけど、内部では全部これ👇
オブジェクト → オブジェクト → オブジェクト → null
つまり「オブジェクトの連鎖」でできている。
② prototypeの正体
これが一番重要。
const arr = [1, 2, 3];
arr.map // 使える
でも実際は👇
arr.hasOwnProperty("map"); // false
👉 arr自身はmapを持っていない
じゃあどこから来てる?
arr.__proto__ === Array.prototype // true
👉 Array.prototypeから借りてるだけ
イメージ
arr
↓
Array.prototype(ここにmapがある)
↓
Object.prototype
↓
null
👉 これがプロトタイプチェーン
③ proto の正体
obj.__proto__
これは何かというと👇
👉 内部の [[Prototype]] にアクセスするためのアクセサ
ただしこれは非推奨。
今はこれ👇
Object.getPrototypeOf(obj)
Object.setPrototypeOf(obj, proto)
④ 「探す仕組み」を理解する
例えば👇
arr.toString()
内部ではこう動く👇
- arrにある? → ない
- Array.prototypeにある? → あった!
👉 近い順に探すだけ
⑤ ファクトリ関数の問題点
function createUser(name) {
return {
name,
greet() {
console.log(`Hello ${this.name}`);
}
};
}
一見OKだけど👇
const u1 = createUser("A");
const u2 = createUser("B");
u1.greet === u2.greet // false
👉 毎回関数が新しく作られる
⑥ prototypeで解決
function User(name) {
this.name = name;
}
User.prototype.greet = function () {
console.log(`Hello ${this.name}`);
};
const u1 = new User("A");
const u2 = new User("B");
u1.greet === u2.greet // true
👉 関数が共有される
⑦ new の中身
const obj = {};
obj.__proto__ = User.prototype;
User.call(obj);
return obj;
👉 prototypeをつなぐのが本質
⑧ classはただの書きやすい構文
class User {
constructor(name) {
this.name = name;
}
greet() {
console.log(`Hello ${this.name}`);
}
}
これは実質👇
function User(name) {
this.name = name;
}
User.prototype.greet = function () {
console.log(`Hello ${this.name}`);
};
👉 class = シンタックスシュガー
⑨ thisでハマる理由
button.addEventListener("click", user.greet);
👉 thisが変わる
理由👇
- 呼び出し元がbuttonになる
- userじゃなくなる
解決
button.addEventListener("click", user.greet.bind(user));
👉 thisを固定
⑩ extendsの正体
class Dog extends Animal {}
内部では👇
Dog.prototype = Object.create(Animal.prototype);
👉 prototypeを繋いでるだけ
⑪ superの正体
super.speak()
実質👇
Animal.prototype.speak.call(this);
👉 親のメソッドをthis付きで呼んでる
⑫ オーバーライドの本質
class Dog extends Animal {
speak() {
console.log("ワン");
}
}
👉 上書きではない
dog
↓
Dog.prototype ← ここで見つかる
↓
Animal.prototype
👉 先に見つかっただけ
⑬ フロントエンド的な理解
昔👇
class Component extends React.Component
今👇
- 関数コンポーネント
- hooks
👉 継承より合成(composition)
まとめ
重要なのはこれだけ👇
- prototype = 共有する場所
- proto = 参照リンク
- class = 書きやすくしただけ
- extends = prototype接続
- super = 親呼び出し
最後に
このあたりを理解すると👇
- 「thisバグ」
- 「メソッド見つからない」
- 「なぜ動くかわからないコード」
ここが一気にクリアになる。
フロントエンドやるなら、避けて通れない基礎。
参考
Discussion