JavaScript

JavaScriptのcall()・apply()・bind()の使い方と違い|thisを固定する方法

JavaScriptのcall()・apply()・bind()の違いは、call()とapply()がthisを指定してその場で実行し(引数は個別か配列か)、bind()がthisと引数を固定した新しい関数を返す点です。イベントリスナーでのthisのずれ、部分適用の例も紹介します。

この記事の目次
  1. bind()メソッドとは
  2. bind(), call(), apply()の違い
  3. call()とapply()の違い
  4. bind()メソッドの基本的な使い方
  5. bind()メソッドとeventListeners
  6. HTML
  7. JavaScript
  8. bind()メソッドの関数への部分的な適用
  9. call()メソッドとapply()メソッドの使い方

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

先に3つの違いをまとめます。call()とapply()は「thisを指定して、その場で関数を実行する」メソッドで、違いは引数の渡し方だけです。call()は引数を1つずつ並べ、apply()は配列で渡します。bind()は実行せず、「thisと引数を固定した新しい関数」を返します。あとで呼ぶイベントハンドラーやコールバックにはbind()、今すぐ呼ぶならcall()かapply()、と覚えると使い分けに迷いません。

function greet(greeting, punctuation) {
  console.log(greeting + "、" + this.name + punctuation);
}
const user = { name: "yamada" };

greet.call(user, "こんにちは", "!");        // その場で実行。引数は1つずつ
greet.apply(user, ["こんにちは", "!"]);     // その場で実行。引数は配列
const greetUser = greet.bind(user, "こんにちは"); // 実行せず、新しい関数を返す
greetUser("!");                              // あとで呼ぶ

なお、アロー関数はthisを自分で持たないため、call()・apply()・bind()でthisを指定しても無視されます。thisを差し替えたい関数はfunctionで定義したものに限られます。

bind()メソッドとは

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

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

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

bind()とthisの関係はこちらの記事も参考にしてみてください。
JavaScriptのthisキーワードについて
このようにbindでthisを固定することを、bindによるthisの束縛といいます。
また、第二引数以降に値を入れると、関数の引数も固定できます。
呼び出すときに別の引数を渡しても、bindで固定した引数が先に埋まり、呼び出し時の引数はその後ろに続きます。

//第二引数を指定して引数の値を固定する
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()と同じく、第二引数以降に関数の引数を1つずつ並べて指定します。
bind()とよく似ており、異なる点は使用した時点で実行される点だけです。
apply()は、第二引数に引数をまとめた配列を1つ渡します。
それぞれの違いは次の通りです。

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()、という使い分けで十分です。

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を登録した要素、つまりdocument.querySelector(‘.buy’)で取得したbutton要素を指します。

bind()メソッドを使わない例では、this.busesがbutton要素のbusesプロパティ(存在しないのでundefined)になり、undefined++の結果としてNaNが返されます。

bind()メソッドを使った例では正しく動作しています。このように、bind()メソッドを使うとthisキーワードを手動で調整できます。
なお、クラスのメソッドをイベントハンドラーに渡すときも同じ問題が起きます。コンストラクタでthis.handleClick = this.handleClick.bind(this)としておくのが定番で、クラスの書き方はJavaScriptのES6のクラス(class)キーワードの使い方を参考にしてください。

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

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

//Partial application

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

//bind()メソッドを使うことでrateをプレセットすることができる
const addVAT = addTax.bind(null, 0.25);
//上の意味:addVAT = value => value + value * 0.25;
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関数はより特定の場合に使える新しい関数を作り出していることになります。
「関数を受け取って新しい関数を返す」というこの考え方は高階関数と呼ばれ、JavaScriptの第一級関数と高階関数で基礎から説明しています。

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()メソッドとほぼ同じです。違いは、第二引数に引数をまとめた配列を1つ渡す点です。
call()メソッドでも、スプレッド構文...busDataを使えば配列の中身を引数として展開できます。結果はapply()メソッドと全く同じになるため、配列を渡すためだけにapply()を選ぶ場面は減っています。
ただしMath.max.apply(null, numbers)のような古いコードを読む機会はまだ多いので、「apply()の第二引数は配列」と覚えておくと読み解けます。

スポンサーリンク