ES2015で導入された、より洗練された構文 Part 2

2017年2月14日(火)
太田 智彬
前回に続いて、ECMAScript 2015(ES2015)で追加された構文を紹介する。

Template literals

ES2015では文字列処理が強化され、複数行文字列や文字列内挿機能を使用できるようになりました。Template literalsではシングルクォートの代わりにバックティック文字(` `)で文字列を囲みます。

リスト1: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」という関数と紐付いています。

リスト2:Template literalのタグ付けの使用例

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()は、未加工の文字列を取得するために使用されます。デフォルトテンプレート関数のように未加工の文字列を生成し、文字列連結を行います。

リスト3:未加工の文字列を取得するString.raw()

String.raw`Hi\n${ 2 + 3 }!`
// "Hi\\n5!"

Destructuring

ES2015では、変数の分割代入が使用できるようになりました。以下に、配列やオブジェクトからの分割代入の例を示します。

配列の分割代入

リスト4:配列の分割代入の例

const [year, month, day] = [2016, 8, 11]

オブジェクト分割代入

リスト5:オブジェクト分割代入の例

let {a, b} = {a: 1, b: 2}
({a, b} = {a: 3, b: 4})

Rest

下記の例では、'Cheeseburger'が変数hamburgerに割り当てられ、配列の残りの部分(Rest)がfruitsで表しています。

リスト6:Rest

const [hamburger, ...fruits] = ['Cheeseburger', 'apple', 'banana', 'peach']
console.log(hamburger, fruits)
// 'Cheeseburger', ['apple', 'banana', 'peach']

分割代入は、関数の引数でも使用することができます。

リスト7:引数に分割代入を使用する

const foo = ({a, b}) => {
  console.log(a, b)
}
foo({a: 1, b: 2})

以下のように、デフォルト引数と組み合わせることもできます。

リスト8:デフォルト引数との組み合わせ例

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

引数の初期値を指定することができます。

リスト9:引数の初期値を指定

const func = (x, y = 12) => {
  return x + y
}
func(3) // 15

Rest

可変長引数を配列として受け取ることができます。

リスト10:可変長の引数を配列として扱う

const func = (x, ...y) => {
  console.log(x) // 3
  console.log(y) // ["hello", true]
  return x * y.length
}
func(3, "hello", true) // 6

Spread

配列を引数として受け取り、展開することができます。

リスト11:配列を展開し、個々の引数として扱う

const func = (x, y, z) => {
  return x + y + z
}
func(...[1, 2, 3]) // 6

Note

ES2015で導入された記法を既存のメソッドと組み合わせると、さらに簡潔な記述にできる場合があります。以下に、いくつかの例を示します。

Array.prototype.push

リスト12:Array.prototype.push

const domains = ['com', 'net', 'org'],
      jp = ['tokyo', 'jp']
domains.push(...jp)
// ['com', 'net', 'org', 'tokyo', 'jp']

Array.prototype.concat

リスト13: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

リスト14:Mathオブジェクトの例

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と呼びます。

リスト15:Iterartorの取得

const array = [1, 2, 3]
array[Symbol.iterator]()
// ArrayIterator {}

Iteratorはnext()というメソッドを持ち、next()メソッドはイテレーションが終わりかどうかを示すdoneと、値を格納したvalueを持つオブジェクト(Iterator Result)を返します。

リスト16:Iteratorのnext()メソッド

let iterableObject = [1, 2, 3],
    iterator = iterableObject[Symbol.iterator](),
    iteratorResult = iterator.next() // {value: 1, done: false}

まとめると、Iterable/Iterator/Iterator Resultの関係性は下記のようになっています。

Iterable、Iterator、Iterator Resultの関係

Iterable、Iterator、Iterator Resultの関係

For..Of

Iteratorが保持している要素を簡単に取り出すための構文がFor..ofです。

For..Ofを使用しない例

リスト17: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を使用した例

リスト18: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に実装してみます。

リスト19:Generatorなしの実装例

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を用いて、先ほどの内容をリファクタリングしてみます。

リスト20: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関数を定義する時は下記のように記述できます。

リスト21: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*

リスト22: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*に渡してやることができます。

リスト23:Arrayを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分更新)記事公開当初、誤って修正途中の古いバージョンの記事が公開されておりました。お詫びして訂正致します。

リクルートテクノロジーズ ITマネジメント統括部 ディベロップメント3部所属
東京都渋谷区出身。大規模サイトの構築やWebアプリケーションの開発を経て、現在はテクニカルディレクターとしてフロントエンドのチームリード/制作フロー改善に従事。著書:『エンジニアのためのGitの教科書』『ブレイクスルーJavaScript』(翔泳社)『現場で役立つCSS3デザインパーツライブラリ』(MdN)ほか。

連載バックナンバー

Web開発技術解説
第6回

ES2015のモジュール管理

2017/5/9
ES2015に追加されたモジュール管理の機能を紹介し、現行のブラウザから使用する方法を解説する。
開発言語技術解説
第5回

ES2015が備えるモダンな非同期処理

2017/3/28
ES2015に追加された非同期処理の新しい記述方法について学ぶ。

Think ITメルマガ会員登録受付中

Think ITでは、技術情報が詰まったメールマガジン「Think IT Weekly」の配信サービスを提供しています。メルマガ会員登録を済ませれば、メルマガだけでなく、さまざまな限定特典を入手できるようになります。

Think ITメルマガ会員のサービス内容を見る

他にもこの記事が読まれています