[EffectiveJavaScript輪読会4]固定レシーバを持つメソッドを抽出するにはbindを使う

GAS

どうも。つじけ(tsujikenzo)です。このシリーズでは2021年8月からスタートしました「ノンプロ研EffectiveJavaScript輪読会」についてお送りします。

前回のシリーズでは、第3章に相当する考察をお届けしました。

引き続き、第3章後半と、4章をお送りします。

目次と日程

第3章後半は「関数の扱い」です。地道な筋トレが続きます。

  • 第1章 JavaScriptに慣れ親しむ
  • 第2章 変数のスコープ
  • 第3章 関数の扱い
  • 第4章 オブジェクトとプロトタイプ
  • 第5章 配列とディクショナリ
  • 第6章 ライブラリとAPI設計
  • 第7章 並行処理

LT大会は2021年10月14日です。がんばりましょう。

今日はさっそく1回目で、「固定レシーバを持つメソッドを抽出するにはbindを使う」 をお届けします。

テキスト第3章「関数の扱い」の項目25に対応しています。

今日のアジェンダ

  • レシーバの復習
  • コールバック関数の第2引数
  • レシーバを固定する関数を自作する
  • bindメソッド
  • 実務で使う

レシーバの復習

これまで何度もお伝えしている通り、JavaScriptの関数はプロパティとして利用できます。

変数に格納し、コールバック関数の引数として利用もできます。

function myFunction4_25_00() {

  const obj = { getHello() { return 'Hello' } };

  const helloArray = [1, 2, 3].map(obj.getHello);

  console.log(helloArray); //[ 'Hello', 'Hello', 'Hello' ]
}

しかし、メソッドにthisが含まれているばあいは、thisは書かれていた場所でオブジェクト自身と結合しないこと(レシーバ)は勉強しました。

コールバック関数の第2引数を使うか、展開して書く方法があります。

function myFunction4_25_00_02() {

  const obj = {
    name: 'tsujike',
    getHello() { return `Hello, ${this.name}` }
  };

  //thisはobjに結合されている
  console.log(obj.getHello()); //Hello, tsujike

  const helloArray = [1, 2, 3].map(obj.getHello);

  //thisはobjに結合されていない
  console.log(helloArray); //[ 'Hello, undefined', 'Hello, undefined', 'Hello, undefined' ]

  //第2引数を使って、thisに明示的にオブジェクトを渡そう
  const helloArray2 = [1, 2, 3].map(obj.getHello, obj);
  console.log(helloArray2); //[ 'Hello, tsujike', 'Hello, tsujike', 'Hello, tsujike' ]

  //展開しても同様の結果を得る
  const helloArray3 = [1, 2, 3].map(number => obj.getHello(number));
  console.log(helloArray3); //[ 'Hello, tsujike', 'Hello, tsujike', 'Hello, tsujike' ]
}

テキストも写経してみましょう。

オブジェクトの中で配列を管理しています。まずは、配列に要素を追加したいばあいです。

function myFunction4_25_01() {

  const buffer = {};
  buffer.entries = [];
  buffer.add = function (element) { this.entries.push(element) };
  buffer.concat = function () { return this.entries.join('') };

  const title = ['2021年', 'BT大会'];

  //buffer.entriesを結合したい

  //thisが結合するグローバルオブジェクトにentriesプロパティがないのでエラーになる
  title.forEach(buffer.add);//TypeError: Cannot read property 'push' of undefined
}

コールバック関数の第2引数

forEachの第2引数(thisArg)を使って、レシーバを指定しましょう。

無事に、配列の要素を結合できました。

function myFunction4_25_02() {

  const buffer = {};
  buffer.entries = [];
  buffer.add = function (s) { this.entries.push(s) };
  buffer.concat = function () { return this.entries.join('') };

  const title = ['2021年', 'BT大会'];

  //関数のレシーバとして、bufferを渡せる第2引数。thisArg 省略可callback 内でthisとして使用する値です。
  title.forEach(buffer.add, buffer);

  console.log(buffer); //	{ entries: [ '2021年', 'BT大会' ],  add: [Function],  concat: [Function] }

  //buffer.entriesを結合したい
  const combinedElement = buffer.entries.join('');
  console.log(combinedElement);//2021年BT大会

}

レシーバを固定する関数を自作する

すべてのコールバック関数が、第2引数を提供しているわけではありません。(そのような例は知りませんが。。。)

なら、レシーバを指定できる(必ず希望するオブジェクトに結合してくれる)関数を自作してみましょう。

function myFunction4_25_03() {

  const buffer = {};
  buffer.entries = [];
  buffer.add = function (s) { this.entries.push(s) };
  buffer.concat = function () { return this.entries.join('') };

  const title = ['2021年', 'BT大会'];

  //必ずbuffer.addを呼び出すコールバック関数
  const func = element => buffer.add(element);
  title.forEach(func);

  const combinedElement = buffer.entries.join('');
  console.log(combinedElement); //	2021年BT大会

}

bindメソッド

ES5から、この自作ラッパーと同機能のbindメソッドが導入されました。

Function.prototype.bind() - JavaScript | MDN
bind() メソッドは新しい関数を生成し、これは呼び出された際に this キーワードに指定された値が設定されます。この値は新しい関数が呼び出されたとき、一連の引数の前に置かれます。

まぁ、スッキリ。素敵。

function myFunction4_25_04() {

  const buffer = {};
  buffer.entries = [];
  buffer.add = function (s) { this.entries.push(s) };
  buffer.concat = function () { return this.entries.join('') };

  const title = ['2021年', 'BT大会'];

  title.forEach(buffer.add.bind(buffer));

  const combinedElement = buffer.entries.join('');
  console.log(combinedElement); //	2021年BT大会

}

実務で使う

では、ES6ならどう書くかまとめです。

配列を結合したいなら、スプレッド構文でいいじゃないかと思うでしょう。

しかし、すでに書かれているメソッドを変更できないような場面にも、遭遇するでしょう。

臨機応変に対応しましょう。

function myFunction4_25_05() {

  const buffer = {};
  buffer.entries = [];
  buffer.add = function (s) { this.entries.push(s) };
  buffer.concat = function () { return this.entries.join('') };

  const title = ['2021年', 'BT大会'];

  //addを使いなさいって話か
  // buffer.entries = [...title];

  //map()メソッドの第2引数を使う
  title.map(buffer.add, buffer);
  console.log(buffer.entries); //	[ '2021年', 'BT大会' ]
  console.log(buffer.concat()); //2021年BT大会

  //展開する方が分かりやすいかも
  title.map(element => buffer.add(element));
  console.log(buffer.entries); //		[ '2021年', 'BT大会', '2021年', 'BT大会' ]
  console.log(buffer.concat()); //2021年BT大会2021年BT大会
}

まとめ

以上で、「固定レシーバを持つメソッドを抽出するにはbindを使う」をお届けしました。

bind()メソッド、使うときあるかなぁ。。。

次回は、「関数をカリー化するには、bindを使う」 をお届けします。

このシリーズの目次

タイトルとURLをコピーしました