[EffectiveJavaScript輪読会3]カスタムレシーバ付きでメソッドを呼び出すにはcallを使おう

GAS

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

前回のおさらい

前回は、「高階関数を快適に使えるようにしよう」をお届けしました。

[EffectiveJavaScript輪読会3]高階関数を快適に使えるようにしよう
どうも。つじけ(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を使おう」 をお届けします。

参考資料

このシリーズの目次

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