LoginSignup
110
122

More than 5 years have passed since last update.

2015~2017年のECMAScript コンプリートガイド

Last updated at Posted at 2018-02-16

以下は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

アロー関数は、単純で簡潔な見た目に関数を書ける機能です。

before
    const foo = function foo() {
      //...
    }

上記の関数を下のように書けます。

after
    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でようやく手に入れました。

before
    setTimeout(function() {
      console.log('I promised to run after 1s')
      setTimeout(function() {
        console.log('I promised to run after 2s')
      }, 1000)
    }, 1000)

Promiseを使うと以下のように書き換えられます。

after
    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は以下のような全く新しいパラダイムのプログラミングが可能になります。
・双方向の通信
・プログラムがフリーズしない無限ループ

以下にジェネレータの例を示します。

after
    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のプログラミング作法には大きな変化がありました。

現在では、他のオブジェクト指向プログラミング言語と同じように継承を容易に扱うことが可能です。

after
    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は次のように宣言します。

after
    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 ...と書きます。

after
    import * from 'mymodule'
    import React from 'react'
    import { React, Component } from 'react'
    import React as MyLibrary from 'react'

Exporting modules

他モジュールにエクスポートする際は、exportキーワードを使用します。

after
    export var foo = 2
    export function bar() { /* ... */ }

Multiline strings

ES2015では、文字列型の定義としてバックティックが新規導入されました。
バックティックは複数行にわたる文字列を定義することが可能です。

after
    const str = `One
    Two
    Three`

以前はこう書くしかありませんでした。

before
    var str = 'One\n' +
    'Two\n' +
    'Three'

Template Literals

テンプレートリテラルは、文字列に式を埋め込んで変数展開することができる記法です。

after
    const var = 'test'
    const string = `something ${var}` //something test

より複雑な式も書くことができます。

after
    const string = `something ${1 + 2 + 3}`
    const string2 = `something ${foo() ? 'x' : 'y' }`

Default parameters

関数にデフォルト引数を書けるようになりました。

after
    const foo = function(index = 0, testing = true) { /* ... */ }
    foo()

Destructuring assignments

オブジェクトの分割代入を使って変数に代入することができます。

after
    const person = {
      firstName: 'Tom',
      lastName: 'Cruise',
      actor: true,
      age: 54, //made up
    }

    const {firstName: name, age} = person

nameageにはpersonの該当値が入っています。

この構文は配列でも動作します。

after
    const a = [1,2,3,4,5]
    [first, second, , , fifth] = a

Enhanced Object Literals

オブジェクトリテラルは、ES2015で大きく進化しました。

Simpler syntax to include variables

before
    const something = 'y'
    const x = {
      something: something
    }

変数名と値が同じ場合は省略できます。

after
    const something = 'y'
    const x = {
      something
    }

Prototype

プロトタイプは__proto__プロパティで参照可能です。

after
    const anObject = { y: 'y' }
    const x = {
      __proto__: anObject
    }

super()

after
    const anObject = { y: 'y', test: () => 'zoo' }
    const x = {
      __proto__: anObject,
      test() {
        return super.test() + 'x'
      }
    }
    x.test() //zoox

Dynamic properties

after
    const x = {
      ['a' + '_' + 'b']: 'z'
    }
    x.a_b //z

For-of loop

ES5では2009年にforEachループが導入されました。
幸いなことにforのように壊れた仕様ではありません。

ES2015ではforEachの良識と破壊力を併せ持ったfor-ofループが導入されました。

after
    // イテレータ
    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なので、真偽評価が行えません。

mistake
    if (![1,2].indexOf(3)) {
      console.log('Not found')
    }

ES7で可能になりました。

after
    if (![1,2].includes(3)) {
      console.log('Not found')
    }

Exponentiation Operator

指数演算子**はMath.pow()と同じですが、関数ではなく言語構造です。

after
    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()メソッドが追加されました。

after
    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()

オブジェクト自身が持つ全プロパティの値の配列を返します。

after
    const person = { name: 'Fred', age: 87 }
    Object.values(person) // ['Fred', 87]

配列にも使えます。

after
    const people = ['Fred', 'Tony']
    Object.values(people) // ['Fred', 'Tony']

Object.entries()

オブジェクト自身が持つ全プロパティのキーと値のペアの配列を返します。

after
    const person = { name: 'Fred', age: 87 }
    Object.entries(person) // [['name', 'Fred'], ['age', 87]]

もちろん配列にも使えます。

after
    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は正しくコピーされません。

before
    const person1 = {
        set name(newName) {
            console.log(newName)
        }
    }

    const person2 = {}
    Object.assign(person2, person1)

以下であれば正しく動作します。

after
    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

関数宣言、および関数呼び出しで末尾カンマが使えるようになります。

after
    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

非同期関数は以下のように記述します。

after
    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より遙かに簡潔になります。

after
    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のあたりがよくわからなかった。

110
122
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
110
122