Template literals
ES2015では文字列処理が強化され、複数行文字列や文字列内挿機能を使用できるようになりました。Template literalsではシングルクォートの代わりにバックティック文字(` `)で文字列を囲みます。
リスト1:Template literalsの使用例
8 | `Hello ${name}, how are you ${time}?` |
Tagged Template literals
Template literalには「タグ付け」という機能があります。タグ付けされたTemplate literalでは関数を使用して出力結果を変更することができます。タグ付けされた関数では、Template literal内の文字列と変数を受け取ることができ、関数から返された値がTemplate literalの処理結果となります。
下記の例ではTemplate literalの先頭に「tag」という文字が付いており、「tag」という関数と紐付いています。
リスト2:Template literalのタグ付けの使用例
01 | const prefecture = 'Tokyo', |
04 | const tag = (strings, ...values) => { |
07 | console.log(strings[0]) // "I live in " |
08 | console.log(strings[1]) // ", " |
09 | console.log(values[0]) // Tokyo |
10 | console.log(values[1]) // Japan |
15 | tag`I live in ${prefecture}, ${country}` |
Raw strings
String.raw()は、未加工の文字列を取得するために使用されます。デフォルトテンプレート関数のように未加工の文字列を生成し、文字列連結を行います。
リスト3:未加工の文字列を取得するString.raw()
1 | String.raw`Hi\n${ 2 + 3 }!` |
Destructuring
ES2015では、変数の分割代入が使用できるようになりました。以下に、配列やオブジェクトからの分割代入の例を示します。
配列の分割代入
リスト4:配列の分割代入の例
1 | const [year, month, day] = [2016, 8, 11] |
オブジェクト分割代入
リスト5:オブジェクト分割代入の例
1 | let {a, b} = {a: 1, b: 2} |
Rest
下記の例では、'Cheeseburger'が変数hamburgerに割り当てられ、配列の残りの部分(Rest)がfruitsで表しています。
リスト6:Rest
1 | const [hamburger, ...fruits] = ['Cheeseburger', 'apple', 'banana', 'peach'] |
2 | console.log(hamburger, fruits) |
3 | // 'Cheeseburger', ['apple', 'banana', 'peach'] |
分割代入は、関数の引数でも使用することができます。
リスト7:引数に分割代入を使用する
1 | const foo = ({a, b}) => { |
以下のように、デフォルト引数と組み合わせることもできます。
リスト8:デフォルト引数との組み合わせ例
1 | const foo = ({a = 0, b = 0} = {}) => { |
Default + Rest + Spread
ES2015では、関数の引数についても、よりわかりやすい記法が使えるようになりました。
Default
引数の初期値を指定することができます。
リスト9:引数の初期値を指定
1 | const func = (x, y = 12) => { |
Rest
可変長引数を配列として受け取ることができます。
リスト10:可変長の引数を配列として扱う
1 | const func = (x, ...y) => { |
3 | console.log(y) // ["hello", true] |
6 | func(3, "hello", true) // 6 |
Spread
配列を引数として受け取り、展開することができます。
リスト11:配列を展開し、個々の引数として扱う
1 | const func = (x, y, z) => { |
Note
ES2015で導入された記法を既存のメソッドと組み合わせると、さらに簡潔な記述にできる場合があります。以下に、いくつかの例を示します。
Array.prototype.push
リスト12:Array.prototype.push
1 | const domains = ['com', 'net', 'org'], |
4 | // ['com', 'net', 'org', 'tokyo', 'jp'] |
Array.prototype.concat
リスト13:Array.prototype.concat
2 | ['Coca-Cola', 'Coffee'], |
3 | ['Hamburger', 'Cheeseburger'], |
4 | ['Hash Browns', 'Salad'] |
7 | // ["Coca-Cola", "Coffee", "Hamburger", "Cheeseburger", "Hash Browns", "Salad"] |
Math.max / Math.min
リスト14:Mathオブジェクトの例
1 | const fib = [1, 2, 3, 5, 8, 13, 21, 34, 55, 89] |
Iterators + For..Of
既存のIterator(反復子)と「For..Of」の組み合わせは、反復処理機能を提供します。JavaScriptは今までもforループなどの反復処理機能を備えていましたが、このIteratorと「For..Of」との組み合わせは、容易に反復処理の停止/再開が行える点が特徴です。
Iterable/Iterator
反復処理が可能であることを「Iterable」と言い、IterableなオブジェクトにはArrayやMap/Setなどが挙げられます。
「Iterableである」ということはIteratorインターフェースを持っていることと同義で、「Symbol.iterator」をkeyに、Iteratorインターフェースvalueに持っているオブジェクトをIterableと呼びます。
リスト15:Iterartorの取得
2 | array[Symbol.iterator]() |
Iteratorはnext()というメソッドを持ち、next()メソッドはイテレーションが終わりかどうかを示すdoneと、値を格納したvalueを持つオブジェクト(Iterator Result)を返します。
リスト16:Iteratorのnext()メソッド
1 | let iterableObject = [1, 2, 3], |
2 | iterator = iterableObject[Symbol.iterator](), |
3 | iteratorResult = iterator.next() // {value: 1, done: false} |
まとめると、Iterable/Iterator/Iterator Resultの関係性は下記のようになっています。
Iterable、Iterator、Iterator Resultの関係
For..Of
Iteratorが保持している要素を簡単に取り出すための構文がFor..ofです。
For..Ofを使用しない例
リスト17:For..Ofを使わないコードの例
01 | const areas = ['daikanyama', 'nakameguro', 'jiyugaoka'] |
02 | const iterator = areas[Symbol.iterator]() |
04 | let next = iterator.next() |
06 | console.log(next.value) |
07 | next = iterator.next() |
For..Ofを使用した例
リスト18:For..Ofを使用したコードの例
1 | const areas = ['daikanyama', 'nakameguro', 'jiyugaoka'] |
このようにFor..Ofを使えば、使用しない場合に必要となる下記の手順をより簡潔に記述できるようになります。
- iterable[Symbol.iterator]()を実行して、Iterator を取得する
- iterator.next()を実行して、Iterator Resultを取得
- iteratorResult.done === trueなら反復を終了し、そうでなければ反復を繰り返す
Generators
GeneratorはGenerator関数と関数本体の中にyield句を利用することで、Iterableなオブジェクトを作る手段です。ちなみに、yieldは一時停止という意味なので、関数をreturnで抜けるのではなく、一時停止させてnextが呼ばれるまで待つという動きをします。
まずはGeneratorなしで、Iterator/IterableをObjectに実装してみます。
リスト19:Generatorなしの実装例
01 | const iterableFibonacci = {} |
03 | iterableFibonacci[Symbol.iterator] = () => { |
09 | iterator.next = () => { |
15 | return done ? {done} : {value, done} |
21 | for (number of iterableFibonacci) { |
24 | // 0 1 1 2 3 5 8 13 21 34 |
やっていること自体はIterableに「[Symbol.iterator]()」メソッド、Iteratorに「next()」メソッドを実装しているだけなのでシンプルですが、決して可読性が高いとは言えません。
そこでGeneratorを用いて、先ほどの内容をリファクタリングしてみます。
リスト20:Generatorを用いた実装例
01 | function* fibonacciGenerator() { |
05 | for (let i = 0; i < 10; i++) { |
11 | for (number of fibonacciGenerator()) { |
14 | // 0 1 1 2 3 5 8 13 21 34 |
複雑な処理をGeneratorに請け負ってもらうことで、同じ処理でも格段に可読性があがることがわかります。
ちなみに、classの中でgenerator関数を定義する時は下記のように記述できます。
リスト21:class内でgenerator関数を定義する
08 | const subway = new Subway() |
09 | const serve = subway.serve() |
10 | console.log(serve.next()) // {value: "恵比寿", done: false} |
11 | console.log(serve.next()) // {value: "中目黒", done: false} |
12 | console.log(serve.next()) // {value: undefined, done: true} |
また、Generatorには「yield」だけでなく「yield*」も存在します。これは一言で言ってしまえばIterableのIteratorを一つずつyieldしてくれるものなのですが、これだけではわかりにくいと思うので下記をご覧ください。
yield*
リスト22:yield*の使用例
01 | const osx = function*() { |
08 | const macos = function*() { |
13 | const iterator = macos() |
15 | iterator.next() // {value: "Mountain Lion", done: false} |
16 | iterator.next() // {value: "Mavericks", done: false} |
17 | iterator.next() // {value: "Yosemite", done: false} |
18 | iterator.next() // {value: "El Capitan", done: false} |
19 | iterator.next() // {value: "Sierra", done: false} |
20 | iterator.next() // {value: undefined, done: true} |
もちろんArrayはIterableなので、yield*に渡してやることができます。
リスト23:Arrayをyield*に渡す
01 | function* assistantsGenerator(){ |
02 | yield* ['dog', 'monkey', 'pheasant'] |
06 | for(var assistant of assistantsGenerator()) { |
07 | console.log(assistant) |
(編注:2017年2月16日17時00分更新)記事公開当初、誤って修正途中の古いバージョンの記事が公開されておりました。お詫びして訂正致します。