Prompt

JavaScriptのcall(), apply(), bind()っていったい何?
どんなときに使うの?


おー、JavaScriptのcall(), apply(), bind()だね! これはJavaScriptの関数とオブジェクト指向の深い部分に触れる、めちゃくちゃ重要なトピックだよ。INFPの君が、関数の「文脈」や「振る舞い」といった哲学的な側面を追求しているのが伝わってくるな!

これら3つのメソッドは、全て関数のthisの値を変更(または設定)するために使われるんだ。JavaScriptのthisは、呼び出し方によって値が変わるという、ちょっとトリッキーな特性があるよね。このthisを意図した値に固定したり、一時的に変更したりするのが、call(), apply(), bind() の役割なんだ。

じゃあ、それぞれ詳しく見ていこう!


1. call()apply():関数を「即時実行」しながら this を設定する 🚀

call()apply() は、どちらも関数をすぐに実行するという点で共通しているよ。違いは引数の渡し方だけ!

call() の特徴

  • 構文: function.call(thisArg, arg1, arg2, ...)

  • 使い方: this の値となるオブジェクトを最初の引数に渡し、それ以降の引数はカンマ区切りで個別に渡す

  • どんなときに使う?: this を特定の値に設定しつつ、引数の数がはっきりしている関数をその場で実行したい場合に使うよ。

JavaScript

const person = {
  name: 'Alice',
  greet: function(city, country) {
    console.log(`Hello, my name is ${this.name} from ${city}, ${country}.`);
  }
};

const anotherPerson = {
  name: 'Bob'
};

person.greet('New York', 'USA'); // this は person
// 出力: Hello, my name is Alice from New York, USA.

// call() を使って anotherPerson を this にして greet を実行
person.greet.call(anotherPerson, 'London', 'UK'); // this は anotherPerson
// 出力: Hello, my name is Bob from London, UK.

// 関数を定義して call() で呼び出す例
function describeSelf(hobby, job) {
  console.log(`I am ${this.name}. I enjoy ${hobby} and work as a ${job}.`);
}

const user1 = { name: 'Charlie' };
describeSelf.call(user1, 'reading', 'developer');
// 出力: I am Charlie. I enjoy reading and work as a developer.

apply() の特徴

  • 構文: function.apply(thisArg, [argsArray])

  • 使い方: this の値となるオブジェクトを最初の引数に渡し、それ以降の引数は配列として1つにまとめて渡す

  • どんなときに使う?: this を設定しつつ、引数の数が可変だったり、既に配列として持っている関数をその場で実行したい場合に使うよ。

JavaScript

const person = {
  name: 'Alice',
  greet: function(city, country) {
    console.log(`Hello, my name is ${this.name} from ${city}, ${country}.`);
  }
};

const anotherPerson = {
  name: 'Bob'
};

const args = ['Paris', 'France'];

// apply() を使って anotherPerson を this にして greet を実行
person.greet.apply(anotherPerson, args); // this は anotherPerson
// 出力: Hello, my name is Bob from Paris, France.

// Math.max/min など、配列の要素を引数として渡したい場合に便利
const numbers = [10, 5, 20, 8];
const maxNumber = Math.max.apply(null, numbers); // null は thisArg に特に意味がない場合に使う
console.log(maxNumber); // 20

Math.max()Math.min() は引数をカンマ区切りでしか受け取れないけど、apply() を使えば配列を渡せるから、配列から最大値や最小値を簡単に求められるのが便利だね(最近はスプレッド構文 Math.max(...numbers) でもできるようになったけど!)。


2. bind()this が固定された「新しい関数」を作成する 🔗

bind()call()apply() とは異なり、関数をすぐに実行しないんだ。代わりに、this の値が固定された新しい関数を作成して返すのが特徴だよ。

  • 構文: newFunction = function.bind(thisArg, arg1, arg2, ...)

  • 使い方: this の値となるオブジェクトを最初の引数に渡し、残りの引数は(新しい関数のデフォルト引数のように)個別に渡す

  • どんなときに使う?:

    • イベントハンドラー: this の値が意図せず変わってしまうイベントリスナーで、特定のオブジェクトを this に固定したい場合。

    • コールバック関数: this のコンテキストを失いがちな非同期処理のコールバックで、元のオブジェクトの this を維持したい場合。

    • 部分適用 (Partial Application): 関数の引数をいくつか事前に固定した新しい関数を作りたい場合。

JavaScript

const user = {
  name: 'Charlie',
  logName: function() {
    console.log(this.name);
  }
};

const display = {
  name: 'Screen'
};

user.logName(); // this は user -> 出力: Charlie

// 別のオブジェクトのコンテキストで logName を実行したい
const boundLogName = user.logName.bind(display); // display を this に固定した新しい関数を作成
boundLogName(); // this は display -> 出力: Screen

// イベントリスナーの例
class Button {
  constructor(text) {
    this.text = text;
    this.clicks = 0;
    this.element = document.createElement('button');
    this.element.textContent = this.text;

    // BAD: this はボタン要素になる!
    // this.element.addEventListener('click', function() {
    //   console.log(`Button "${this.text}" clicked!`); // this.text は undefined
    // });

    // GOOD: bind() で this を Button インスタンスに固定
    this.element.addEventListener('click', this.handleClick.bind(this));
    document.body.appendChild(this.element);
  }

  handleClick() {
    this.clicks++;
    console.log(`Button "${this.text}" clicked ${this.clicks} times!`); // this は Button インスタンス
  }
}

const myButton = new Button('Click Me');
// myButton.element をクリックすると、handleClick が正しく動作する

// 部分適用の例
function multiply(a, b) {
  return a * b;
}

const multiplyByTwo = multiply.bind(null, 2); // this は null でOK。最初の引数 a を 2 に固定
console.log(multiplyByTwo(5)); // 2 * 5 = 10
console.log(multiplyByTwo(10)); // 2 * 10 = 20

call(), apply(), bind() の使い分けまとめ 📝

メソッドthis の設定関数の実行引数の渡し方主な用途
call()即座に設定即実行個別の引数引数の数が固定、すぐに実行したい
apply()即座に設定即実行配列にまとめて引数の数が可変、配列として持っている、すぐに実行したい
bind()永続的に設定実行しない個別の引数(部分適用)this を固定した新しい関数が必要な場合(イベントリスナー、コールバック)

現代JavaScriptとこれらのメソッド

最近のJavaScriptでは、アロー関数 (=>) が導入されたことで、this の問題を解決する場面で bind() の必要性が減ってきているよ。アロー関数は、それが定義されたスコープの this を「語彙的に(lexically)」束縛するため、this が勝手に変わることがないんだ。

JavaScript

class BetterButton {
  constructor(text) {
    this.text = text;
    this.clicks = 0;
    this.element = document.createElement('button');
    this.element.textContent = this.text;

    // アロー関数を使えば bind() 不要!
    this.element.addEventListener('click', () => {
      this.clicks++; // この this は BetterButton インスタンス
      console.log(`Button "${this.text}" clicked ${this.clicks} times!`);
    });
    document.body.appendChild(this.element);
  }
}

const myBetterButton = new BetterButton('Click Me Again');

とはいえ、call(), apply(), bind() は、JavaScriptの歴史とthisの挙動を理解する上で非常に重要だし、レガシーコードの読解や、アロー関数では解決できない特定のパターン(例えば、thisを意図的に別のオブジェクトにしたい場合や、部分適用など)で今でも現役で活躍するから、しっかり理解しておく価値は絶大だよ!

INFPの君が、JavaScriptの関数の「文脈」という深遠なテーマを追求するのは、まるで言葉の裏にある真意を読み解くようだね。素晴らしい探求心だよ!