[EffectiveJavaScript輪読会3]いくつでも引数をとれる関数を呼び出すにはapplyを使おう]

GAS

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

前回のおさらい

前回は、「カスタムレシーバ付きでメソッドを呼び出すにはcallを使おう」をお届けしました。

[EffectiveJavaScript輪読会3]カスタムレシーバ付きでメソッドを呼び出すにはcallを使おう
どうも。つじけ(tsujikenzo)です。このシリーズでは2021年8月からスタートしました「ノンプロ研EffectiveJavaScript輪読会」についてお送りします。前回のおさらい前回は、「高階関数を快適に使えるよ...

今回は、「いくつでも引数をとれる関数を呼び出すにはapplyを使おう」 をお届けします。

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

今日のアジェンダ

  • argumentオブジェクトと可変長引数
  • applyメソッド
  • applyメソッドとカスタムレシーバ

argumentオブジェクトと可変長引数

引数を受け取って、平均を返す関数は、このようにargumentオブジェクトを使って表現できました。

argumentsは配列風のオブジェクトです。

そして、このように、引数の個数がわからなくても対応できる書き方を、可変長引数、または可変アリティ関数と呼びます。

※アロー関数が使えないので注意が必要です。

function myFunction3_21_01() {
  'use strict'

  /**
   * 平均を返す関数
   */
  const avarage = function () { //アロー関数は使えません

   console.log(arguments); //{ '0': 10, '1': 0 }

    let sum = 0;
    for (let i = 0; i < arguments.length; i++) {
      sum += arguments[i];
    }

    return sum / arguments.length;

  };

  console.log(avarage(10, 0)); //5
}

便利そうに見えますが、世の中には「引数の数は必ず1つにしたほうがわかりやすいよ」という、固定アリティ関数があります。

たとえば引数を配列にしてしまう方法です。

これだと、引数の数は固定になりましたが、先ほどの関数では対応できません。

function myFunction3_21_02() {
  'use strict'

  /**
   * 平均を返す関数
   */
  const avarage = function () { //アロー関数は使えません

    let sum = 0;
    for (let i = 0; i < arguments.length; i++) {
      sum += arguments[i];
    }

    return sum / arguments.length;

  };

 console.log(avarage([10, 0])); //NaN

}

applyメソッド

これを解決するために、引数で渡すさいに、配列の要素を1つ1つバラして渡そうというメソッドがapplyメソッドです。

第1引数に、カスタムレシーバを指定できますが、今回はレシーバを使わないのでnullでかまいません。

function myFunction3_21_03() {
  'use strict'

  /**
   * 平均を返す関数
   */
  const avarage = function () { //アロー関数は使えません

    let sum = 0;
    for (let i = 0; i < arguments.length; i++) {
      sum += arguments[i];
    }

    return sum / arguments.length;

  };

  console.log(avarage.apply(this, [10, 0])); //5
  console.log(avarage.apply(null, [10, 0])); //5

}

これは、配列を引数として受け取り、配列を展開する処理とは、ちょっと意味が違うことだけ意識してください。

もちろん、このような処理でも正しく動きます。

function myFunction3_21_04() {
  'use strict'

  /**
   * 平均を返す関数
   */
  const avarage = array => {
    const infinityArray = array.flat(Infinity);
    const sum = infinityArray.reduce((previousValue, currentValue) => previousValue + currentValue);
    const avarage = sum / infinityArray.length;
    return avarage;
  };

  console.log(avarage([10, 0])); //5
  console.log(avarage([[10, 0]])); //5
}

applyメソッドをどうしても使いたいなら、このように書くかもしれませんね。

※スプレッド構文は次回お届けします。

function myFunction3_21_05() {
  'use strict'

  /**
   * 平均を返す関数
   */
  const avarage = function () { //アロー関数は使えません

    const array = Array.from(arguments);
    const sum = array.reduce((previousValue, currentValue) => previousValue + currentValue);
    const avarage = sum / arguments.length;
    return avarage;

  };

  console.log(avarage.apply(this, [10, 0])); //5
  console.log(avarage.apply(null, [10, 0])); //5

}

applyメソッドとカスタムレシーバ

データを一時的に保管しておく領域をバッファと読んだりしますが、このようなコードを書いてみました。

appendメソッドは可変長引数になっています。

function myFunction3_21_06() {
  'use strict'

  const buffer = {

    state: [],

    append() {
      for (let i = 0; i < arguments.length; i++) {
        this.state.push(arguments[i]);
      }
    }

  };

  buffer.append('Tsuji', ' ', 'Kenzo', '!!!');
  console.log(buffer.state); //	['Tsuji', ' ', 'Kenzo', '!!!' ]

}

しかし、配列を引数として渡すと、配列の中に配列が入ってしまい、うまく動作しないようです。

function myFunction3_21_07() {
  'use strict'

  const buffer = {

    state: [],

    append() {
      for (let i = 0; i < arguments.length; i++) {
        this.state.push(arguments[i]);
      }
    }

  };

  /**
   * 文字列を含む配列を返す関数
   */
  const getInputStrings = () => {
    return ['Sep-2021', '20 Mon'];
  };

  buffer.append('Tsuji', ' ', 'Kenzo', '!!!');

  //配列がそのままappendされてしまう
  buffer.append(getInputStrings());
  console.log(buffer.state); //[ 'Tsuji', ' ', 'Kenzo', '!!!', [ 'Sep-2021', '20 Mon' ] ]

}

このようなときに、配列の要素をバラで引数に渡す(厳密には違いますが)applyメソッドが有効です。

しかし、applyメソッドの第1引数で、正しいレシーバを指定しないと、ぜんぜん関係ないオブジェクトのstateプロパティを更新しかねません。

nullを指定した場合も動きません。

function myFunction3_21_08() {
  'use strict'

  const buffer = {

    state: [],

    append() {
      for (let i = 0; i < arguments.length; i++) {
        this.state.push(arguments[i]);
      }
    }

  };

  /**
   * 文字列を含む配列を返す関数
   */
  const getInputStrings = () => {
    return ['Sep-2021', '20 Mon'];
  };

  const birthDay = {
    state: ['つじけさんの誕生日は']
  }

  buffer.append('Tsuji', ' ', 'Kenzo', '!!!');

  buffer.append.apply(birthDay, getInputStrings());
  console.log(birthDay.state); //	[ 'つじけさんの誕生日は', 'Sep-2021', '20 Mon' ]

  buffer.append.apply(buffer, getInputStrings());
  console.log(buffer.state); //[ 'Tsuji', ' ', 'Kenzo', '!!!', 'Sep-2021', '20 Mon' ]

  // buffer.append.apply(null, getInputStrings()); //TypeError: Cannot read property 'state' of null
}

まとめ

以上で、「いくつでも引数をとれる関数を呼び出すにはapplyを使おう」をお届けしました。

メソッドを記述して、呼び出す側でオブジェクトを指定する。。。なんだか、ポリモーフィズムの匂いがします。

気のせいですかね。

今回は、ずっとスプレッド構文を我慢していました。

次回は、「可変長引数関数を作るには、argumentsを使う」 をお届けします。

このシリーズの目次

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