📝

JavaScriptの「prototype / this / class / extends」を1本で理解する

に公開

フロントエンド開発をしていると、当たり前のように使っているこれ👇

  • this
  • class
  • extends
  • prototype

正直「なんとなく動く」で使えてしまうけど、
デバッグで詰まるのはだいたいこのあたり。

この記事では、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()

内部ではこう動く👇

  1. arrにある? → ない
  2. 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バグ」
  • 「メソッド見つからない」
  • 「なぜ動くかわからないコード」

ここが一気にクリアになる。

フロントエンドやるなら、避けて通れない基礎


参考

https://www.udemy.com/course/the-web-developer-bootcamp-2021-japan/?srsltid=AfmBOorlWxAHEEg6wSAPlxXAeyC4qAjxq2kfjJfvofw5KIxqF1DqOZYO

Discussion