概要
JavaScript には年々新しいシンタックスが追加されている。それらを活用すればコードが簡潔になる、読みやすくなる、バグを回避できるなどのメリットがある。IE などのブラウザでは使用できないこともあるが、babel などの transpiler や polyfill を使用することで解決できる(そもそも「モダン」でないブラウザは使用されるべきではないが)。以下に最近の JavaScript (正確には ECMAScript)のバージョンで追加されたシンタックスを紹介する。それ以外の method (Object.entries()
など) などについてはここでは紹介しない。
ECMAScript 2015 (ES6)
ECMASCript 2015 では非常に多くのシンタックスが追加され、それらをフルに使いこなせばそれ以前のコードと見比べると別言語にすら見えると感じる。const と let によりバグの少ないコードが書きやすくなり、arrow function は functional programming をしやすくし、class の導入は Java などのオブジェクト指向言語に慣れている人たちに喜ばれるものと思われる。また、object や array の作成、分解がしやすくなるよう様々な構文が追加された。
const & let
const
やlet
で変数を宣言できるようになった。var
とは異なりconst
やlet
で宣言された変数は block scope1 である。const
で宣言された変数は再代入ができず、let
は再代入ができる。
最近では、基本的にconst
を使って変数を宣言し、再代入をする必要があるときのみlet
を使い、var
は使わないというスタイルが推奨されることが多い2。
// 再宣言
var a = 3
var a = 4 // エラーにならない
let b = 3
let b = 4 // Uncaught SyntaxError
const c = 3
const c = 4 // Uncaught SyntaxError
// スコープ
if (true) {
var a = 3
}
console.log(a) // 3
if (true) {
let b = 3
}
console.log(b) // Uncaught ReferenceError
// 再代入
var a = 3
a = 3
let b = 3
b = 3
const c = 3
c = 3 // Uncaught TypeError
Arrow Function
function expression (function () {}
) をより短い形で書ける。しかし機能的な違いもあり、function expression 内のthis
の値は呼び出され方によって変わるのに対し、arrow function 内のthis
は外のthis
と常に同じとなる。この性質は、例えばオブジェクト A のメソッドの中で作った function 内から A を参照したい時に便利である。ちなみに arrow という名前は=>
が文字通り arrow(矢印)に見えるからである。他言語ではラムダ式とも呼ばれる。
Before
const f = function(x) {
return x * 2
}
After
const f = x => {
return x * 2
}
// return文だけの場合 {} と return を省略できる
const f = x => x * 2
Template Literal
クオート("
や'
)ではなくバックティック(`
)で囲むことにより、string の literal に直接変数や式の値を埋め込んだり、複数行の文字列を作ることができる。複数行に渡る場合は、インデントも文字列に含まれることに気をつける必要がある。
const x = 3
const equation = `2x = ${x * 2}` // "2x = 6"
const program = `function () {
return 3
}` // "function () {\n return 3\n}"
for ... of
Array などの iterable3な object について、それに含まれる値を一つづつ処理できる。
let sum = 0
for (const x of [33, 67]) {
sum += x
}
console.log(sum) // 100
Default Prameters
function の parameter(引数)のデフォルトの値を指定できる。該当の引数が指定されないとき、あるいはundefined
が指定されたとき、デフォルト値が代わりに使用される。
Before
function double(x) {
if (x === undefined) {
x = 0
}
return x * 2
}
After
function double(x = 0) {
return x * 2
}
Rest Parameters
function の引数で、「残り(rest)すべて」を配列として受け取ることができる。他言語でいう可変長引数のシンタックスに似ている(JS の function はいつでも任意個の引数を受け取れるという点で特殊ではあるが)。
function f(a, b, ...rest) {
console.log(rest)
}
f(1, 2, 3, 4, 5) // [3, 4, 5]
Destructuring
Array の要素を取り出して変数に取り出す時に短く書ける。"structure" は組み立てる、構造を作るという意味で、意味を逆にする接頭辞 "de-" がつくことにより、構造をもつものをバラバラに分解する、という意味になる。
Before
const arr = [10, 20, 30, 40]
const first = arr[0]
const second = arr[1]
const rest = arr.slice(2)
After
const arr = [10, 20, 30, 40]
const [first, second, ...rest] = arr
Property Definition / プロパティ定義
オブジェクトの property の literal でキーと、値として指定された変数名が一致するとき短く書ける。
After
const a = 1
const b = 2
const o = {
a: a,
b: b,
}
After
const a = 1
const b = 2
const o = {
a,
b,
}
Spread Syntax
rest parameters は受け取る側で複数のものをまとめるものであるのに対し、spread(広げる、ばら撒く) syntax は渡す側で array などの iterable3をバラバラにするものである。
function middle(x, y, z) {
return y
}
const arr = [2, 3]
console.log(middle(1, ...arr)) // 2
const arr2 = [1, ...arr] // [1, 2, 3]
object の literal では iterable ではなく object を spread することができる(ECMAScript 2018 で追加)。
const o1 = { a: 1, b: 2 }
const o2 = { c: 3, d: 4 }
const o3 = {
...o1,
...o2,
e: 5,
}
console.log(o3) // { a: 1, b: 2, c: 3, d: 4, e: 5 }
Method Definition / メソッド定義
オブジェクトの property として function expression を書くときに短く書ける。
Before
{
bla: function (x) {
// ...
}
}
After
{
bla(x) {
// ...
}
}
Computed property name / 動的なプロパティ名
オブジェクトの literal にて key に expression を指定できる。
Before
const key = 'foo'
const o = {}
o[key] = 1
After
const key = 'foo'
const o = {
[key]: 1,
}
Class / クラス
オブジェクト指向言語でお馴染みのクラスを使用できるようになった。しかし内部的には以前からある prototype が用いられていることに注意する必要がある。クラスについては説明すべき機能や性質が多すぎるため、ここでは割愛する。
class Vector2D {
// コンストラクタ
constructor(x, y) {
this.x = x
this.y = y
}
// 長さを返すメソッド
magnitude() {
const { x, y } = this
return Math.sqrt(x ** 2 + y ** 2)
}
}
const vec = new Vector2D(3, 4)
console.log(vec.magnitude()) // 5
Generator Function
iterable を生成する function を簡単に作るためのシンタックスである。リクエストされるたびに一つづつ値を返すため、うまく使えばメモリ効率のよいプログラムとなる。通常の function expression あるいは function declaration に「*
」をつけることで、その中でyield
を使用できるようになり、yield
したものがアウトプットされる。
// python の range と同等
function* range(end) {
for (let i = 0; i < end; i++) {
yield i
}
}
for (const x of range(5)) {
console.log(x)
}
// 0
// 1
// 2
// 3
// 4
ECMAScript 2017 (ES8)
ECMAScript 2017 で追加された主なシンタックスは async/await のみである。
async/await
Promise
ベースの非同期処理を、あたかも同期処理であるかのように書くことができ、これにより非同期処理が非常に書きやすくなる。async
をつけた function 内ではawait
が使えるようになり、await
の右においたPromise
が resolve するまで待つ4ようになる。Promise
がreject
した場合はエラーが発生しtry-catch
でキャッチすることができる。
async function fetchPosts() {
const posts = await fetch('/posts').then(res => res.json())
return posts
}
async function logPosts() {
const posts = await fetchPosts()
console.log(posts)
}
ECMAScript 2018
Rest/Spread Properties
ECMAScript 2016 では Array の Spread Syntax や Rest Syntax が追加されたが、これは Object の property についての同様のシンタックスである。
Before
const person = {
age: 10,
name: 'John',
job: 'programmer',
weight: '60kg',
}
const age = person.age
const name = person.name
const rest = {
job: person.job,
weight: person.weight,
}
After
const person = {
age: 10,
name: 'John',
job: 'programmer',
weight: '60kg',
}
const { age, name, ...rest } = person
これらは function expression や arrow function の parameter でも使える。
const extractName = ({ name }) => name
console.log(extractName({ name: 'John' })) // John
Async Generator Function & for await ... of
for ... of は iterable の要素を一つづつ処理するものだが、for await ... of は async iterable の要素を処理する。await
と同様に async function 内でのみ使用できる。直感的には async iterable とは、iterable とは異なり「非同期に」要素を一つづつ取り出せるものである。フォーマルに言うとSymbol.asyncIterator
プロパティをもつオブジェクトである。
async iterable を手軽に作るシンタックスとして async generator function がある。簡単に言えば generator function の非同期バージョンであり、リクエストされるたびに yield のところまで実行される。
両者は stream (非同期に任意回数、値を発するデータ構造; 対してPromise
は 1 回のみ) の処理に便利である。詳しくは以下のページを参照されたい。
// time ミリ秒待つ Promise
function sleep(time) {
return new Promise(resolve => {
setTimeout(() => resolve(), time)
})
}
// 間隔 interval で 0 から end まで出力する
async function* rangeInterval(end, interval) {
for (let i = 0; i < end; i++) {
await sleep(interval)
yield i
}
}
!(async () => {
for await (const x of rangeInterval(5, 1000)) {
console.log(x)
}
})()
// 1秒間隔で以下が出力される
// 0
// 1
// 2
// 3
// 4
-
block scope とは、ブロック(
for
文、while
文、if
文、function () { ... }
、{ ... }
など)の中で宣言されたとき、その中でしか使えない(正確にはその変数名を参照できない)という性質である。逆にvar
で宣言された変数は function scope であり、それが宣言された function 内でどこでも参照できる。 ↩ -
iterable とは、直感的には「値を一つづつ取り出せる」ような object であり、Array がその代表例である。形式的な定義は
[Symbol.Iterator]
プロパティを持っているオブジェクトである。詳しくはMDNを参照されたい。 ↩ ↩2 -
待つといっても
while(true){}
のようにそこで JavaScript の処理が止まるわけではなく、他にやることがあれば実行される。実はawait x
と書くことは、Promise
に対して.then()
でコールバックを設定することと本質的に同じである。 ↩