以下はTHE COMPLETE ECMASCRIPT 2015-2017 GUIDEの日本語訳です。
THE COMPLETE ECMASCRIPT 2015-2017 GUIDE
Introduction
JavaScriptの記事を読むたびに、必ず以下のような用語が目に入ってきます。
・ES3
・ES5
・ES6
・ES7
・ES8
・ES2015
・ES2016
・ES2017
・ECMAScript 2017
・ECMAScript 2016
・ECMAScript 2015
それぞれどういう意味なのでしょうか。
これらは全て、ECMAScriptという技術標準のことを指しています。
ECMAScriptはJavaScriptのベースとなっている技術標準であり、しばしばESと短縮して呼ばれます。
JavaScript以外にもECMAScriptを実装している言語は存在します。
・ActionScript Flashに使われているが、Flashの廃止に伴い人気は下降中
・JScript かつてNetscapeしかJavaScriptをサポートしていなかったころ、Microsoft Internet Explorerがサポートしていた独自のスクリプト
もちろんJavaScriptがES最大の普及例です。
何故このような変な名前なのでしょうか。
Ecma International
というスイスの標準化団体が存在し、国際標準の定義を行っています。
JavaScriptは、作成された後にNetscapeとSun MicrosystemsによってEcmaに提出され、ECMA-262
通称ECMAScript
という名前が付けられました。
Wikipediaの記述によると、何故この名前になったかはMicrosoftとの法的問題やブランディングのためで、NetscapeとSun Microsystemsのプレスリリースを読めば分かるかもしれません。
IE9以降MicrosotftはJScriptという名前を使わず、JavaScriptという名称を使い始めました。
従って201x年現在、ECMAScriptを実装している唯一の言語はJavaScriptとなります。
Current ECMAScript version
執筆時点でのECMAScriptのバージョンはES2017(ES8)で、2017年6月にリリースされました。
When is the next version coming out?
歴史的にJavaScriptは6月に標準化されてきたので、ECMAScript 2018(ES2018、ES9)は2018年6月にリリースされると思われますが、これはあくまで推測です。
What is TC39
TC39とは、JavaScriptを発展させる委員会です。
TC39にはMozilla、Google、Facebook、Apple、Microsoft、Intel、PayPal、SalesForceなど、JavaScriptおよびブラウザに関わる多くの企業が参加しています。
JavaScriptに対する全ての提案は、様々な段階を経てから標準となります。
ES Versions
ESのバージョンは、時には通し番号だったり時には年だったりして混乱しがちです。
ES2015以前は、ECMAScriptのバージョンはEditionで呼ばれていました。
従って、ES5は2009年に公開されたECMAScriptバージョンの正式名称です。
その次のES2015では、正式名称が途中でES6からES2015に変更されたのですが、この変更が遅れたために、未だにES6と呼ばれることが多々あります。
コミュニティでは名前を正式名称で呼ぶことにしていますが、一般的にはいまだにEditionで呼ばれます。
表にすると多少はわかりやすくなるでしょう。
Edition | 正式名称 | リリース |
---|---|---|
ES8 | ES2017 | 2017年6月 |
ES7 | ES2016 | 2016年6月 |
ES6 | ES2015 | 2015年6月 |
ES5.1 | ES5.1 | 2011年6月 |
ES5 | ES5 | 2009年12月 |
ES4 | ES4 | 廃棄 |
ES3 | ES3 | 1999年12月 |
ES2 | ES2 | 1998年6月 |
ES1 | ES1 | 1997年6月 |
ES.Next
ES.Nextは、常にJavaScriptの次のバージョンを表します。
よって、執筆時点ではES8がリリースバージョンで、ES9がES.Nextになります。
ES2015 aka ES6
ECMAScript 2015(ES6)は、ECMAScript標準の基本となるバージョンです。
それ以前の最新バージョンであるECMAScript 5.1から4年後に発行され、名称もEditionから年表記に切り替えられました。
よって、(皆がそう呼んでいますが)ES6と呼んではならず、ES2015が正しい名前です。
リリースまでに長い期間があったため、このリリースでは、新機能やJavaScript開発のベストプラクティスの変更など、重要な変更が多く行われています。
どれほどの変化があったかを簡単に表すと、ES2015の仕様書はそれ以前の250ページから600ページに増加しました。
ES2015の最も重要な変更には、以下のようなものがあります。
・Arrow functions
・Promises
・Generators
・let and const
・Classes
・Modules
・Multiline strings
・Template literals
・Default parameters
・Destructuring assignment
・Enhanced object literals
Arrow Functions
アロー関数は、単純で簡潔な見た目に関数を書ける機能です。
const foo = function foo() {
//...
}
上記の関数を下のように書けます。
const foo = () => {
//...
}
関数本体がワンライナーで終わる場合は、次のようになります。
const foo = () => doSomething()
引数がある場合は以下のようになります。
const foo = param => doSomething(param)
これは後方互換性のある変更で、元々のfunctionはこれまでと同じく使用できます。
A new this scope
アロー関数におけるthisのスコープは、その外側を継承します。
元のfunctionでは、呼び出し元の関数によってthisが変わるという厄介な問題がありましたが、アロー関数ではこの問題が解消されます。
var that = this
を書く必要はもうありません。
Promises
かの有名なコールバック地獄を、Promiseで排除することが可能になりました。
しかしながら、Promiseの扱いはまだ少し複雑です。
ES2017ではasyncというもっと良い方法で解消されています。
JavaScriptは、ES2015以前に開発者が使っていた多くのライブラリ(jQuery、q、deferred.js、vow等々)と同等の技術を、Promiseでようやく手に入れました。
setTimeout(function() {
console.log('I promised to run after 1s')
setTimeout(function() {
console.log('I promised to run after 2s')
}, 1000)
}, 1000)
Promiseを使うと以下のように書き換えられます。
const wait = () => new Promise((resolve, reject) => {
setTimeout(resolve, 1000)
})
wait().then(() => {
console.log('I promised to run after 1s')
return wait()
})
.then(() => console.log('I promised to run after 2s'))
Generators
ジェネレータは、実行中に一次停止して後から再開できる特別な関数で、止まってる間に他のコードを実行することができます。
ジェネレータは待機命令を受け取ったら、キューに積まれている次のコードを実行します。
そして待っているコードが完了したら、再び実行を再開します。
一次停止は単一の簡単なキーワード、yield
で実行されます。
ジェネレータはyieldを受け取ると実行を停止します。
ジェネレータは複数のyieldを含めることができ、何度でも途中停止することができます。
ジェネレータは*function
と表記しますが、これはCやC++、Go等で使われる間接参照演算子とは何の関係もありません。
ジェネレータにより、JavaScriptは以下のような全く新しいパラダイムのプログラミングが可能になります。
・双方向の通信
・プログラムがフリーズしない無限ループ
以下にジェネレータの例を示します。
function *calculator(input) {
var doubleThat = 2 * (yield (input / 2))
var another = yield (doubleThat)
return (input * doubleThat * another)
}
使い方としては、まず初期化します。
const calc = calculator(10)
次に、ジェネレータに対してイテレーションを開始します。
calc.next()
これによってイテレータが開始され、コードは以下のObjectを返します。
{
done: false
value: 5
}
何が起きたかというと、コンストラクタ引数10で関数*calculator
が起動します。
この時点では関数の中身は実行されません。
次にnextで関数が実行され、最初のyieldに達するまで進み、そしてyieldの中身であるinput/2
、すなわち5を返します。
従って、この時点でのvalueは5になり、そして関数はここで一時停止します。
次にイテレータに値7を渡して呼び出しましょう。
calc.next(7)
返り値は以下のようになります。
{
done: false
value: 14
}
yieldの値はinput/2
であるように見えますが、これは実際は最初のyieldの返り値です。
関数に戻った際には、新しい引数の7がyieldの値として使われます。
そして2番目のyieldまで到達し、doubleThatの値をyieldします。
返り値は14となります。
さて次の、そして最後のイテレータでは100を与えましょう。
calc.next(7) // たぶん100の間違い
返り値はこうなります。
{
done: true
value: 14000
}
yieldが見つからなくなるとイテレータの状態はdoneになり、最終的に(input * doubleThat * another)
の計算結果、今回であれば10 * 14 * 100
の14000を返すことになります。
let and const
varは歴史的経緯により関数スコープになっています。
新しくブロックスコープの変数宣言としてletが追加されました。
forやif、{}
等でブロックを作ると、letで定義した変数はその外に出て行くことはできません。
一方varは関数の頭まで巻き上げられてしまいます。
constはletと似ていますが、再代入不可能です。
今後のJavaScriptにvarは不要で、letとconstがその代わりになります。
特にconstは人気で、今日では幅広く使われています。
Classes
従来のJavaScriptはプロトタイプベース唯一の主流言語であり、クラスベースの他の言語からJavaScriptに移行したプログラマは困惑しがちでした。
ES2015でようやく導入されたクラスはただのシンタックスシュガーに過ぎませんが、JavaScriptのプログラミング作法には大きな変化がありました。
現在では、他のオブジェクト指向プログラミング言語と同じように継承を容易に扱うことが可能です。
class Person {
constructor(name) {
this.name = name
}
hello() {
return 'Hello, I am ' + this.name + '.'
}
}
class Actor extends Person {
hello() {
return super.hello() + ' I am an actor.'
}
}
var tomCruise = new Actor('Tom Cruise')
tomCruise.hello() // Hello, I am Tom Cruise. I am an actor.
クラス変数の宣言は必要ありませんが、コンストラクタで変数の初期化を行う必要があります。
Constructor
クラスには、newして初期化したときに呼び出されるconstructorという特殊なメソッドがあります。
Super
親クラスのメソッドはsuper()
で参照できます。
Getters and setters
プロパティのsetter/getterは次のように宣言します。
class Person {
get fullName() {
return `${this.firstName} ${this.lastName}`
}
}
set age(years) {
this.theAge = years
}
}
Modules
ES2015が出る前は、少なくとも3種類のモジュール管理標準が存在しており、コミュニティは分断されていました。
・AMD
・RequireJS
・CommonJS
ES2015でようやく共通フォーマットが導入されました。
Importing modules
インポートはimport ... from ...
と書きます。
import * from 'mymodule'
import React from 'react'
import { React, Component } from 'react'
import React as MyLibrary from 'react'
Exporting modules
他モジュールにエクスポートする際は、export
キーワードを使用します。
export var foo = 2
export function bar() { /* ... */ }
Multiline strings
ES2015では、文字列型の定義としてバックティックが新規導入されました。
バックティックは複数行にわたる文字列を定義することが可能です。
const str = `One
Two
Three`
以前はこう書くしかありませんでした。
var str = 'One\n' +
'Two\n' +
'Three'
Template Literals
テンプレートリテラルは、文字列に式を埋め込んで変数展開することができる記法です。
const var = 'test'
const string = `something ${var}` //something test
より複雑な式も書くことができます。
const string = `something ${1 + 2 + 3}`
const string2 = `something ${foo() ? 'x' : 'y' }`
Default parameters
関数にデフォルト引数を書けるようになりました。
const foo = function(index = 0, testing = true) { /* ... */ }
foo()
Destructuring assignments
オブジェクトの分割代入を使って変数に代入することができます。
const person = {
firstName: 'Tom',
lastName: 'Cruise',
actor: true,
age: 54, //made up
}
const {firstName: name, age} = person
name
とage
にはpersonの該当値が入っています。
この構文は配列でも動作します。
const a = [1,2,3,4,5]
[first, second, , , fifth] = a
Enhanced Object Literals
オブジェクトリテラルは、ES2015で大きく進化しました。
Simpler syntax to include variables
const something = 'y'
const x = {
something: something
}
変数名と値が同じ場合は省略できます。
const something = 'y'
const x = {
something
}
Prototype
プロトタイプは__proto__
プロパティで参照可能です。
const anObject = { y: 'y' }
const x = {
__proto__: anObject
}
super()
const anObject = { y: 'y', test: () => 'zoo' }
const x = {
__proto__: anObject,
test() {
return super.test() + 'x'
}
}
x.test() //zoox
Dynamic properties
const x = {
['a' + '_' + 'b']: 'z'
}
x.a_b //z
For-of loop
ES5では2009年にforEachループが導入されました。
幸いなことにfor
のように壊れた仕様ではありません。
ES2015ではforEachの良識と破壊力を併せ持ったfor-of
ループが導入されました。
// イテレータ
for (const v of ['a', 'b', 'c']) {
console.log(v);
}
// キーも使う
for (const [i, v] of ['a', 'b', 'c'].entries()) {
console.log(i, v);
}
ES2016 aka ES7
正式にはECMAScript 2016として知られているES7は、2016年6月にリリースされました。
ES6と異なり、ES7の変更は小規模で、2機能の追加にとどまります。
Array.prototype.includes()
配列に要素が含まれているかを確認するための、よりわかりやすい要素が導入されました。
ES6以下では、配列に要素が含まれているかを確認する場合はindexOfを使います。
indexOfは要素が存在しない場合は-1が返されます。
-1はtruthyなので、真偽評価が行えません。
if (![1,2].indexOf(3)) {
console.log('Not found')
}
ES7で可能になりました。
if (![1,2].includes(3)) {
console.log('Not found')
}
Exponentiation Operator
指数演算子**はMath.pow()
と同じですが、関数ではなく言語構造です。
Math.pow(4, 2) == 4 ** 2
この機能は数学向けアプリを実装するのに最適です。
指数演算子**は、Python/Ruby/MATLAB/Lua/Perl等多くの言語に実装されています。
ES2017 aka ES8
ECMAScript 2017、 ECMA-262 Standardのエディション8(一般的にはES2017とかES8と呼ばれる)は、2017年6月にリリースされました。
ES8もES6に比べると小規模な変更ですが、非常に便利な機能が幾つも追加されています。
String padding
パディングの目的は、文字列長を揃えるために文字を追加することです。
ES2017では、String.padStart()
とpadEnd()
メソッドが追加されました。
padStart(targetLength [, padString])
padEnd(targetLength [, padString])
以下は使用例です。
'test'.padStart(4) // 'test'
'test'.padStart(5) // ' test'
'test'.padStart(8) // ' test'
'test'.padStart(8, 'abcd') // 'abcdtest'
'test'.padEnd(4) // 'test'
'test'.padEnd(5) // 'test '
'test'.padEnd(8) // 'test '
'test'.padEnd(8, 'abcd') // 'testabcd'
Object.values()
オブジェクト自身が持つ全プロパティの値の配列を返します。
const person = { name: 'Fred', age: 87 }
Object.values(person) // ['Fred', 87]
配列にも使えます。
const people = ['Fred', 'Tony']
Object.values(people) // ['Fred', 'Tony']
Object.entries()
オブジェクト自身が持つ全プロパティのキーと値のペアの配列を返します。
const person = { name: 'Fred', age: 87 }
Object.entries(person) // [['name', 'Fred'], ['age', 87]]
もちろん配列にも使えます。
const people = ['Fred', 'Tony']
Object.entries(people) // [['0', 'Fred'], ['1', 'Tony']]
getOwnPropertyDescriptors()
このメソッドは、オブジェクト自身が持つ全プロパティのプロパティディスクリプタを返します。
JavaScriptのオブジェクトのプロパティはそれぞれディスクリプタを持っています。
ディスクリプタとはプロパティの持つ属性の一覧で、要するにプロパティの定義そのものです。
・value プロパティの値
・writable プロパティが書き換え可能であるか
・get: プロパティ読み込み時に呼ばれる関数。getter
・set: プロパティ書き込み時に呼ばれる関数。setter
・configurable falseの場合ディスクリプタの変更が不可
・enumerable 列挙可能か(for inで出てくるか否か)
Object.getOwnPropertyDescriptors(obj)
はオブジェクトを受け取り、プロパティディスクリプタの一覧を返します。
In what way is this useful?
ES2015では、ひとつ以上のオブジェクトから全てのプロパティの値だけをコピーするObject.assign()
が追加されました。
しかし、デフォルト以外の属性が設定されていた場合は正しくコピーされないという問題があります。
例えば、person1は正しくコピーされません。
const person1 = {
set name(newName) {
console.log(newName)
}
}
const person2 = {}
Object.assign(person2, person1)
以下であれば正しく動作します。
const person3 = {}
Object.defineProperties(person3,
Object.getOwnPropertyDescriptors(person1))
動作は以下のようになります。
person1.name = 'x' // "x"
person2.name = 'x' //
person3.name = 'x' // "x"
person2ではsetterが動いていません。
Object.create()
を使ったシャローコピーでも同じ現象が発生します。
Trailing commas
関数宣言、および関数呼び出しで末尾カンマが使えるようになります。
const doSomething = (var1, var2,) => {
//...
}
doSomething('test2', 'test2',)
これにより、行頭にカンマを書くという悪癖を終わらせることができます。
Async functions
ES2017では非同期関数が導入されました。
ES2017で最も重要な変更点です。
非同期関数はPromiseとジェネレータと共に使うことで、Promiseまわりの回りくどい構文を劇的に改善することができます。
Why they are useful
非同期関数はPromiseより高いレベルの抽象化です。
ES2015において、非同期処理の複雑さを解決する目的でPromiseが導入されました。
しかしその後の2年間において、Promiseでは最終的な解決にならないことが明らかになりました。
Promiseは確かにコールバック地獄を解決しましたが、代わりにPromise自身の複雑さ及びPromise構文の複雑さが入り込んできただけでした。
もっと簡潔に記述できる非同期処理が必要でした。
A quick example
非同期関数は以下のように記述します。
function doSomethingAsync() {
return new Promise((resolve) => {
setTimeout(() => resolve('I did something'), 3000)
})
}
async function doSomething() {
console.log(await doSomethingAsync())
}
console.log('Before')
doSomething()
console.log('After')
コンソールには以下のように出力されます。
Before
After
I did something //after 3s
Multiple async functions in serie
非同期関数は簡単に連鎖させることができ、構文もPromiseより遙かに簡潔になります。
function promiseToDoSomething() {
return new Promise((resolve)=>{
setTimeout(() => resolve('I did something'), 10000)
})
}
async function watchOverSomeoneDoingSomething() {
const something = await promiseToDoSomething()
return something + ' and I watched'
}
async function watchOverSomeoneWatchingSomeoneDoingSomething() {
const something = await watchOverSomeoneDoingSomething()
return something + ' and I watched as well'
}
watchOverSomeoneWatchingSomeoneDoingSomething().then((res) => {
console.log(res)
})
Shared Memory and Atomics
WebWorkerを使うと、ブラウザ上でマルチスレッドのプログラミングが可能になります。
メッセージはイベントを使ってやりとりします。
ES2017では、SharedArrayBufferを使ってWebWorkerとアプリ開発者でメモリを共有できます。
相手側の共有メモリへの書き込みがいつ行われるかは分からないので、読み取り中に書き込み動作が割り込まれないようにAtomicsオブジェクトが導入されました。
詳細については仕様書に記載されています。
奥付
2018/02/01 Flavio Copes著。
React、Rudex、PWA、ES6、ES7、ES8、Webpack、GraphQL、Babel、ServiceWorker、Fetch、PUSH通知等についてまとめたModern Web Developmentって本を書いたよ!
感想
Object.values()
って何に使うの???
Atomicsのあたりがよくわからなかった。