JavaScriptのbind()、call()、apply()メソッドの使い方と違いについて

2021年2月6日JavaScript

JavaScriptのbind()メソッドの基本的な使い方とcall()/apply()メソッドとの違いを具体例とともに紹介します。
また、bind()メソッドにはとても便利な使い方ができるので、実際によく使われるbind()メソッドとeventListenersの合わせ技を紹介します。
さらに、bind()メソッドの関数への部分的な適用もよく使われる手法なので、知っておきましょう。

bind()メソッドとは

bind()メソッドは、thisや引数を固定した新しい関数を作成します。
bind()メソッドの第一引数として指定したオブジェクトをthisの参照先を固定することができます。

function hello() {
  console.log("こんにちは、" + this.name);
}
const yamada = hello.bind({ name: "yamada" });

yamada("sasaki"); //こんにちは、yamada

bind()とthisの関係はこちらの記事も参考にしてみてください。


呼び出す関数で引数を指定したとしても、bindの第二引数で引数を固定している場合、bindが指定した引数が優先されます。
また、
このようにbindによる固定を、bindによるthisの束縛といいます。
また、第二引数に引数にしたい値をいれることで、引数を固定することができます。

//第二引数を指定して引数の値を固定する
function hello(name) {
  console.log("こんにちは、" + name);
}

const yamada = hello.bind(null, "yamada");

yamada("sasaki"); //こんにちは、yamada

bind(), call(), apply()の違い

bind()はthisキーワードや引数の参照先を変更します。
使用時点で実行はしません。
call(), apply()はthisキーワードや引数の参照先を変更する点はbind()と同じです。
異なる点は、使用時点で関数を実行することです。
次の例をみてみましょう。

function hello() {
  console.log("こんにちは、" + this.name);
}
const yamada = { name: "yamada" };

//bindの場合
const bindYamada = hello.bind(yamada);
bindYamada(); //こんにちは、yamada

//call()の場合
hello.call(yamada); //こんにちは、yamada

//apply()の場合
hello.apply(yamada); //こんにちは、yamada

このように、bind()だけthisを固定、呼び出しの二段階となります。

call()とapply()の違い

call()はbind()と同じく、第二引数以下は関数の引数を文字列で指定できます。
bind()とよく似ており、異なる点としては呼び出し時に実行される点だけです。
apply()は、第二引数には文字列はとれず、配列となります。
それぞれの違いは次の通りです。

function hello(name1, name2, name3) {
  console.log("こんにちは、" + name1 + " " + name2 + " " + name3);
}
const yamada = { name: "yamada" };

//bindの場合
const bindYamada = hello.bind(null, yamada.name, "sasaki", "saito");
bindYamada(yamada); //こんにちは、yamada sasaki saito

//callの場合
hello.call(yamada, yamada.name, "sasaki", "saito"); //こんにちは、yamada sasaki saito

//applyの場合
hello.apply(yamada, [yamada.name, "sasaki", "saito"]); //こんにちは、yamada sasaki saito

引数が配列ならapply()、文字列ならcall()を使うといった使い分けでOKです。

bind()メソッドの基本的な使い方

bind()メソッドはcall()メソッドと同じく手動でthisキーワードを設定できます。

call()メソッドとの違いは、bind()メソッドは即座に呼び出さない点です。

その代わりにthisキーワードが返される新しい関数を返します。

//bind()メソッドの基本的な使い方
const busJapan = {
bus: 'busJapan',
busCode: 'BJ',
bookings: [],
//book: function() {}でもいい
book(bunNum, name) {
console.log(`${name}様のバスのお座席は、${this.bus}便の${this.busCode}${bunNum}です。`);
this.bookings.push({ bus: `${this.busCode}${bunNum}, name`})
},
};

const busNippon = {
bus: 'busNippon',
busCode: 'BN',
bookings: [],
//ここにもbookメソッドをいれたい。
}
const busTokyo = {
bus: 'busTokyo',
busCode: 'BT',
bookings: [],
//ここにもbookメソッドをいれたい。
}
const book = busJapan.book;

/* ---------- call()メソッド ------------*/
// book.call(busNippon, 40, '小池');

/* ---------- apply()メソッド ------------*/
// const busData = [23, '染谷'];
// book.apply(busNippon, busData);

/* ---------- bind()メソッド ------------*/

const bookNPN = book.bind(busNippon);//ここでthisを設定するが即座に呼び出すわけではない
const bookBTK = book.bind(busTokyo);

bookNPN(65, '田中');
//田中様のバスのお座席は、busNippon便のBN65です。
bookBTK(78, '岡田');
//岡田様のバスのお座席は、busTokyo便のBT78です。

//bindはthisキーワードと引数を指定ということもできる。
//bunNum, nameが必要だが、ここではbunNum=45のみを設定ということも可能
const bookBTK45 = book.bind(busTokyo, 45);
bookBTK45('新藤');
//新藤様のバスのお座席は、busTokyo便のBT45です。

bind()メソッドは、thisキーワードと他の引数の設定を分けて管理できるため、関数を複数回呼び出すときに、より簡単になります。
また、bindはthisキーワードと引数を指定ということもできます。

上のケースでは、bunNum, nameが必要ですが、bunNum=45のみを設定しておき、後でnameだけを渡して実行することができます。
もちろんnameも合わせて設定し、後で呼び出すだけということも可能です。

bind()メソッドとeventListeners

HTML
    <button class="buy">新しいバスを買う </button>
JavaScript
// bind()メソッドとeventListeners
const busJapan2 = {
bus: 'busJapan',
busCode: 'BJ',
bookings: [],
//book: function() {}でもいい
book(bunNum, name) {
console.log(`${name}様のバスのお座席は、${this.bus}便の${this.busCode}${bunNum}です。`);
this.bookings.push({ bus: `${this.busCode}${bunNum}, name`})
},
};

const busNippon2 = {
bus: 'busNippon',
busCode: 'BN',
bookings: [],
//ここにもbookメソッドをいれたい。
}

busJapan2.buses = 200;
busJapan2.buyBus = function() {
console.log(this);
//1 bind()メソッドを使わない例:<button class="buy"></button>が選択される
//2 bind()メソッドを使った例:{bus: "busJapan", busCode: "BJ", bookings: Array(0), buses: 200, book: ƒ, …}
this.buses++;
console.log(this.buses);
//1 bind()メソッドを使わない例:NaN
//2 bind()メソッドを使った例:201
}
//1 bind()メソッドを使わない例
document.querySelector('.buy').addEventListener('click', busJapan2.buyBus);
//NaNが返されてしまう。

//2 bind()メソッドを使った例
document.querySelector('.buy').addEventListener('click', busJapan2.buyBus.bind(busJapan2));

thisキーワードはハンドラーであるaddEventListenerがくっついている元を指します。

bind()メソッドを使わない例では、NaNが返されてしまいます。それは、document.querySelector('.buy’)を意味します。そのため、button要素が返されています。

bind()メソッドを使った例では正しく動作しています。このように、bind()メソッドを使うことでthisキーワードを手動で調整することが可能となります。

bind()メソッドの関数への部分的な適用

bind()メソッドの関数への部分的な適用(partial function application)を紹介します。

//Partial application

const addTax = (rate, value) =&gt; value + value * rate;
console.log(addTax(0.1, 300));//330

//bind()メソッドを使うことでrateをプレセットすることができる
const addVAT = addTax.bind(null, 0.25);
//上の意味:addVAT = value =&gt; value + value * 0.23;
console.log(addVAT(100));//125

//上のアロー関数の書き換え(bind()メソッドを使わない場合)
const addTax2 = function (rate) {
return function (value) {
return value + value * rate;
};
};
const addVAT2 = addTax2(0.25);
console.log(addVAT2(100));//125

上の例では、bind()メソッドを使うことでrateをプリセットしています。

thisキーワードの部分は、今回ケースではなんの意味もなしませんが、なにかいれる必要があるので、一般的にはnullが使われます。

上のaddTax関数は一般的な用途で使いますが、bind()メソッドを使ったaddVAT関数はより特定の場合に使える新しい関数を作り出していることになります。

call()メソッドとapply()メソッドの使い方

const busJapan = {
  bus: 'busJapan',
  busCode: 'BJ',
  bookings: [],
  //book: function() {}でもいい
  book(bunNum, name) {
    console.log(`${name}様のバスのお座席は、${this.bus}便の${this.busCode}${bunNum}です。`);
    this.bookings.push({ bus: `${this.busCode}${bunNum}, name`})
  },
};

const busNippon = {
  bus: 'busNippon',
  busCode: 'BN',
  bookings: [],
  //ここにもbookメソッドをいれたい。
}
const book = busJapan.book;



//call()メソッド
book.call(busNippon, 40, '小池');
// 小池様のバスのお座席は、busNippon便のBN40です。


//Apply()メソッド
const busData = [23, '染谷'];
book.apply(busNippon, busData);
//染谷様のバスのお座席は、busNippon便のBN23です。

//call()メソッド+スプレッド構文で全く同じことができる。
book.call(busNippon, ...busData);
//染谷様のバスのお座席は、busNippon便のBN23です。

call()メソッドの第一引数は、コールバック関数として機能し、thisキーワードにどれのことかを告げます。第二、第三引数は、引数として処理されます。

apply()メソッドはcall()メソッドとだいたい同じです。違いは、call()メソッドのように第二引数以降に配列が必要な点です。
call()メソッドは、第二引数以降にメソッドに渡す引数を設定できるのですが、スプレッド構文を使うことで配列もいれることができます。そしてその結果はapply()メソッドと全く同じになるため、apply()メソッドは近年使われなくなりつつあります。