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