【視聴メモ】 What’s new in JavaScript (Google I/O '19)

この記事は Google I/O '19 のセッションの視聴メモです。
想定読者は自分なので正確性や網羅率には問題があるかもしれません。

---

## Reference

* [YouTube](https://www.youtube.com/watch?v=c0oy0vQKEZE)

## Speakers

- Mathias Bynens https://twitter.com/mathias
- Sathya Gunasekaran https://twitter.com/_gsathya

## Abstract

このプレゼンテーションではモダンなWebサイトとNodeアプリケーションを作るためのJavaScriptの開発手法を紹介します。ChromeとNodeに近日登場する機能と、V8がそれらのためにどんな最適化をしているのか、また実際の開発においてWebとNodeのアプリケーションのパフォーマンスと安定性をどう改善しているのかという点がわかります。

## Contents

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3387/86d6e5ec-06f8-d0c0-8471-4fda4fecc629.png)

### V8のパフォーマンス改善

- Chrome 61 (2017/09) と Chrome 75 (2019/06) を比べるとパースは2倍以上速くなった
- Node 7 (2016/10) と Node 12 (2019/04) を比べると Promise や async/await の実行は11倍（300ms→30ms）速くなった
- Chrome 60 (2017/07) と Chrome76 (2019/07) を比べるとメモリ使用量は20%ほど減少した

### New JS Features

- async iterators / async generators
- Promise#finally
- optional catch binding
    - try-catch でエラーを使わないときエラー変数を定義しなくても良い構文
- String#trimStart / String#trimEnd
- object rest & spread

### class fields

* クラスフィールドがpublicとprivateで定義できるようになった
* privateフィールドにスコープ外からアクセスするとSyntaxErrorになる。
* `this.#<filed>` という形でなければアクセスできないのは自明なので良さそう。

```js
class IncreasingCounter {
  #count = 0;
  get values() {
    console.log('Getting the current value!');
    return this.#count;
  }
  increment() {
    this.#count++;
  }
}

const counter = new IncreasingCounter();
counter.#count; // -> SyntaxError
```

### String#matchAll

正規表現に関する新機能

- `String#match` はマッチした文字列しか取得できない
    - マッチした位置などの詳細を知れない
- `String#exec` はwhileループで書かないといけない
    - 配列で取得できない
- `String#matchAll` はマッチの結果を詳細と共に取得できて配列にもなる
    - capture group と一緒に使うと `match.groups.<group-name>`でマッチ部分を名前付けして取得できる

```js
const string = 'Favorite GitHub repos: tc39/ecma262 v8/v8.dev';
const regex = /\b(?<owner>[a-z0-9]+)\/(?<repo>[a-z0-9\.]+)\b/g;
for (const match of string.matchAll(regex)) {
  console.log(`${match[0]} at ${match.index} with '${match.input}'`);
  console.log(`→ owner: ${match.groups.owner}`);
  console.log(`→ repo: ${match.groups.repo}`);
}
```

### BigInt

- 数字をアンダーバーで区切って見やすくする `123_456_789`
- 数字にnサフィックスでBigIntになる `123_456_789n`
- 区切る書き方と併用できるようになった
    - BigInt#toLocaleString
        - ロケールを意識して数字を区切ってくれる
    - Intl.NumberFormat
- JSBI: BigInt のポリフィル

### Array#flatMap

- `[...].flat(<number>)` で配列中の配列も再帰的に展開可能になった
    - 引数に `Infinity` を指定するとできる限り展開するようになる

### Object#fromEntries

- `Object#entries` の逆
- Map からオブジェクトを生成することもできる

### globalThis

- あらゆる環境でグローバルオブジェクトを取得するコードを実装するのは大変

### Stable sort

- `Array#sort` が安定ソートになった

### Intl

- i18nのための機能
- `Intl.RelativeTimeFormat`
    - 1日前が `yesterday` になったり `昨日` になったりする
- サードパーティーライブラリで対応するにはロケールファイルをダウンロードする必要があるので、標準で対応していると嬉しい
- `Intl.ListFormat`
    - "a, b, c" が "a, b, or c" にフォーマットされる
    - 日本語だと微妙な感じに…
        - `lf.format([ '東京', '大阪', '福岡' ]) // => "東京、大阪、または福岡"`
- `Intl.DateTimeFormat#formatRange`
    - "2019年5月7日から5月9日" が "May 5-7, 2019" にフォーマットされる
        - flagを有効にしないと確認できなかったので日本語でどうなるかは未確認
- `Intl.Locale`
    - ロケールごとの言語やカレンダーシステム、時間周期を設定できるインターフェイス

### Top level await

- 現状ではトップレベルにawaitは書けない
- ワークアラウンドとして、トップレベルの処理をすべてのasync関数で囲って即時実行するというものがある
- あまり意味のない手間なのでトップレベルでawaitを書けるようにする
- Chromeコンソールはすでにasync関数で覆われて実行されているのでトップレベルawaitが実行できるが、他の環境では対応していない

### Promise APIs

- `Promise.all` - Promiseが1つでもrejectされたとき止まる
- `Promise.race` - Promiseが1つでも終了（reject含む）したとき止まる
- `Promise.allSettled` - すべてのPromiseが終了したとき止まる
- `Promise.any` - Promiseが1つでも終了（reject含まない）したとき止まる

### WeakRef

- 使われてないエントリーが勝手にガベージコレクションされる
- Mapと組み合わせるとメモリに優しいキャッシュができる

```js
const cache = new Map();
    
function getImageCached(name) {
  let ref = cache.get(name);
  if (ref !== undefined) {
    const deref = ref.deref();
    if (deref !== undefined) return deref;
  }
  const image = perfomeExpensiveOperation(name);
  ref = new WeakRef(image);
  cache.set(name, ref);
  return image;
}
```

- keyの方はただのstringなので保存され続けるがどうするか → `FinalizationGroup` を使う
- `FinalizationGroup` は登録した値が開放されたときにコールバックを呼べる

```js
const finalizationGroup = new FinalizationGroup((iterator) => {
  for (const name of iterator) {
    const ref = cache.get(name);
    if (ref !== undefined && ref.deref() === undefined) {
      cache.delete(name);
    }
  }
});
    
// Exec finalizationGroup.register(image, name) after cache.set(name, ref);
// 第1引数の値が開放されたときに第2引数を元にコールバックが呼ばれる
// そのとき第1引数はすでに開放されているので第1引数を識別するような第2引数が必要になる
```