2016年から2019年までのJavaScriptの全て

以下はAlbertoM( [Webサイト](https://inspiredwebdev.com/) / [Twitter](https://twitter.com/montalesi) / [GitHub](https://github.com/AlbertoMontalesi) / [dev.to](https://dev.to/albertomontalesi) )による記事、[Everything you need to know from ES2016 to ES2019](https://inspiredwebdev.com/everything-from-es-2016-to-es-2019)の日本語訳です。


# Everything you need to know from ES2016 to ES2019

JavaScriptは絶え間なく進化し続けている言語であり、この数年で多くの新機能がECMAScriptの仕様に追加されました。

この記事は私の著書[Complete Guide to Modern JavaScript](https://inspiredwebdev.com/store)からの抜粋であり、そしてこの本はES2016・ES2017・ES2018・ES2019の新しい機能について解説しています。

**記事の最後に、全てを要約したチートシートを用意しています。**


## Everything new in ES2016

ES2016で追加された機能はわずか二つです。
・Array.prototype.includes()
・指数演算子

## Array.prototype.includes()

`includes()`メソッドは、配列に特定の値が含まれる場合はtrueを返し、含まれていなければfalseを返します。

```js
let array = [1,2,4,5];

array.includes(2); // true
array.includes(3); // false
```

### Combine includes() with fromIndex

検索を開始するインデックスを`includes()`に渡すことができます。
デフォルトは0で、負の値を渡すこともできます。
最初の値は検索する要素で、2番目の値は開始インデックスです。

```js
let array = [1,3,5,7,9,11];

array.includes(3,1); // true 1番目つまり最初から検索開始する
array.includes(5,4); // false

array.includes(1,-1); // false 最後尾から検索開始する
array.includes(11,-3); // true
```

`array.includes(5,4);`はfalseを返します。
4番目の値から検索を開始するため、2番目にある値5は検索範囲外だからです。

`array.includes(1,-1);`はfalseを返します。
インデックス`-1`は配列の最後の要素という意味で、そこから検索を開始するためです。

`array.includes(11,-3);`はtrueを返します。
インデックス`-3`は配列の最後から3番目の要素という意味で、そこから検索を始めたため範囲内に11を発見しました。

## The exponential operator

ES2016より前は、以下のように書いていました。

```js
Math.pow(2,2); // 4
Math.pow(2,3); // 8
```

指数演算子の導入により、以下のように書けるようになりました。

```js
2**2; // 4
2**3; // 8
```

複数の演算を組み合わせる際に特に役立ちます。

```js
2**2**2; // 16
Math.pow(Math.pow(2,2),2); // 16
```

`Math.pow()`を使うと長く面倒な書式になります。
指数演算子を使うと、同じことをより早くよりクリーンに記述することができます。


## ES2017 string padding, Object.entries(), Object.values() and more

ES2017では、多くのクールな新機能が追加されました。
それらを以下に解説していきます。

## String padding (.padStart() and .padEnd())

文字列の先頭もしくは末尾にパディングを付けられます。

```js
"hello".padStart(6); // " hello"
"hello".padEnd(6); // "hello "
```

パディング値に6を指定したのに、スペースが1しか入らなかったのはなぜでしょう。
padStartとpadEndは規定文字数になるように穴埋めするからです。
上記例の場合、"hello"は5文字あるため、不足した1文字だけがパディングされました。

```js
"hi".padStart(10); // "        hi" 8文字埋められた
"welcome".padStart(10); // "   welcome" 4文字埋められた
```

### Right align with padStart

値を右揃えしたいときにもpadStartが使えます。

```js
const strings = ["short", "medium length", "very long string"];
const longestString = strings.sort(str => str.length).map(str => str.length)[0];
strings.forEach(str => console.log(str.padStart(longestString)));

// very long string
//    medium length
//            short
```

まず、最も長い文字列の長さを取得します。
次いですべての文字列にpadStartを適用し、文字列長を最も長い文字列に合わせました。

### Add a custom value to the padding

スペースだけではなく、任意の文字でパディングすることができます。

```js
"hello".padEnd(13," Alberto"); // "hello Alberto"
"1".padStart(3,0); // "001"
"99".padStart(3,0); // "099"
```


## Object.entries() and Object.values()

とりあえずObjectを作成します。

```js
const family = {
  father: "Jonathan Kent",
  mother: "Martha Kent",
  son: "Clark Kent",
}
```

かつてのJavaScriptは、プロパティに以下のようにアクセスしていました。

```js
Object.keys(family); // ["father", "mother", "son"]
family.father; // "Jonathan Kent"
```

`Object.keys()`は、プロパティのキーのみを返します。

ES2017では、プロパティにアクセスする方法が2種類増えました。

```js
Object.values(family); // ["Jonathan Kent", "Martha Kent", "Clark Kent"]

Object.entries(family);
// ["father", "Jonathan Kent"]
// ["mother", "Martha Kent"]
// ["son", "Clark Kent"]
```

`Object.values()`はプロパティ値のみの配列を返し、`Object.entries()`はキーと値の両方を含む配列の配列を返します。


## Object.getOwnPropertyDescriptors()

このメソッドは、オブジェクトの持つ全てのプロパティディスクリプタを返します。
ディスクリプタの属性はvalue・writable・get・set・configurable・enumerableです。

```js
const myObj = {
  name: "Alberto",
  age: 25,
  greet() {
    console.log("hello");
  },
}

Object.getOwnPropertyDescriptors(myObj);
// age:{value: 25, writable: true, enumerable: true, configurable: true}

// greet:{value: ƒ, writable: true, enumerable: true, configurable: true}

// name:{value: "Alberto", writable: true, enumerable: true, configurable: true}
```


## Trailing commas in function parameter lists and calls

これは、本当に小さな変更です。
しかしこれで、そのパラメータが最後であるかどうかをいちいち気にせずに末尾カンマを書けるようになりました。

```js
// 昔
const object = {
  prop1: "prop",
  prop2: "propop"
}

// 現在
const object = {
  prop1: "prop",
  prop2: "propop",
}
```

2番目のプロパティは最後にカンマが増えていることに注目してください。
入れなくてもエラーにはなりませんが、同僚やチームメイトの生活を楽にするためにも入れておくことをお勧めします。

```js
// カンマ入れない
const object = {
  prop1: "prop",
  prop2: "propop"
}

// 同僚が最終行をコピペしてプロパティを追加した
const object = {
  prop1: "prop",
  prop2: "propop"
  prop3: "propopop"
}

// 突然エラーが出るようになった
```


## Shared memory and Atomics

[MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Atomics)によると ([日本語版](https://developer.mozilla.org/ja/docs/Web/JavaScript/Reference/Global_Objects/Atomics))

> メモリーが共有されている場合、複数のスレッドがメモリー内の同じデータを読み書きできます。アトミック演算では、予測される値の書き込みと読み込みを保証するため、次の演算が開始される前に現在の演算が完了し、その演算が割り込まれないようにします。

Atomicはコンストラクタではありません。
プロパティとメソッドは全て静的であり、newしたりinvokeしたり関数として呼び出したりすることはできません。

Atomicが持つメソッドは以下のようなものがあります。
・ add / sub
・ and / or / xor
・ load / store

Atomicは、汎用固定長バイナリデータバッファ`SharedArrayBuffer`などで使用されます。
いくつかの例を見てみましょう。

## Atomics.add(), Atomics.sub(), Atomics.load() and Atomics.store()

```js
// SharedArrayBuffer作成
const buffer = new SharedArrayBuffer(16);
const uint8 = new Uint8Array(buffer);

// これに各計算する
uint8[0] = 10;

console.log(Atomics.add(uint8, 0, 5)); // 10

console.log(uint8[0]) // 15

console.log(Atomics.load(uint8,0)); // 15
```

最初の`Atomics.add()`は加算を行いますが、返り値は計算する前の値です。
その後`uint8[0]`を参照すると、addが実行されたあとなので値が15になっていることが確認できます。

配列値をAtomicに取得するには`Atomics.load()`を使い、第一引数が対象の配列、第二引数がインデックスです。

`Atomics.sub()`は`Atomics.add()`と同じ挙動で、減算を行います。

```js
// SharedArrayBuffer作成
const buffer = new SharedArrayBuffer(16);
const uint8 = new Uint8Array(buffer);

// これに各計算する
uint8[0] = 10;

console.log(Atomics.sub(uint8, 0, 5)); // 10

console.log(uint8[0]) // 5

console.log(Atomics.store(uint8,0,3)); // 3

console.log(Atomics.load(uint8,0)); // 3
```

`Atomics.sub()`を使って10から5を引きました。
計算自体の返り値は、`Atomics.add()`と同じく計算する前の値であり、すなわち10です。

次に`Atomics.store()`を使い特定の値、今回は配列の0番目のインデックスに3を登録しました。

`Atomics.store()`は渡した値をそのまま返します。
`Atomics.load()`すると、値は書き替えられた後なので5ではなく3になります。


## Atomics.and(), Atomics.or() and Atomics.xor()

これらはそれぞれAND、OR、XORのビット演算を行います。
ビット演算の詳細は[Wikipedia](https://en.wikipedia.org/wiki/Bitwise_operation)などで読むことができます。


## ES2017 Async and Await

ES2017では、`async/await`と呼ばれる新たなPromiseが導入されました。

## Promise review

その前に、まず普通のPromise構文を簡単に復習しましょう。

```js
// GitHubからユーザを取得
fetch('api.github.com/user/AlbertoMontalesi').then( res => {
  // 値をJSONで返す
  return res.json();
}).then(res => {
  // 全てが正常に動作したらここに来る
  console.log(res);
}).catch( err => {
  // エラーがあったらここに来る
  console.log(err);
})
```

GitHubからユーザを取得してコンソールに出力するだけの簡単な例です。

また別の例を見てみましょう。

```js
function walk(amount) {
  return new Promise((resolve,reject) => {
    if (amount < 500) {
      reject ("the value is too small");
    }
    setTimeout(() => resolve(`you walked for ${amount}ms`),amount);
  });
}

walk(1000).then(res => {
  console.log(res);
  return walk(500);
}).then(res => {
  console.log(res);
  return walk(700);
}).then(res => {
  console.log(res);
  return walk(800);
}).then(res => {
  console.log(res);
  return walk(100);
}).then(res => {
  console.log(res);
  return walk(400);
}).then(res => {
  console.log(res);
  return walk(600);
});

// you walked for 1000ms
// you walked for 500ms
// you walked for 700ms
// you walked for 800ms
// uncaught exception: the value is too small
```

## Async and Await

これを`async/await`で書き換えるとこうなります。

```js
function walk(amount) {
  return new Promise((resolve,reject) => {
    if (amount < 500) {
      reject ("the value is too small");
    }
    setTimeout(() => resolve(`you walked for ${amount}ms`),amount);
  });
}

// asyncなfunctionを作成
async function go() {
  // awaitがあれば終わるまで待つ
  const res = await walk(500);
  console.log(res);
  const res2 = await walk(900);
  console.log(res2);
  const res3 = await walk(600);
  console.log(res3);
  const res4 = await walk(700);
  console.log(res4);
  const res5 = await walk(400);
  console.log(res5);
  console.log("finished");
}

go();

// you walked for 500ms 
// you walked for 900ms 
// you walked for 600ms 
// you walked for 700ms 
// uncaught exception: the value is too small
```

非同期関数を作成するには、まず`async`キーワードを記載します。
このキーワードが入った関数はPromiseを返すようになります。
Promiseでない値を返そうとした場合、自動的にPromiseでラップされて返されます。
`await`キーワードは、`async`関数内でのみ機能します。
`await`を書くと、プログラムはPromiseが結果を返すまでそこで停止します。

`async`関数の外で`await`キーワードを使うとどうなるでしょうか。

```js
// asyncではない関数
function func() {
  let promise = Promise.resolve(1);
  let result = await promise; 
}
func();
// SyntaxError: await is only valid in async functions and async generators


// トップレベル
let response = Promise.resolve("hi");
let result = await response;
// SyntaxError: await is only valid in async functions and async generators
```

復習：`await`は、`async`関数内でのみ使用可能。


## Error handling

Promiseではエラーを`.catch()`でキャッチします。
これについては特に違いはありません。

```js
async function asyncFunc() {

  try {
    let response = await fetch('http:your-url');
  } catch(err) {
    console.log(err);
  }
}

asyncFunc(); // TypeError: failed to fetch
```

関数内でエラーハンドリングしていない場合は、以下のように書くこともできます。

```js
async function asyncFunc(){
  let response = await fetch('http:your-url');
}
asyncFunc(); // Uncaught (in promise) TypeError: Failed to fetch

asyncFunc().catch(console.log); // TypeError: Failed to fetch
```


## ES2018 Async Iteration and more?

ES2018で導入された機能も見ていきましょう。

## Rest / Spread for Objects

ES6 (ES2015) でスプレッド構文が導入されたことを覚えていますか？

```js
const veggie = ["tomato","cucumber","beans"];
const meat = ["pork","beef","chicken"];

const menu = [...veggie, "pasta", ...meat];
console.log(menu); // Array [ "tomato", "cucumber", "beans", "pasta", "pork", "beef", "chicken" ]
```

スプレッド構文にRestパラメータがオブジェクトに対しても使用可能になりました。

```js
let myObj = {
  a:1,
  b:3,
  c:5,
  d:8,
}

// zは残り全部
let { a, b, ...z } = myObj;
console.log(a);     // 1
console.log(b);     // 3
console.log(z);     // {c: 5, d: 8}

// スプレッド構文でクローン
let clone = { ...myObj };
console.log(clone); // {a: 1, b: 3, c: 5, d: 8}

myObj.e = 15;
console.log(clone) // {a: 1, b: 3, c: 5, d: 8}
console.log(myObj) // {a: 1, b: 3, c: 5, d: 8, e: 15}
```

スプレッド構文を使うとオブジェクトのクローンが簡単に作成できます。
元のオブジェクトを変更しても、クローンしたオブジェクトは変更されません。


## Asynchronous Iteration

非同期イテレータを用いて、データを非同期的に反復することができます。

[ドキュメント](https://github.com/tc39/proposal-async-iteration)によると、

> 非同期イテレータは、`next()`メソッドが`{ value, done }`のペアを返すこと以外、イテレータにそっくりです。

従って、`for-await-of`ループで反復処理してPromiseにすることができます。

```js
const iterables = [1,2,3];

async function test() {
    for await (const value of iterables) {
        console.log(value);
    }
}
test();
// 1
// 2
// 3
```

ループの実行中、`[Symbol.asyncIterator]()`を用いてデータソースから非同期イテレータを作成します。
次のループにアクセスするたびに、返ってきたPromiseを暗黙的にawaitします。


## Promise.prototype.finally()

Promiseが終了したときに呼び出されます。

```js
const myPromise = new Promise((resolve,reject) => {
  resolve();
})
myPromise
  .then( () => {
    console.log('still working');
  })
  .catch( () => {
    console.log('there was an error');
  })
  .finally(()=> {
    console.log('Done!');
  })
```

`.finally()`もPromiseを返すので、さらに`then`や`catch`を続けることも可能ですが、そこに渡ってくるPromiseはfinallyではなく元の値です。


```js
const myPromise = new Promise((resolve,reject) => {
  resolve();
})
myPromise
.then( () => {
    console.log('still working');
    return 'still working';
  })
  .finally(()=> {
    console.log('Done!');
    return 'Done!';
  })
  .then( res => {
    console.log(res); // still working
  })
```

見てのとおり、最後の`then`に渡ってくる`Promise`は、`finally`によるものではなく最初の`then`が作ったものです。


## RegExp features

正規表現の機能が4種類追加されました。

### s(dotAll) flag for regular expressions

`s`フラグが導入されました。
これにより、'.'が改行を含む任意の1文字に一致するようになります。

```js
/foo.bar/s.test('foo\nbar'); // true
```

### RegExp named capture groups

[ドキュメント](https://github.com/tc39/proposal-regexp-named-groups)によると、

> 番号付きキャプチャグループを使用して、正規表現が一致した文字列の特定の個所を参照することができます。
> キャプチャグループには一意の番号が割り当てられており、その番号で参照することができますが、これにより正規表現の把握とリファクタリングが難しくなります。
> 日付に一致する`/(\d{4})-(\d{2})-(\d{2})/`を例に取ると、どの番号が月に対応し、どの番号が日に対応しているかは、コードをよく調べてみないと理解できません。
> また月と日の順番を入れ替えたいとなったら、参照する番号の方まで書き換えなければなりません。
> ここでキャプチャグループに`(?<name>...)`構文を用いて、識別子`name`で参照することができるようになります。
> 日付の正規表現を`/(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u`と書けます。
> 各識別子は一意であり、`ECMAScript`の命名規則に従う必要があります。
> 名前付きキャプチャグループには、返り値のgroupsプロパティからアクセスすることができます。
> 以下の例のように、名前付きキャプチャグループと同時に、番号付きの参照も作成されます。

```js
let re = /(?<year>\d{4})-(?<month>\d{2})-(?<day>\d{2})/u;
let result = re.exec('2015-01-02');
// result.groups.year === '2015';
// result.groups.month === '01';
// result.groups.day === '02';

// result[0] === '2015-01-02';
// result[1] === '2015';
// result[2] === '01';
// result[3] === '02';

let {groups: {one, two}} = /^(?<one>.*):(?<two>.*)$/u.exec('foo:bar');
console.log(`one: ${one}, two: ${two}`); // one: foo, two: bar
```

### RegExp Lookbehind Assertions

[ドキュメント](https://github.com/tc39/proposal-regexp-lookbehind)によると、

> 後読みアサーションを使用すると、手前に別のパターンが存在するパターンにマッチすることができます。
> たとえば`$`記号を含まずに金額だけマッチするような使い方ができます。
> 肯定後読みアサーションは`(?<=...)`と記述します。
> `$`記号を含まずに金額だけマッチさせたい場合は`/(?<=$)\d+(\.\d*)?/`とし、これは`$10.53`にマッチして`10.53`がキャプチャされます。
> しかし`€10.53`にはマッチしません。
> 否定後読みアサーションは`(?<!...)`と記述し、手前に別のパターンが存在しないパターンにマッチします。
> `/(?<!$)\d+(?:\.\d*)/`は`$10.53`にマッチしませんが、`€10.53`にはマッチします。


### RegExp Unicode Property Escapes

[ドキュメント](https://github.com/tc39/proposal-regexp-unicode-property-escapes)によると、

> `\p{…}`および`\P{…}`形式のUnicodeプロパティエスケープが追加されます。
Unicodeプロパティエスケープは、`u`フラグの指定された正規表現で使用可能な新しいタイプのエスケープシーケンスです。
これを使うと、以下のような記述が可能になります。

```js
const regexGreekSymbol = /\p{Script=Greek}/u;
regexGreekSymbol.test('π'); // true
```

## Lifting template literals restriction

タグ付きテンプレートリテラルを使うと、エスケープシーケンスの制限を気にする必要がなくなります。
詳細は[こちら](https://tc39.github.io/proposal-template-literal-revision/#sec-template-literals)をご覧ください。


## What's new in ES2019?

ECMAScriptの最新バージョンである、ES2019で追加されたものを見ていきましょう。

## Array.prototype.flat() / Array.prototype.flatMap()

`Array.prototype.flat()`は、指定された深さまでの配列を再帰的にフラット化します。
深さ引数のデフォルトは1です。
`Infinity`を指定すると、無制限にネストを解除します。

```js
const letters = ['a', 'b', ['c', 'd', ['e', 'f']]];

// デフォルトは1
letters.flat(); // ['a', 'b', 'c', 'd', ['e', 'f']]

// 2段階
letters.flat(2); // ['a', 'b', 'c', 'd', 'e', 'f']

// 2段 = 1段 * 2
letters.flat().flat(); // ['a', 'b', 'c', 'd', 'e', 'f']

// ネストがなくなるまで再帰的にフラット化
letters.flat(Infinity) // ['a', 'b', 'c', 'd', 'e', 'f']
```

`Array.prototype.flatMap()`は、引数の取り扱いは`flat()`と同じです。
配列を単純にフラット化するのではなく、関数を渡して任意の処理を行うことができます。

```js
let greeting = ["Greetings from", " ", "Vietnam"];

// 普通のmap
greeting.map(x => x.split(" ")); 
// ["Greetings", "from"]
// ["", ""]
// ["Vietnam"]

// mapしてflat
greeting.flatMap(x => x.split(" ")) // ["Greetings", "from", "", "", "Vietnam"]
```

普通に`map()`を使うと、ネストした配列になります。
`flatMap()`を使うことでフラットな配列にすることができます。


## Object.fromEntries()

`Key-value`ペアからオブジェクトに変換します。

```js
const keyValueArray = [
  ['key1', 'value1'],
  ['key2', 'value2']
]

const obj = Object.fromEntries(keyValueArray)
// {key1: "value1", key2: "value2"}
```

`Object.fromEntries()`は引数として配列、Map、その他の反復可能プロトコルを受け取ります。
反復可能プロトコルの詳細については[こちら](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Iteration_protocols#The_iterable_protocol)をご覧ください。


## String.prototype.trimStart() / .trimEnd()

`String.prototype.trimStart()`は文字列の先頭にある空白を削除し、`String.prototype.trimEnd()`は文字列の末尾にある空白を削除します。

```js
let str = "    this string has a lot of whitespace   ";

str.length; // 42

str = str.trimStart(); // "this string has a lot of whitespace   "
str.length; // 38

str = str.trimEnd(); // "this string has a lot of whitespace"
str.length; // 35
```

`trimStart()`のエイリアスとして`trimLeft()`が、`trimEnd()`のエイリアスとして`trimRight()`が存在します。


## Optional Catch Binding

ES2019より前は、catch句に必ず例外変数を取る必要がありました。
ES2019では省略することができます。

```js
// Before
try {
   ...
} catch(error) {
   ...
}

// ES2019
try {
   ...
} catch {
   ...
}
```

エラーを無視したいときに便利です。
この機能のユースケースについては[この記事](http://2ality.com/2017/08/optional-catch-binding.html)を強くお勧めします。


## Function​.prototype​.toString()

関数の`.toString()`は、ソースコードを文字列として返します。

```js
function sum(a, b) {
  return a + b;
}

console.log(sum.toString());
// function sum(a, b) {
//    return a + b;
//  }
```

コメントも含みます。

```js
function sum(a, b) {
  // perform a sum
  return a + b;
}

console.log(sum.toString());
// function sum(a, b) {
//   // perform a sum
//   return a + b;
// }
```

## Symbol.prototype.description

`.description`は`Symbol`の値を文字列で返します。

```js
const me = Symbol("Alberto");

me.description; // "Alberto"

me.toString() //  "Symbol(Alberto)"
```


## Download the cheatsheet

[このリンク](https://inspiredwebdev.com/cheatsheets)から、これらのチートシートをダウンロードすることができます。

よかったら[Amazon](http://a-fwd.to/5gUojI8)や[Leanpub](https://leanpub.com/thecompleteguidetomodernjavascript2019)で私の本を買ったり、[Educative](https://www.educative.io/courses/complete-guide-to-modern-javascript?aff=BqmB)のコースを受けてみてください。



# 感想

全てと言いつつ全てではありませんが、これは元々著者が[The Complete Guide to Modern JavaScript](http://a-fwd.to/5gUojI8)という書籍を出していて、この記事はその抜粋だからです。

抜粋といってもわりとけっこうな分量でしたが、元の本は全300ページという更に相当な力作となっています。
最初は変数や関数といった基礎部分から順にステップアップしていく内容で、この記事で紹介されている新機能は200ページ以降に出てくる、いわばおまけ部分です。
全編英語なのでなかなかたいへんですが、読み通せば現代のJavaScript事情に詳しくなれることは間違いありません。
しかもKindleなら2000円弱と大変お買い得、これは持ってて正解ですね。