JavaScript 文法 (ES2015/2016/2017)

2015年6月にリリースされた新しいJavaScriptの仕様「ES2015」によって、JavaScriptの文法は大きな変化を遂げました。従来のJavaScriptの書き方も引き続き使用できますが、今後は ES2015
以降が主流になっていくはずなので、新しくJavaScriptを学ぶ方は、ES2015 以降の文法を学ぶことをおすすめします。

ちなみに、JavaScriptの標準仕様のことを「ECMAScript」と呼び、ES2015 は2015年にリリースされたのでそのように呼ばれています。また、6度目の仕様変更だったので ES6 とも呼ばれます。

※ JavaScript の仕様は毎年更新される予定ですが、各ブラウザがサポートしないと動作しないので、ES2015以降の文法をネイティブのJavaScriptに変換する Babel を使用するか、ブラウザの公式サイトなどで対応状況を確認しておきましょう。Babelのサイトで ES2015 をネイティブの JavaScript に変換・実行してくれるページがあるので、こちらで練習すると効率的です。ただし、import/export のモジュール機能はファイルを2つ以上使用したりするので、その場合は webpack などで Babel を使うことをおすすめします。(参照: webpack3 でフロントエンド開発(入門編))

ES2015
ES2016
ES2017
Stage

ES2015

ES2015 に沿った形で、よく使う JavaScript の文法をまとめました。

変数

変数の前にletをつけます。

let a = 'hoge'

定数など、再代入を禁止するにはconstをつけます。

const a = 'hoge'
a = 'fuga' // エラー

テンプレート文字列

バッククォート`を使用すると、改行をそのまま変数に反映させることができます。

let a = `ho
ge`
console.log(a)
// "ho
// ge"

また、テンプレート文字列内で${ }を使うと変数の値を出力することができます。

let a = 'hoge'
console.log(`aの値: ${a}`)
// aの値: hoge

コメント

// から行末までがコメントになります。

// コメント行

評価値が false になるパターン

以下の場合は false になります。

・ null
・ undefined
・ 0
・ ''
・ false

配列

const arr = [1, 2]

// 追加
arr.push(3)

// 結合
arr.concat([3, 4, 5])

// 特定の値のインデックス取得
let i = arr.indexOf(2)

// 特定のインデックス要素を削除
arr.splice(i, 1)

// 最後の要素を削除
arr.pop()

// 連結して文字列を作る
[1, 2, 3].join("-") // "1-2-3"

map
各要素に対して何らかの処理をしてから新しい配列を作ります。
array は元の配列です。(index と array は省略可)

const arr = [1, 2]
// 各要素に2をかける
let arr2 = arr.map((element, index, array)=> element * 2)
console.log(arr2) // [2, 4]

filter
条件に合う要素のみ抽出します。array は元の配列です。(index と array は省略可)

const arr = [1, 2, 3, 4, 5]
// 2の倍数を抽出
const arr2 = arr.filter((element, index, array)=> element % 2 == 0)
console.log(arr2) // [2, 4]

reduce
要素の最初から取り出していき、最終的に1つの値を算出します。
currentValue は現在取り出している要素、previousValue はそれまでに処理した結果の合計値です。

const arr = [1, 2, 3]
// 各要素を足していく
const result = arr.reduce((previousValue, currentValue, index, array)=> previousValue + currentValue)
console.log(result) // 6

reduceRight にすると、要素の最後から取り出します。

every/some
every は全ての要素がある条件を満たすかどうか、some は条件を満たす要素があるかを true/false で返します。array は元の配列です。(index と array は省略可)

const arr = [1, 2, 3]
const result = arr.every((element, index, array)=> element > 5)
console.log(result) // false

const arr = [1, 2, 3]
const result = arr.some((element, index, array)=> element < 2)
console.log(result) // true

連想配列(オブジェクト)

const obj = { a: 1, b: 2 }

// キーのみ配列で取り出す
Object.keys(obj) // ["a", "b"]

// 追加
obj['c'] = 3
obj.c = 3

// 結合
Object.assign(obj, { c: 3 })

// 特定のキーを削除
delete(obj['c'])
delete(obj.c)

変数名をキー名として適用する場合は、ショートハンド(短縮記法)が使用できます。

let a = 1
console.log({ a }) // { a: 1 }

Map

Key-Value でデータを扱うクラスです。キーに Object を指定することもできます。

let map = new Map()

// 値をセットする
map.set('a', 1)
map.set('b', 2)

// 初期化と同時にセットする場合
let map = new Map([['a', 1], ['b', 2]])

// 値を取り出す
map.get('a')

// キーの存在確認
map.has('a')

// キーの数
map.size

// 特定キーの削除
map.delete('a')

// キーの全削除
map.clear()

Set

値が重複しない配列を扱うクラスです。同じ値を追加しようとしても無視されます。

let set = new Set()

// 値をセットする
set.add(1)
set.add(2)

// 初期化と同時にセットする場合
let set = new Set([1, 2])

// 値の存在確認
set.has(1)

// 値の数
set.size

// 特定の値を削除
set.delete(1)

// 値の全削除
set.clear()

三項演算子

条件式 ? 式1 : 式2の形で使用し、条件式 が true の場合は 式1、false の場合は 式2 を返します。

const num = 30
console.log(num >= 20 ? '20以上です' : '20未満です') // 20以上です

条件分岐

if の場合

if (条件) {
  ...
}

// 1行で書く場合
if (条件) ...

if (条件) {
  ...
} else {
  ...
}

if (条件1) {
  ...
} else if (条件2) {
  ...
} else {
  ...
}

switch の場合

const a = 10
switch (a) {
  case 1:
    ...
    break
  // 複数指定
  case 2:
  case 3:
    ...
    break
  // 指定したcase以外
  default:
    ...
}

ループ

forの場合

// 3回ループ
for(let i = 0; i < 3; i++) {
  console.log(i) // 0, 1, 2
}

for-in の場合

// 配列
for(let val in [1, 2, 3]) {
  console.log(val) // 1, 2, 3
}

// オブジェクト
let json = {a: 1, b: 2}
for(let key in json) {
  console.log(json[key]) // 1, 2
}

for-of の場合

// 配列
for(let val of [1, 2, 3]) {
  console.log(val) // 1, 2, 3
}

// Map
let map = new Map([['a', 1], ['b', 2]])
for (let [key, value] of map.entries()) {
  console.log(key) // "a", "b"
  console.log(value) // 1, 2
}

// Set
let set = new Set([1, 2])
for(let val of set) {
  console.log(val) // 1, 2
}

forEach の場合

// 配列
[1, 2, 3].forEach(function(val, i) {
  console.log(val) // 1, 2, 3
  console.log(i) // 0, 1, 2
})

// Map
let map = new Map([['a', 1], ['b', 2]])
map.forEach(function (value, key) {
  console.log(key) // "a", "b"
  console.log(value) // 1, 2
})

// Set
let set = new Set([1, 2])
set.forEach(function(val) {
  console.log(val) // 1, 2
})

分割代入

配列やオブジェクトの値を個別に取り出して、変数に入れることができます。

//配列
let arr = [1, 2, 3]
let [a, b] = arr
console.log(a) // 1
console.log(b) // 2

// 初期値を設定する場合. 何も書かないことで変数名を省略可能
let [a, b,, d = 4] = arr
console.log(d) // 4

// オブジェクト
let obj = {a: 1, b: 2, c: 3}
let {a, b} = obj
console.log(a) // 1
console.log(b) // 2

// 任意のキー名に設定する場合
let {a: x, b: y} = obj

console.log(x) // 1
console.log(y) // 2

// 初期値を設定する場合
let {a, b, e=5} = obj
console.log(e) // 5

クラス

class User {
  constructor(name) {
    // プロパティの設定
    this.name = name
  }
  // メソッド
  hello() {
    console.log(`Hello, ${this.name}`)
  }

  // クラスメソッド
  static hello() {
    console.log("Hello")
  }
}

let user = new User('Tanaka')
user.hello() // "Hello, Tanaka"

User.hello() // "Hello"

クラスを継承することもできます。

// 親クラス
class Animal {
  constructor(name) {
    this.name = name
  }
  hello() {
    return `Hello, ${this.name}`
  }
}

// 子クラス
class Cat extends Animal {
  hello() {
    // 親クラスのメソッドは super で呼び出す
    console.log(super.hello())
  }
}

let cat = new Cat('Tama')
cat.hello() // "Hello, Tama"

関数

function

function add(a, b) {
  return a + b
}

add(1, 2) // 3

// 引数のデフォルト値あり
function add(a, b = 5) {
  return a + b
}

add(1) // 6

アロー関数

function( ) ではなく ( )=> を使うアロー関数というものもあります。

const add = (a, b) => {
  return a + b
}

add(1, 2) // 3

引数が1つのときは ( ) を省略できます。

const add = a => { return a + 5 }
add(1) // 6

さらに、処理が1行の場合は { } と return を省略できます。

const add = a => a + 5
add(1) // 6

オブジェクトリテラルを返すときは、{ } がオブジェクトのものかブロックのものかわからなくなるので、( ) で囲みます。この場合、return は暗黙的につけられるので省略できます。

const add = (x, y) => ({ result: x + y })
add(1, 2) // { result: 3 }

アロー関数内のthisは、function( )の時と異なり自身のオブジェクトを指します。

// 指定秒数ごとにカウントするクラス
class Timer {
  constructor(interval) {
    this.count = 0
    this.interval = interval
  }

  start() {
    setInterval(() => {
        // thisは自身のオブジェクトを指したまま. function() で書くとグローバルオブジェクトになる
        this.count++
        console.log(this.count)
    }, this.interval)
  }
}

let timer = new Timer(1000)
timer.start()

import/export

変数や関数、クラスをモジュールとして定義することができます。これにより、あるファイルから別のファイルで定義された変数・関数・クラスを呼び出すことができます。

モジュールの定義

exportをつけて定義します。例として、module.js というファイルに以下の3つのモジュールを定義してみます。

// 変数
export const num = 1

// 関数
export const add = (a, b)=> {
  return a + b
}

// クラス
export class Greeting {
  hello() {
    return 'Hello'
  }
}

// デフォルトモジュール
// importする際に特に指定がなければ default がついたモジュールが呼ばれます
export default function(a, b) {
  return a - b
}

※ デフォルトモジュールは、関数またはクラスでのみ利用でき、変数はエラーになります。

export default function ...
export default class ...
export default const ...
export default let ...

モジュールの呼び出し

importで呼び出します。呼び出し方にはいろいろなパターンがあります。

import * as mod from './module.js' // 全モジュールを1つの変数で読み込む場合
import substract from './module.js' // デフォルトモジュールのみ読み込む場合
import { num, add } from './module.js' // モジュールを個別に読み込む場合
import './module.js' // 読み込むだけの場合. cssなど.

mod.num // 1
mod.add(1, 2) // 3

let greeting = new mod.Greeting()
greeting.hello() // "Hello"

substract(10, 2) // 8

add(10, 20) // 30

Promise

非同期処理の結果を受け取る処理をメソッドチェーンで(=直列的に)書くことができます。promiseオブジェクト.then({ ... }).catch({ ... })という形で、promise オブジェクトに非同期の処理を書きます。

const asyncFunc = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      // 成功の場合はresolveを呼びます
      resolve('success')

      // 失敗の場合はrejectを呼びます
      // reject('failure')
    }, 1000)
  })
}

asyncFunc()
  // 成功時
  .then(data => {
    // 引数の data に resolve で渡した値が入ってくる
    console.log(data)
  })
  // 失敗時
  .catch(data => {
    // 引数の data に reject で渡した値が入ってくる
    console.log(data)
  }
)

非同期処理が連続する場合でも、どちらの処理のエラーも catch で受け取ることができます。以下は asyncFunc1() に続けて asyncFunc2() を実行する例です。

const asyncFunc1 = () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      // 成功の場合
      resolve('asyncFunc1: success')

      // 失敗の場合
      // reject('asyncFunc1: failure')
    }, 1000)
  })
}

// 引数の data には、asyncFunc1 の resolve から渡された変数が入っている
const asyncFunc2 = (data) => {
  console.log(data)

  return new Promise((resolve, reject) => {
    setTimeout(() => {
      // 成功の場合
      resolve('asyncFunc2: success')

      // 失敗の場合
      // reject('asyncFunc2: failure')
    }, 1000)
  })
}

asyncFunc1()
  // asyncFunc1 成功時
  .then(asyncFunc2)
  // asyncFunc2 成功時
  .then(data => {
    console.log(data)
  })
  // asyncFunc1 または asyncFunc2 失敗時
  .catch(data => {
    // 引数の data に reject で渡した値が入ってくる
    console.log(data)
  }
)

Promise.all()
引数に promise オブジェクトの配列を与えると、すべての非同期処理で resolve が呼ばれた後に then が呼ばれます。

const asyncFunc1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    // 成功の場合
    resolve('asyncFunc1: success')

    // 失敗の場合
    // reject('asyncFunc1: failure')
  }, 1000)
})

const asyncFunc2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    // 成功の場合
    resolve('asyncFunc2: success')

    // 失敗の場合
    // reject('asyncFunc2: failure')
  }, 1000)
})

Promise.all([asyncFunc1, asyncFunc2])
  .then(data => {
    // すべての resolve の値が配列として data に渡ってきます
    console.log(data) // ["asyncFunc1: success", "asyncFunc2: success"]
  })
  .catch(data => {
    console.log(data)
  }
)

Promise.race()
引数に promise オブジェクトの配列を与えると、いずれかの非同期処理で resolve が呼ばれた時点で then が呼ばれます。それ以降 resolve されても then は呼ばれません。一番早く終わった非同期処理のみ採用したい場合に使用します。

const asyncFunc1 = new Promise((resolve, reject) => {
  setTimeout(() => {
    // 成功の場合
    resolve('asyncFunc1: success')

    // 失敗の場合
    // reject('asyncFunc1: failure')
  }, 1000)
})

const asyncFunc2 = new Promise((resolve, reject) => {
  setTimeout(() => {
    // 成功の場合
    resolve('asyncFunc2: success')

    // 失敗の場合
    // reject('asyncFunc2: failure')
  }, 2000)
})

Promise.race([asyncFunc1, asyncFunc2])
  .then(data => {
    // 最初に完了した非同期処理の resolve の値が data に渡ってきます
    console.log(data) // "asyncFunc1: success"
  })
  .catch(data => {
    console.log(data)
  }
)

ES2016

2016年6月に2つの仕様がリリースされました。

使用方法(webpackの場合)

babel-preset-es2016 をインストールします。

$ npm install --save-dev babel-preset-es2016

webpack.config.js

presets: ['es2015', 'es2016']

Array.prototype.includes()

配列に特定の要素が含まれているかを true/false で返します。

[1,2,3].includes(1) // true

Exponentiation Operator

べき乗の計算を行うオペレータです。

2 ** 3 // 8

ES2017

2017年6月に6つの仕様がリリースされました。

使用方法(webpackの場合)

babel-preset-es2017, babel-plugin-transform-runtime をインストールします。

$ npm install --save-dev babel-preset-es2017 babel-plugin-transform-runtime

webpack.config.js

query: {
  presets: ['es2015', 'es2016', 'es2017'],
  plugins: ['transform-runtime']
}
※ babel-plugin-transform-runtime をインストールしないと、 下記の async functions を使用する時に Can't find variable: regeneratorRuntime のエラーが出ます

async functions

Promise の非同期処理を同期的に呼び出すようにします。非同期処理の呼び出し時にawaitをつけると、結果が返るまで待機するようになります。非同期処理の関数と、await をつけてその非同期処理を呼び出す関数は asyncをつけて定義します。以下は ES2015 の Promise を書き直した例です。

const asyncFunc1 = async () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      // 成功の場合
       resolve('asyncFunc1: success')

      // 失敗の場合
      // reject('asyncFunc1: failure')
    }, 1000)
  })
}

const asyncFunc2 = async () => {
  let result = await asyncFunc1()
  console.log(result)

  setTimeout(() => {
    console.log('asyncFunc2: success')

    // 失敗の場合
    // console.log('asyncFunc2: failure')
  }, 1000)
}

asyncFunc2().catch(data => console.log(data))

asyncFunc1のエラーは catch でハンドリングできますが、asyncFunc2 の中でtry-catchすることもできます。ただし、catch した後はasyncFunc2 のその後の処理も実行されます。

try {
  let result = await asyncFunc1()
} catch (data) {
  console.log(data)
}

// ... この後の処理も実行される

また、await は並行して走らせることもできます。たとえば、5秒後に結果が返る非同期関数を2つ用意します。

const asyncFunc1 = async () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(1)
    }, 5000)
  })
}

const asyncFunc2 = async () => {
  return new Promise((resolve, reject) => {
    setTimeout(() => {
      resolve(2)
    }, 5000)
  })
}

この2つの返り値を表示する時に、以下のように呼び出すと10秒かかります。

const showResult = async () => {
  let result1 = await asyncFunc1() // 5秒待つ
  let result2 = await asyncFunc2() // さらに5秒待つ
  console.log(result1, result2)
}

showResult()

しかし、以下のようにすると待機処理が並行して走るので5秒で済みます。

const showResult = async () => {
  let result1 = asyncFunc1()
  let result2 = asyncFunc2()
  console.log(await result1, await result2)
}

showResult()

Trailing commas in function parameter lists and calls

関数定義の引数と、呼び出し時の引数で、末尾のカンマを許容します。

const a = (num,) => {
  console.log(num)
}

a(1,)

Object.values/Object.entries

Object.values は、Object の値を配列で返します。

const obj = {
 a: 1,
 b: 2
}

Object.values(obj) // [1, 2]

Object.entries は、Object のキーと値のペアの配列を要素にした配列を返します。

const obj = {
 a: 1,
 b: 2
}

Object.entries(obj) // [["a", 1], ["b", 2]]

String Padding

特定の文字数になるまで文字を埋めます。文字の最初から埋める場合はpadStart、最後から埋める場合はpadEndを使用します。第1引数が文字の長さ、第2引数が埋める文字列になります(指定しない場合は" ")。

console.log('123'.padStart(5) // "  123"
'123'.padStart(5, '45') // "45123"
'123'.padEnd(5) // "123  "
'123'.padEnd(5, '45') // "12345"

Object.getOwnPropertyDescriptors

Object の PropertyDescriptor をコードから取得できるようになります。

const obj = {
  a: 1,
  b: 2
}

const descriptor = Object.getOwnPropertyDescriptors(obj)
console.log(descriptor)
/* { 
      a: {
        value: 1, writable: true, enumerable: true, configurable: true 
      }, 
      b: {
        value: 2, writable: true, enumerable: true, configurable: true 
      }
   }
*/  

Value は値、Writable は Value が書き換え可能かどうか、Enumerable は Value をfor...inで列挙可能かどうか、Configurable は Descriptor を編集可能か、を示します。

(あまりないと思いますが)Object.create()などで Object をコードで1から生成する時に、property 定義を特定の Object と同じものにしたい場合などに活用できそうです。

Shared memory and atomics

複数の Web Worker やスレッド間で特定の長さのバイト情報を共有できるSharedArrayBufferと、共有されている SharedArrayBuffer が複数の Web Worker やスレッドから読み書きされてもデータの整合性・一貫性を保つAtomicが提供されています。

この分野はかなり説明が長くなってしまうので省略します。

Stage

Stage は、未リリースだけど将来正式な仕様として採用されるかもしれない機能です。0から始まり、条件を満たすと1つずつアップして、4になるとリリース確定の状態になります。Stage の内容は随時変更されるので、使う前に確認するようにしましょう。

ECMAScript の Stage 内訳
Babel の Stage 内訳

Stage の機能を webpack で利用する場合
下記では全部インストールしていますが、使いたい stage-n のみでも大丈夫です。

$ npm install babel-preset-stage-0 babel-preset-stage-1 babel-preset-stage-2 babel-preset-stage-3

webpack.config.js

presets: ['es2015', 'stage-0', 'stage-1', 'stage-2', 'stage-3']

Stageの中から便利な機能を紹介します。(投稿日時点の情報です)

Rest/Spread Properties(stage3)

...で配列やオブジェクトを展開するオペレータ。さまざまな場面で使用することができます。

1. 配列・オブジェクトの結合に使用する

let arr = [1, 2]
console.log([...arr, 3, 4, 5]) // [1, 2, 3, 4, 5]

let a = {"a": 1}
let b = {...a, "b": 2}
console.log(b) // {"a": 1, "b": 2}

2. 可変長引数として使用する

function func(a, ...b) {
  console.log(a) // 1
  console.log(b) // [2, 3]
}
func(1, 2, 3)

3. 分割代入で使用する

let arr = [1, 2, 3]
let [a, ...b] = arr
console.log(b) // [2, 3]