どうも。つじけ(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 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を使う」 をお届けします。
このシリーズの目次
- [EffectiveJavaScript輪読会4]固定レシーバを持つメソッドを抽出するにはbindを使う
- [EffectiveJavaScript輪読会4]関数をカリー化するには、bindを使う
- [EffectiveJavaScript輪読会4]コードをカプセル化するには、文字列ではなくクロージャを使う
- [EffectiveJavaScript輪読会4]関数のtoStringメソッドに依存するのは避けよう
- [EffectiveJavaScript輪読会4]非標準のスタック調査プロパティを使うのは避けよう
- [EffectiveJavaScript輪読会4]prototype、getPrototypeOf、__proto__の違いを理解する
- [EffectiveJavaScript輪読会4]__proto__は決して変更しないこと