目次


多忙な JavaScript 開発者のための ECMAScript 6 ガイド、第 2 回

関数に関する機能強化

JavaScript 内でアロー関数やジェネレーター関数などの関数型要素を使用する

Comments

コンテンツシリーズ

このコンテンツは全4シリーズのパート#です: 多忙な JavaScript 開発者のための ECMAScript 6 ガイド、第 2 回

このシリーズの続きに乞うご期待。

このコンテンツはシリーズの一部分です:多忙な JavaScript 開発者のための ECMAScript 6 ガイド、第 2 回

このシリーズの続きに乞うご期待。

Interactive code: When you see Run at the top of a code sample, you can run the code, check the results, make changes, and run it again.

編集者による注記: このシリーズは、対話式コーディング機能を追加して更新されています。コード・リストに「実行」ボタンがある場合、そのコードを実行して結果を確認し、コードを変更してから再度実行して、その成果を確認できます。

シリーズ「多忙な JavaScript 開発者のための ECMAScript 6 ガイド」の第 2 回へようこそ。第 1 回では、JavaScript での長年にわたる問題に対処するため、あるいは特定の機能をより簡単にコーディングできるようにするための、言語に関する 6 つの更新について説明しました。そのうちの 1 つは、変数宣言内で使用して、配列やオブジェクトをその構成部分に分割する、新しい分割代入操作です。まだ第 1 回を読んでいない場合は、第 1 回を読んでからこの記事を読んでください。今回の記事は、第 1 回の説明内容を理解していることを前提にしています。

第 2 回では、JavaScript 内で関数を操作する方法を変えることになる、言語に関する更新に話題を絞ります。特に、関数定義と呼び出し構文に目を向けて、今回は関数定義における分割代入について詳しく説明します。また、新しく導入されたアロー関数の構文とジェネレーター関数についても説明します。後者は、従来のイテレーターや昔ながらの for ループに対する興味深い波及物です。

関数宣言内での分割代入

JavaScript の新しい分割代入は、配列やオブジェクトを「分割」して、配列またはオブジェクトの構成部分に「代入」できるという考えにちなんで、その名前が付けられています。第 1 回ではローカル変数内で構成の分割をどのようにして使用するのかを説明しましたが、この機能は関数パラメーター宣言内でも利用できます。

関数がオブジェクトを期待している場合、関数を開始する前に、そのオブジェクトを分割して関連する部分を抽出することができます。それには、このリストに示されているように、分割代入の構文を関数の引数に追加します。

Show result

displayDetails 関数で興味の対象となっているのは、渡されたオブジェクトの firstName フィールドと age フィールドだけです。分割代入をオブジェクトの構文に追加すると、関数呼び出しの一貫としてこれらのフィールドの値を効率的に抽出できます。呼び出しの構文自体は変わらないため、レガシー・コードをリファクタリングして簡単に新しい構文を適用することができます。

関数内で配列の構文を分割代入することもできますが、この機能は、これから説明する他の機能に比べ、それほど目を見張るものではありません。

関数パラメーター

ECMAScript では、関数パラメーターの構文にいくつかの変更が加えられています。具体的には、デフォルト・パラメーター値、rest パラメーター、および関数呼び出しの展開演算子が導入されています。前にも言ったように、これらの新仕様のほとんどは、ECMAScript 開発者たちが長年従ってきたコーディグの慣例に対するシンタックス・シュガーです。万全の言語機能が揃った今、これまでと同じ成果をより少ないコード行を使用して達成できるようになっています。

まずは、3 つの関数パラメーターの変更のうちでおそらく最も理解しやすいデフォルト・パラメーターについて説明します。

デフォルト・パラメーター

ECMAScript 6 では分割代入の構文を使用できるだけでなく、いくつかの C 系列の言語にあるようなデフォルト・パラメーター値の構文も使用できるようになっています。デフォルト・パラメーターをこれまで一度も見たことがないとしても、その構文はかなり明白なので直感的に理解できます。

Show result

基本的なことを説明すると、パラメーターが呼び出しサイトで指定されている場合、そのパラメーターは渡された値を取ります。値が指定されていなければ、デフォルト値が割り当てられます。

第 1 回で紹介した更新のいくつかと同じように、新しいデフォルト・パラメーターも本質的にはシンタックス・シュガーです。以前のバージョンの ECMAScript では、デフォルト・パラメーターを以下のようにコーディングすることになります。

Show result

新しい構文のほうが断じて短く、しかも表現力があり、多くのプログラマーが長年行ってきた慣習が標準化されています。

rest パラメーター

ECMAScript ライブラリー内でよく使われているイディオムの 1 つは、1 つ以上の必須パラメーターに続き、ユーザー定義の方法によって呼び出しを詳細化または変更するオプション・パラメーターの集合を取る関数またはメソッドを定義するというものです。これまで、このような関数やメソッドを定義するには、サイレントに構成されて、あらゆる関数呼び出しに渡される組み込み arguments パラメーターが使用されていました。

Show result

新しい rest パラメーターの構文を使用すると、オプションの引数をローカル配列変数に取り込むことができます。これらのオプションの引数は、上記と同じように、しかも変更することなく使用できます。

Show result

rest パラメーター (1 行目にある args) の有無をテストする必要はありません。追加のパラメーターが渡されないとしても、この言語では rest パラメーターが 0 長の配列として存在することを保証します。

展開演算子

展開演算子は、ある意味で、rest パラメーターとは逆の概念です。rest パラメーターは特定の呼び出しで渡される数々のオプションの値を収集する一方、展開演算子は値の配列を取り、呼び出し対象の関数に渡す個々のパラメーターとして使用できるように基本的には分割代入して「展開」します。

展開の最も単純な使用例は、個々の要素を連結して 1 つの配列にするというものです。

Show result

展開演算子の構文を使用しない場合、最初の配列から個々の要素を抽出して 2 番目の配列に追加してから、残りの要素を追加するという処理が必要になります。

展開演算子は関数呼び出し内でも使用できます。実のところ、展開演算子を使用する可能性が最も高いのは、関数呼び出しです。

Show result

rest パラメーターとは異なり、展開演算子は関数定義の中で使用されるのではなく、呼び出しの時点で使用されます。

関数の構文とセマンティクス

パラメーターに対する変更に加え、ECMAScript 6 では関数の構文とセマンティクスに関しても大幅な変更が加えられています。このセクションでは、とりわけ重要な更新のいくつかについて説明します。元の構文も引き続き JavaScript プログラム内で使用できることを覚えておいてください。この新しい構文が初めは使いにくかったり、直感的ではないと感じたりする場合は、元の構文と併用することで新しい構文を取り入れやすくなります。

アロー関数

Scala や F# などの比較的新しい言語が主流として受け入れられるようになっている中、主流から外れた古い言語では、一層磨きのかかった機能を採り入れ出しました。そのような機能の一例が、アロー関数の構文です。アロー関数は、関数リテラルを作成するための省略記号として使用されます。ECMAScript 6 からは、いわゆるファット・アロー (スキニー・アローの反対のバージョン) を使用して、関数リテラルを作成できます。以下に一例を示します。

Show result

C#、Java 8、Scala、または F# などの関数型プログラミングの経験があるとしたら、この構文はお馴染みのはずです。馴染みがないとしても、アローを理解するのはごく簡単なことで、アローの前の括弧に関数本体に渡すパラメーターを取り込み、アロー自体で関数本体を開始するという仕組みです。関数本体が単一の文または式からなる場合、波括弧を使用する必要はありません。本体に複数の文または式が含まれる場合は、アローの直後に括弧を入れて、本体が複数の文または式で構成されていることを示します。

Show result

パラメーターが 1 つしかない場合は、括弧を完全に省略するのでも構いません。その場合の構文は以下のとおりです。
names.forEach(n => console.log(n));
この構文でも同じく有効に機能します (個人的には、パラメーターが 1 つだけでも括弧を使用しますが、この点は見た目の好みなので、分別のある人々が分別をわきまえて異議を唱えるのは構いません)。

また、アロー関数の本体が 1 つの値を算出する単一の式である場合、明示的に値を返す必要はありません。その単一の式は、暗黙的にアロー関数の呼び出し側に返されるためです。ただし、本体が複数の文または式で構成されている場合は、波括弧が必須であり、すべての戻り値を通常の「return」構文を使用して呼び出し側に返す必要があります。

「this」の新しい定義

ECMAScript 6 の作成が開始される遥か前から、プログラマーたちは ECMAScript の this パラメーターが何を指すのか突き止めるのに苦労していました。表向きは、他の C 系列の言語を使用する場合と同じように、this はメソッドを呼び出す対象のオブジェクトを参照するパラメーターです。以下に一例を示します。

Show result

上記のパラメーターは明らかにインスタンス bob を参照していて、firstNamelastName、および (これもオブジェクトのメンバーであることから) displayMe の各メソッドの名前と値を律儀に出力します。

けれども this がグローバル・スコープに存在する関数から参照されると、事態は奇妙な方向に向かっていきます。

Show result

初期化されていないとき、ECMAScript ではグローバル・スコープをオブジェクトとして定義しているため、グローバル・スコープ内の関数から使用する場合の this は、グローバル・スコープ・オブジェクトを参照することになります。上記の例では、最上位のグローバル変数、関数、オブジェクト (この例では「console」など) を含め、グローバル・スコープの各メンバーを忠実に出力します。

このことから、上記の関数を 2 つの別個のコンテキスト内で再利用したとしても、このグローバル・スコープ関数がいずれの場合も多かれ少なかれ期待する処理を行うことがわかります。

Show result

上記の構文は少々奇妙かもしれませんが、ルールを理解して使用している限り、問題にはなりません。ECMAScript コンストラクター関数をオブジェクト・タイプとして使用するようになって初めて、事態は落ち着いてきます。

Show result

p.age の値が 10 分の 1 秒ごとに 1 増えていくと思うかもしれません。しかし、growUp 関数リテラルに含まれる thisp ではなくグローバル・オブジェクトを参照するため、p.age の値は 0 のまま変わりません (通常は、setInterval を設定した growUp() 関数を呼び出して永遠に、Person オブジェクトをエージング処理するはずですが、developerWorks サンドボックスでは、無期限に実行されるコードをサポートしていません。したがって、この例では setTimeout を使用しています)。

語彙的「this」バインディング

this に関する定義の問題を解決するために、アロー関数では「語彙的 this バインディング (lexical this binding)」と呼ばれるものを使用します。つまり、アロー関数は this の値を関数の実行時ではなく、定義時に使用します。

この違いを理解するのに最も簡単な方法は、Node.js の頼りになる昔ながらの EventEmitter を使用することです。EventEmitter (events モジュールから入手) は、単純なパブリッシュ/サブスクライブ・スタイルのメッセージング・システムです。EventEmitter には、特定のイベント名に対するコールバックを登録できます。そのイベントが「発行」されると、コールバックが登録した順に起動されます。

レガシー関数を EventEmitter を登録すると、キャプチャーされる this は、実行時に決定されたものになるはずです。一方、アロー関数を EventEmitter を登録すると、this はアロー関数を定義する時点でバインドされます。

Show result

関数イベントが起動されると、this は EventEmitter 自体にバインドされますが、アロー・イベントは実質的に何にもバインドされません (イベントごとに空のオブジェクトが出力されます)。

ジェネレーター関数

ジェネレーターは、他のパーティーが取り込める一連の値を生成するように意図された関数です。ジェネレーターは数々の関数型言語内で使用されているので、ストリームまたはシーケンスという名前でご存知かもしれません。このジェネレーターが、いよいよ ECMAScript にも登場しました。

ジェネレーターが実際にどのように機能するのかを理解するには、少々説明が必要です。まず、単純な名前の集合を定義します。この集合から名前のそれぞれを返す関数を作成し、すべての名前が返されるまで、関数呼び出しごとに 1 つの名前を返す必要があるとします。

Show result

一見すると、関数を返す関数という上記の巧みな手法に違和感を覚えるかもしれません。けれども、getName 関数は複数の関数呼び出しにわたって自身の状態をトラッキングする必要があるため、このような手法が必要になります。C のような言語内では、getName 関数内部の静的変数に状態を格納することができますが、C 言語の従妹のような Java や C# と同じく、ECMAScript では関数内での静的変数の使用をサポートしていません。この例の場合、クロージャーを使用して、関数リテラルが戻った後に「現在」の変数を維持し、その変数を自身の状態に使用するようにしています。

理解すべき重要な点は、(配列を返すという形で) 値の有限数列を一度にまとめて返すのではなく、この関数は、各要素を一度に 1 つずつ、要素がなくなるまで返すということです。

一方、返す要素が尽きることがないとしいたら、どうなるでしょうか?

関数型プログラミング内での無限ストリーム

前のサンプル・コードは、names 配列をまたがってイテレーターを使用する手法と比べ、それほど改善されていないように見えるでしょう。結局のところ、この処理がイテレーターの機能であり、コレクションの内容をまたがって個々の要素にアクセスできるようにしているのです。ただし、元の配列が可視でなくなり、配列でさえなくなると、重要な違いが現れてきます。

Show result

厳密に言うと、上記に示されているのは相変わらずイテレーターです。けれども、その実装は見本とされるイテレーターとは大幅に異なって見えます。この実装にコレクションはなく、一連のハードコーディングされた値があるだけです。

基本的に、この実装はコレクションが関連付けられていないイテレーターであり、重要な点を明らかにしています。それは、関数によって生成される値のソースが、呼び出し側から離れて奥深くにカプセル化されているということです。このことから、さらに興味深い考えとして、呼び出し側が開始となるコレクションがないこと、生成される値には終わりがないことを認識しない可能性があります。これが、一部の言語で無限ストリームと呼んでいるものです。

フィボナッチ数列 (地球上のあらゆる関数型言語で「Hello World」に相当するもの) は、そのような無限ストリームの一例です。

Show result

An infinite stream is one that will never run out of values to return. In this case, there is no logical end to the Fibonacci series.

最初は奇妙に思えるかもれませんが、無限ストリームの概念は、リアクティブ・プログラミングなどの他の興味深い ECMAScript ベースの手法の中核となっています。例えば、ユーザー・イベント (マウスの移動、ボタンのクリック、キーの押下など) を無限ストリームとして考えて、関数によってそれぞれのユーザー・イベントをストリームからプルして処理する場合を考えてみてください。

無限ストリームを作成するために必要なコードは手に負えない量になることから、ECMAScript 6 ではそのようなコードを簡潔にするための新しい構文 (および新しいキーワード) を定義しています。以下はその一例です。

Show result

この場合も、関数は名前のそれぞれを順に出力します。処理する名前がなくなると、果てしなく「undefined」を出力します。構文に関しては、yield キーワードは return のように見えますが、実際には「関数から戻っても、この関数の中でどこにいたのかを忘れないようにして、次に呼び出されたときに、そこから実行を再開できるようにする」ことを意味します。この動作は明らかに、従来の return より複雑です。

このジェネレーターの使用方法は、最初の例とは少々異なります。最初の例では、getName 関数の戻りをキャプチャーしてから、イテレーターのようにして、関数から返されたオブジェクトを使用しました。この仕組みは、ECMAScript 6 に組み込まれた意図的な設計決定です。厳密には、ジェネレーター関数は Generator オブジェクトを返します。このオブジェクトが、ジェネレーター関数から個々の値を取得するために使用されます。新しい構文は、できる限りイテレーターに近づくように意図されています。

イテレーターと言えば、もう 1 つ、知っておく必要がある構文上の変更があります。

for-of キーワード

ECMAScript 6 内では、従来の for ループがイメージ・チェンジしています。それは、第 2 のキーワード of が追加されたためです。さまざまな点で、この新しい構文は for-in とそれほど変わりませんが、ジェネレーター関数などの機能をサポートします。

フィボナッチ数列を再び例に取ると、この関数に for-of キーワードを追加した場合は以下のようになります。

Show result

for-offor-in との間には微妙な違いがありますが、ほとんどの場合は、古い構文に代えて for-of を使用できます。無限ストリームの例内で getName() に対して行ったように、このキーワードは、ジェネレーターを暗黙的に使用できるようにするだけです。

まとめ

ここまで読んで、ECMAScript 6 は間違いなく、単なる不具合を修正するためのリリースでないことは理解できたはずです。この記事で説明した機能の多くは、関数型プログラミングの概念に由来していますが、そのことに惑わされないでください。ECMAScript 6 は、関数型言語とはまるで違います。特定の関数型機能を採用することで、ECMAScript コードを作成しやすくなりますが、それにはモナド、モノイド、カテゴリー理論の知識が必要です。大まかに見れば、関数型の機能が自分にとって有利に働くなら使用し、そうでないなら他にも選択肢は山ほどある、という態度が主流であるようです。

関数型プログラミングの経験がまったくないとしても、頑張りすぎないでください。ECMAScript 6 は関数型プログラミングに慎重に足を踏み入れられるようにしていますが、その世界にどっぷり浸けることはありません。

いずれにしても、このシリーズの次回の記事では関数型プログラミングから離れて、ECMAScript 6 に含まれている新しいクラス・ベースの構文およびオブジェクトの拡張機能について解説します。


ダウンロード可能なリソース


関連トピック


コメント

コメントを登録するにはサインインあるいは登録してください。

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=60
Zone=Web development
ArticleID=1040101
ArticleTitle=多忙な JavaScript 開発者のための ECMAScript 6 ガイド、第 2 回: 関数に関する機能強化
publish-date=07132017