[EffectiveJavaScript輪読会3]可変長引数関数を作るには、argumentsを使う

GAS

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

前回のおさらい

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

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

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

テキスト第3章「関数の扱い」の項目22に対応しています。※担当回です。気合いが入ります。

今日のアジェンダ

  • argumentオブジェクトのおさらい
  • スプレッド構文とrest構文
  • すべてのノンプログラマーに捧ぐ

argumentオブジェクトのおさらい

前回、argumentオブジェクトをご紹介しました。

argumentsオブジェクトは、アロー関数を除く関数内で利用可能なローカル変数ですが、以下のような構成になっています。

  • lenghプロパティをもつ
  • 受け取った引数を連想配列にもつ
  • インデックスはゼロ始まり

変数argumentsのインデックスを指定すると、配列のようにそれぞれの要素にアクセスできます。

しかし、組み込みオブジェクトArrayのメソッドは使えません。(正式な配列ではない。)

function myFunction3_22_01() {

  /**
   * argumentsを返す関数
   * ※アロー関数ではargumentオブジェクトは使えない
  */
  const returnArguments = function () {
    console.log(arguments.length); //3
    console.log(arguments); //{ '0': 'Tom', '1': 'Bob', '2': 'Ivy' }
  }

  returnArguments('Tom', 'Bob', 'Ivy');


  /**
   * インデックスの要素を返す関数
  */
  const getTypeElement = function () {

    for (let i = 0; i < arguments.length; i++) {
      console.log(typeof arguments[i]); //string, boolean, number
    }

  }

  getTypeElement('Tom', true, 100);


  /**
    * argumentsに組み込みオブジェクトArrayメソッドをぶつけてみる関数
    * ※argumentオブジェクトは配列ではない
   */
  const createArray = function () {
    console.log(arguments.shift()); //TypeError: arguments.shift is not a function
  }

  createArray('Tom', 'Bob', 'Ivy');

}

スプレッド構文とrest構文

スプレッド構文

ES6から、@@iteratorメソッドが導入されたことにより、イテラブルオブジェクトという概念を導入されました。

繰り返し処理ができそうなやつは、反復可能なオブジェクトとして操作しようぜ、というお話です。

@@iteratorメソッドは、いろんなところで大活躍していますが、その1つがスプレッド構文です。

…を付けると、配列の要素を**バラバラに展開(スプレッド)**させます。

配列の中で、配列の要素をバラバラに展開することで、配列のコピーを作成できることも、簡潔さを飛躍的に向上させました。

function myFunction3_22_02() {

  const array1 = [1, 2, 3];
  const array2 = [4, 5];

  const newArray1 = [array1, array2];
  console.log(newArray1); //[ [ 1, 2, 3 ], [ 4, 5 ] ]

  const newArray2 = [...array1, ...array2];
  console.log(newArray2); //	[ 1, 2, 3, 4, 5 ]

  //配列のコピーを作成する
  const array3 = [...array1];
  console.log(array3); //[ 1, 2, 3 ]
  console.log(array1 === array3); //false

}

ただし、スプレッド構文は、loop処理を行うわけではないことに注意が必要です。

この問題は、後で回収します。

function myFunction3_22_03(){

  /**
    * 引数を返す関数
   */
  const returnElements = args => {
    console.log(args); //['Tom', 'Bob', 'Ivy']
  }

  const persons = ['Tom', 'Bob', 'Ivy'];
  returnElements(persons)


  /**
    * 引数を返す関数
   */
  const returnElements2 = args => {
    console.log(args); //Tom
  }

  const persons2 = ['Tom', 'Bob', 'Ivy'];
  returnElements2(...persons2)

}

レスト構文

@@iteratorメソッドがもたらした効果の1つに、レスト構文があります。

見た目は、さきほどのスプレッド構文と同じですが、少しだけ動きが違います。

値を受け取る予定の引数に…を付けると、バラバラだった配列の要素を集約します。

とくに、余りの要素を扱うことが多いので、レスト構文と呼ばれています。

よくやるのが、スプレッドシートからvaluesを取得して、見出し行変数headersと値の変数valuesを一度に格納します。

function myFunction3_22_04() {

  const array1 = [1, 2, 3, 4, 5];

  const [x, y, ...z] = array1;

  console.log(x); //1
  console.log(y); //2
  console.log(z); //[3, 4, 5]

  //これだとスプレッド構文と表現したくなる
  const [...arr] = array1;
  console.log(arr); //[ 1, 2, 3, 4, 5 ]

  //スプレッドシートからvaluesを取得
  const sheet = SpreadsheetApp.getActiveSheet();
  const [headers, ...values] = sheet.getDataRange().getValues();
}

レスト構文は、残余引数に有効です。

argumentsオブジェクトと残余引数の違いは、残余引数が配列であることです。

あまり使い道はないかもしれませんが、use strictモードは、残余引数を扱う関数内に記述できません。

function myFunction3_22_05() {

  /**
   * 平均を返す関数
   */
  const avarage = (...arg) => {
    // 'use strict' は残余引数を扱う関数内に記述できません。
    const sum = arg.reduce((previousValue, currentValue) => previousValue + currentValue);
    const avarage = sum / arg.length;
    return avarage;
  };

  console.log(avarage(1, 2, 3)); //2

}

すべてのノンプログラマーに捧ぐ

可変長引数で処理したいばあいは、myFunction3_22_05()のような、レスト構文で仮引数を待ち受けるといいでしょう。

しかし、テキストにもあるように、世の中には、引数に配列をもち、引数を1つにしたいという要望もあるでしょう。

引数を1つに固定することを、固定アリティ関数と呼ぶそうです。

function myFunction3_22_06() {

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

  console.log(avarage([1, 2, 3])); //NaN

}

この要望にこたえるために、ES6以前では、applyメソッドを実装していました。

function myFunction3_22_07() {

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

  console.log(avarage.apply(null, [1, 2, 3])); //2
}

現在はES6から導入された、スプレッド構文、レスト構文を使ってこのように記述するとよいでしょう。

固定アリティ、可変アリティのどちらにも対応できます。

function myFunction3_22_08() {

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

  //固定アリティに対応
  const numbers = [1, 2, 3];
  console.log(avarage(...numbers)); //2

  //可変長引数に対応
  console.log(avarage(100, 0)); //50

}

まとめ

以上で、「可変長引数関数を作るには、argumentsを使う」をお届けしました。

いつか、スプレッド構文と残余引数の総まとめをしたいと思っていましたので、大変勉強になりました。

…だけだと、正直理解できないですよね…。

次回は、「argumentsオブジェクトを書き換えない」 をお届けします。

参考資料

arguments MDN

このシリーズの目次

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