JavaScript | クラス内でアロー関数を使う理由

JavaScript
スポンサーリンク

クラス内でのアロー関数の使い方を、動くコード例利点と注意点実務での使い分けを交えてわかりやすくまとめます。

アロー関数は this を「定義場所(レキシカル)」から拾う 特性があるので、クラスのインスタンスメソッドを外部に渡す(コールバックに渡す、イベント登録する、タイマーで呼ぶ等)ときに this の binding 問題を回避できます。


使い方パターン(例を見ながら)

1) 普通のメソッド(プロトタイプ上)

class Timer {
  constructor() {
    this.count = 0;
  }
  tick() {              // prototype に置かれるメソッド
    this.count++;
    console.log('tick', this.count);
  }
}

const t = new Timer();
setInterval(t.tick, 1000); // ❌ this が失われる(グローバルや undefined になる)
JavaScript

問題:setInterval に直接渡すと thisTimer のインスタンスではなくなる(bind されないため)。


2) コンストラクタで bind する(従来の回避策)

class Timer {
  constructor() {
    this.count = 0;
    this.tick = this.tick.bind(this); // 明示的に this を固定
  }
  tick() {
    this.count++;
    console.log('tick', this.count);
  }
}
JavaScript

利点:プロトタイプの利点(メソッドは一つだけ)を活かしつつ this 問題を解決できる。欠点:コンストラクタでの明示バインドが煩雑になることがある。


3) クラスフィールドにアロー関数を使う(簡潔・自動バインド)

class Timer {
  count = 0;
  tick = () => {         // インスタンスごとの関数(自動で this を正しく参照)
    this.count++;
    console.log('tick', this.count);
  }
}

const t = new Timer();
setInterval(t.tick, 1000); // ✅ 正しく動く
JavaScript

ポイント:

  • tick は各インスタンスごとに作られる(プロトタイプ上ではなくインスタンス自身のプロパティになる)。
  • this は常にそのインスタンスを参照するため、bind 不要。
  • シンタックスは class 内で field = () => {} の形(class fields)を使います。

インスタンスメソッド(arrow)とプロトタイプメソッドの違いまとめ

  • プロトタイプメソッド(通常のメソッド)
    • メモリ効率:全インスタンスで1つだけ(プロトタイプに存在)。
    • 動作:this は呼び出し方に依存(外で渡すと this が変わる)。
    • 適している場面:this を直接使わないユーティリティ的なメソッドや、頻繁にインスタンスを作る場合。
  • インスタンスプロパティとしてのアロー関数
    • メモリ:インスタンス毎に関数オブジェクトが生成される(インスタンス×1)。
    • 動作:作成時に this が固定(レキシカル)。外部に渡しても this が壊れない。
    • 適している場面:イベントハンドラ、コールバック、UIコンポーネントのメソッド(自動バインドが便利)。

実践例:イベントハンドラでの使い分け(ブラウザ想定)

<button id="btn">Click</button>
<script>
class Clicker {
  constructor() {
    this.count = 0;
    // 方式A: class field arrow(おすすめ)
    this.onClick = () => {
      this.count++;
      console.log('clicked', this.count);
    };

    // 方式B: prototype メソッド + bind(冗長)
    // this.onClick = this.onClick.bind(this);
  }

  // prototype メソッド(もしこちらを使うなら bind が必要)
  // onClick() {
  //   this.count++;
  //   console.log('clicked', this.count);
  // }
}

const c = new Clicker();
document.getElementById('btn').addEventListener('click', c.onClick);
</script>
JavaScript

ここでアロー関数(class field)を使っておくと、addEventListener に渡しても this が常に c を指すので安全です。


注意点・デメリット(知っておくべきこと)

  1. メモリ:インスタンスごとに関数が作られる → たくさんインスタンスを生成するアプリではメモリ負荷が増える場合がある。
  2. プロトタイプの利点がなくなる:継承やメソッド差し替えの振る舞いが従来と少し違う(メソッドはインスタンスプロパティのためプロトタイプ経由で差し替えづらい)。
  3. デバッグ表示:デバッガやプロファイラでメソッドがプロトタイプで見えないことがある(ただし大抵問題にならない)。
  4. 古い環境class fieldspublic class fields は古いブラウザではサポートされない場合があるので、サポートが必要ならトランスパイル(Babel等)を使う。
  5. 継承先でオーバーライドしたい場合:インスタンス関数だと継承で扱いが異なる(プロトタイプメソッドの方が継承向き)。

いつ使うか(実務的ガイド)

  • UI コンポーネント(ボタンのハンドラやコールバック) → アロー関数(class field)推奨(簡潔でバグが減る)。
  • ライブラリの基礎クラスや大量にインスタンス化する軽量オブジェクト → プロトタイプメソッド推奨(メモリ節約)。
  • 継承階層が深く、メソッドのオーバーライドを多用する設計 → プロトタイプメソッド が適していることが多い。

演習(2問) — 手で書いて動かしてみよう

  1. Worker クラスを作り、start メソッドを setInterval で1秒ごとに this.count を増やしてログするようにしてください。アロー関数の class-field 版で実装して動作確認する。
  2. 同じ Workerプロトタイプメソッドで実装して setInterval に渡した場合に this がどうなるか試し、bind を使って直す方法も試す。

模範解答

演習①:アロー関数(class field)を使う版

<!DOCTYPE html>
<html>
<body>
<script>
class Worker {
  count = 0;

  // ✅ アロー関数で定義(自動で this が固定される)
  start = () => {
    console.log("Worker start!");
    setInterval(() => {
      this.count++;
      console.log(`count: ${this.count}`);
    }, 1000);
  };
}

const w = new Worker();
w.start();  // ✅ 毎秒 count が 1, 2, 3... と増える
</script>
</body>
</html>
HTML

💡 解説

  • start を「アロー関数」で定義しているため、this は常に Worker のインスタンスを指します。
  • setInterval のコールバックもアロー関数にしてあるので、中の this も固定されます。
  • つまり「どこから呼ばれても this が正しく保たれる」状態。
    bind() を書く必要がなく、非常にシンプル。

演習②:プロトタイプメソッドで this が消える例(→ bindで修正)

<!DOCTYPE html>
<html>
<body>
<script>
class Worker {
  constructor() {
    this.count = 0;
  }

  // ❌ 通常のメソッド(prototype上)
  start() {
    console.log("Worker start!");
    setInterval(this.tick, 1000); // ← this.tick 内の this が失われる!
  }

  tick() {
    this.count++;
    console.log(`count: ${this.count}`);
  }
}

const w = new Worker();
w.start(); // ⚠️ TypeError: Cannot read properties of undefined (reading 'count')
</script>
</body>
</html>
HTML

❌ なぜエラー?

  • setInterval(this.tick, 1000) の時点で関数 tick を「ただの関数」として渡している。
  • 関数単体で呼ばれると、thisundefined(またはグローバル)になるため this.count が読めない。

修正版(bindを使って this を固定)

<!DOCTYPE html>
<html>
<body>
<script>
class Worker {
  constructor() {
    this.count = 0;
    this.tick = this.tick.bind(this); // ✅ ここで this を固定!
  }

  start() {
    console.log("Worker start!");
    setInterval(this.tick, 1000); // ✅ これでOK
  }

  tick() {
    this.count++;
    console.log(`count: ${this.count}`);
  }
}

const w = new Worker();
w.start(); // ✅ count: 1, 2, 3... と増える
</script>
</body>
</html>
HTML

💡 解説

  • bind() を使うことで this を強制的にインスタンスに固定。
  • この方法も正しく動くが、毎回コンストラクタで bind が必要で少し冗長。
  • クラスフィールド+アロー関数なら bind 不要でシンプルに書けます。

まとめ比較

方法書き方this の扱いメリットデメリット
プロトタイプメソッドstart() {}呼び出し方で変化メモリ効率良い外部渡し時に this が失われやすい
bindで修正this.method = this.method.bind(this)固定確実に動くコンストラクタで毎回bindが必要
アロー関数(class field)method = () => {}自動固定シンプル・安全インスタンスごとに関数が増える(メモリ)
タイトルとURLをコピーしました