どうも。つじけ(tsujikenzo)です。このシリーズでは2021年8月からスタートしました「ノンプロ研EffectiveJavaScript輪読会」についてお送りします。
前回のおさらい
前回は、「高階関数を快適に使えるようにしよう」をお届けしました。
今回は、「カスタムレシーバ付きでメソッドを呼び出すにはcallを使おう」 をお届けします。
テキスト第3章「関数の扱い」の項目20に対応しています。
今日のアジェンダ
- レシーバのおさらい
- Callメソッド
- callメソッドの高階関数への利用
レシーバのおさらい
項目18にて、レシーバの説明をしました。
復習ですが、メソッドは関数を値にもつプロパティです。
のちほど変数に代入したり、プロパティをさらにプロパティに代入したりして、ただの関数として呼び出されることがあります。
そんなメソッドの中に、thisが書かれていたばあいは、メソッドが書かれていたオブジェクトをレシーバにするのではなく、メソッドをルックアップしたオブジェクトがthisに結合されます。
say()を呼び出したときに、レシーバになるのは、thisが書かれているオブジェクト[person]ではありません。
メソッドをルックアップしたオブジェクトがなにもない、say()関数はどこにも所属していないため、エラーを返します。
function myFunction3_20_01() {
"use strict";
const person = {
fullName: "Bob",
sayName: function() {
return this.fullName;
}
};
console.log(person.sayName()); //Bob
// person.sayNameをsay変数に代入する
const say = person.sayName;
console.log(say); //[Function: sayName]
say(); //TypeError: Cannot read property fullName of undefined
}
このことは、「say()を呼び出すときは、レシーバに注意しなければならない」ということを教えています。
JavaScriptでは、関数を呼びだしたオブジェクトではなく、明示的にレシーバを指定できます。
それを、カスタムレシーバと呼びます。
Callメソッド
Callメソッドは、カスタムレシーバを第1引数に渡すことができます。
戻り値は、thisの値(カスタムレシーバ)と引数を指定して、関数を呼び出した結果です。
function myFunction3_20_02() {
"use strict";
const say = message => {
return `${message} ${this.fullName}!`;
}
const person = {
fullName: "Bob"
};
//say関数をそのまま呼び出すとthisはundefinedとなる
say("こんにちは"); //TypeError: Cannot read property fullName of undefined
//Callメソッドは第1引数でthisを指定する
console.log(say.call(person, "こんにちは")); //こんにちは Bob
}
元も子もないですが、大切なのは、メソッドを変数に代入したり、プロパティに格納せずに、メソッドとして呼び出すことです。
function myFunction3_20_03() {
"use strict";
const person = {
fullName: "Bob",
say(message) {
return `${message} ${this.fullName}!`;
}
};
console.log(person.say("こんにちは")); //こんにちは Bob!
}
callメソッドが便利なのは、削除や更新や上書きされた可能性のあるメソッドを、カスタムレシーバで呼び出せる点です。
hasOwnProperty(メソッド)については、項目45で詳しく解説します。(感覚的にhasOwnとdictが逆のような意識があって不思議ですが。。。)
function myFunction3_20_04() {
const dict = {};
dict.foo = 1;
delete dict.hasOwnProperty;
const hasOwn = {}.hasOwnProperty; //Object.prototype.hasOwnPropertyの糖衣構文
console.log(hasOwn.call(dict, 'foo')); //true
console.log(hasOwn.call(dict, 'hasOwnProperty')); //false
}
callメソッドの高階関数への利用
ちょっと、テキストのコード例が理解できなかったので、きっとこういうことを言っているのだろうという、解釈をしました。
mapメソッドの第1引数には、関数を渡します。いわゆるコールバック関数というやつです。
そして、第2引数には、オブジェクトを渡せることをご存知でしょうか。
arr.map(function callback(value, index, array) {/*新しい配列の生成*/}, thisArg);
mapメソッドのコールバック関数内に書かれているthisのレシーバを、明示的にpersonに指定できます。
function myFunction3_20_05() {
const person = {
name: "Tom",
age: 34,
favorite: 'coffee'
};
const properties = ["name", "favorite"];
const mapped = properties.map(function (value) {
return this[value];
}, person);
console.log(mapped); //[ 'Tom', 'coffee' ]
}
アロー関数だとthisが効かないので要注意です。
function myFunction3_20_06() {
const person = {
name: "Tom",
age: 34,
favorite: 'coffee'
};
const properties = ["name", "favorite"];
const mappedArrow = properties.map(value => {
return this[value];
}, person);
console.log(mappedArrow); //[ undefined, undefined ]
}
これは、callメソッドの第1引数で、カスタムレシーバを指定できることと同じではないでしょうかね。
まとめ
以上で、「カスタムレシーバ付きでメソッドを呼び出すにはcallを使おう」をお届けしました。
いまのところ、実務におけるカスタムレシーバの使いどころが思い浮かびません。
ただ、mapやfilterメソッドの第2引数は、使いどころがあるかもしれませんね。
次回は、「いくつでも引数をとれる関数を呼び出すにはapplyを使おう」 をお届けします。
参考資料
このシリーズの目次
- [EffectiveJavaScript輪読会3]関数、メソッド、コンストラクタの、呼び出しの違いを理解する
- [EffectiveJavaScript輪読会3]高階関数を快適に使えるようにしよう
- [EffectiveJavaScript輪読会3]カスタムレシーバ付きでメソッドを呼び出すにはcallを使おう
- [EffectiveJavaScript輪読会3]いくつでも引数をとれる関数を呼び出すにはapplyを使おう
- [EffectiveJavaScript輪読会3]可変長引数関数を作るには、argumentsを使う
- [EffectiveJavaScript輪読会3]argumentsオブジェクトを書き換えない
- [EffectiveJavaScript輪読会3]argumentsへのリファレンスは変数に保存する