銀の弾丸

プログラミングに関して、いろいろ書き残していければと思っております。

JavaScriptで可変長引数を扱うモダンな書き方

f:id:takamints:20190125200451j:plain
photo credit: wuestenigel Domino effect via photopin (license)

みんなのコンピュータサイエンス
翔泳社 (2019-01-15)
売り上げランキング: 12,305

JavaScript 可変長引数」でググると「arguments」のことばっかり出てきますけど「今やJavaScriptの可変長引数は arguments を使わなくてもスッキリかけますからねっ!」ってことを書いてる記事です。

JavaScriptには、歴史的に妙な仕様が残されていて、かつ互換性を保っていますよね。 それはそれで大変ありがたいことなのですが、一方新たな構文や概念もガンガン導入されており、古くからJavaScriptを使っている人が最近のコードを見ると「なんじゃ?この書き方は?」となったりします。

とはいえ少なくとも便利な方向へ進んでいるのは確かですから、できれば新しい記法や概念を覚えていきたいものですな。

ということで、ここではES2015(ES6)以降のJavaScriptで便利になった、そんな機能のひとつ「可変長引数」について書いておきます。

そう、知ってる人は知っている、かの無理矢理感満載の関数内の arguments オブジェクトですけど、今や別の方法でスッキリ書けるんですよ。

目次

可変長引数を扱う古い方法:arguments を使用する

まずは古い方法から。arguments はよく知ってるよって人は、このセクションは読み飛ばしてもらって結構ですよ。

JavaScriptの関数内では arguments というオブジェクトが、実行時に指定されたパラメータをすべて保持しています。

arguments は配列(Array)のように扱えるArgumentクラスのオブジェクトです。

引数以外にも呼び出し元や呼び出されている関数自体なども保持しています(ただし”use strict”; なstrictモードでは使えないものがあります。)

argumentsの使い方

引数に関しての使い方は、ほぼ配列です。

以下の関数fooは、受け取った引数をコンソールへ出力するだけの関数です。

function foo() {
    console.log("引数の数=" + arguments.length);
    for(let i = 0; i < arguments.length; i++) {
        console.log("[" + i + "]=" + arguments[i]);
    }
}
foo("A");
    // 引数の数=1
    // [0]=A
foo(1,2,3);
    // 引数の数=3
    // [0]=1
    // [1]=2
    // [2]=3

argumentsは明示的な引数宣言と無関係

関数に明示的な引数リストが宣言されていても、 arguments はそれら全てを含んでいます。

function foo(param0) {
    console.log("引数の数=" + arguments.length);
    for(let i = 0; i < arguments.length; i++) {
        console.log("[" + i + "]=" + arguments[i]);
    }
    console.log("param0=" + param0);
}
foo("A");
    // 引数の数=1
    // [0]=A
    // param0=A
foo(1,2,3);
    // 引数の数=3
    // [0]=1
    // [1]=2
    // [2]=3
    // param0=1

argumentsは配列ほど便利ではない

argumentsは配列っぽく使えますが、Arrayクラスの多くのメソッドが使えません。

これでは不便なので、以下のように一旦配列に変換するのが定石的なコードです。

function foo() {
    const vargs = Array.from(arguments);
    console.log("引数の数=" + vargs.length);
    vargs.forEach( function(item, index) {
        console.log("[" + index + "]=" + item);
    });
}

明示的な引数宣言がある場合は、途中からだけ変換したり。

function foo(bar,baz) {
    const vargs = Array.from(arguments).slice(2);
    console.log("引数の数=" + vargs.length);
    vargs.forEach( function(item, index) {
        console.log("[" + index + "]=" + item);
    });
}

arguments はアロー関数で使えない

アロー関数の中では arguments を使用できません。

つまり、アロー関数で可変長引数を受け取るには新しい方法でなければなりません。

argumentsの問題点まとめ

ここまで長々と書いてきましたが、argumentsの問題点は、アロー関数で使えないという致命的な点に加えて、可読性や保守性を損なっている点です。

上に書いたように歴史的に定石コードが使われてきましたが、それでも「単純だが見つけにくいバグ」が入る隙間ができています(これが一番の困り者)。

こういったコードの隙間はできる限りふさいでおくほうが安全です。

そういう意味でも、新しい方法が推奨されます。

可変長引数を扱う新しい方法:レストパラメータの使用

最新のJavaScript(ES6以降)では可変長引数をレストパラメータ(rest parameters)で宣言します。

レストパラメータとは、関数の最後の引数が ...args という形式(ドット3つに変数名)になっているものです (レスト = rest =「残り」ですので、「残りのパラメータ」というような意味でしょう)。

const foo = (param0, ...args) => {
    console.log("可変長引数の数=" + args.length);
    args.forEach( function(item, index) {
        console.log("[" + index + "]=" + item);
    });
    console.log("param0=" + param0);
};
foo("A");
    // 可変長引数の数=0
    // param0=A
foo(1,2,3);
    // 可変長引数の数=2
    // [0]=2
    // [1]=3
    // param0=1

ここでargsは単なるパラメータの名前ですから別の名前(例えば ...list )でも構いません。

レストパラメータは配列(Arrayオブジェクト)であり、この位置以降に指定された引数の値をすべて保持しています。

名前付きの可変長引数:スプレッド構文と組み合わせて

名前付きの可変長引数を使用できます。レストパラメータは配列ですが「スプレッド構文」との合わせ技で可能になります。

以下の例の第二引数が名前付きの可変長引数(...[va0,va1,va2]の部分)です。つまりこれ全体がレストパラメータで、[va0,va1,va2] の部分がスプレッド構文なんですね。

const foo = (param0, ...[va0,va1,va2]) => {
    va0=va0||0;
    va1=va1||0;
    va2=va2||0;
    const result = va0+va1+va2;
    console.log(`${param0}=${va0}+${va1}+${va2}=${result}`);
    return result;
};
foo("A", 1,2,3);
    // A=1+2+3=6
foo("A", 1,2);
    // A=1+2+0=3

指定しなかったところはundefinedになるようですね。

スプレッド構文というのは配列やオブジェクトを個々の要素に分解する構文なんですが、上の部分を見ると何となく理解できますでしょうか。

スプレッド構文の詳細は以下を参照してください。

ちょっとややこしいですけど、名前付きの可変長引数の最後の変数をレストパラメータにできます。

以下の例では、va2がそのように指定されています。この場合、fooに引数を与えなくても、va2は必ず配列になります。

const foo = (param0, ...[va0,va1,...va2]) => {

まとめ

  • argumentsは配列みたいなオブジェクトですが配列ほどには便利ではありません。レストパラメータは配列ですからそれなりに便利。
  • arguments は第一引数からすべてを保持しています、レストパラメータは宣言位置以降の値を保持します。
  • arguments はアロー関数では使用できませんがレストパラメータは使用可能(もちろん通常の function でもOK)。
  • argumentsはローカル変数ですが、ローカル関数の内部からはアクセスできません(ローカル関数自身のargumentsに置き換わります)。
  • レストパラメータはスプレッド構文と組み合わせることで可変長引数に名前を付けられます。

古いからといって間違いではありませんから、既存の動作しているコードを変更する必要などはありません (もしも変更するならば、ユニットテストを用意して取り掛かりましょう)。

しかし今から書くコードは(arguments に限らず)できるかぎり最新の手法を使うべきですね。