この記事は Google I/O '19 のセッションの視聴メモです。
想定読者は自分なので正確性や網羅率には問題があるかもしれません。
Reference
Speakers
- Mathias Bynens https://twitter.com/mathias
- Sathya Gunasekaran https://twitter.com/_gsathya
Abstract
このプレゼンテーションではモダンなWebサイトとNodeアプリケーションを作るためのJavaScriptの開発手法を紹介します。ChromeとNodeに近日登場する機能と、V8がそれらのためにどんな最適化をしているのか、また実際の開発においてWebとNodeのアプリケーションのパフォーマンスと安定性をどう改善しているのかという点がわかります。
Contents
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>
という形でなければアクセスできないのは自明なので良さそう。
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>
でマッチ部分を名前付けして取得できる
- capture group と一緒に使うと
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
- BigInt#toLocaleString
- JSBI: BigInt のポリフィル
Array#flatMap
-
[...].flat(<number>)
で配列中の配列も再帰的に展開可能になった- 引数に
Infinity
を指定するとできる限り展開するようになる
- 引数に
Object#fromEntries
-
Object#entries
の逆 - Map からオブジェクトを生成することもできる
globalThis
- あらゆる環境でグローバルオブジェクトを取得するコードを実装するのは大変
Stable sort
-
Array#sort
が安定ソートになった
Intl
- i18nのための機能
-
Intl.RelativeTimeFormat
- 1日前が
yesterday
になったり昨日
になったりする
- 1日前が
- サードパーティーライブラリで対応するにはロケールファイルをダウンロードする必要があるので、標準で対応していると嬉しい
-
Intl.ListFormat
- "a, b, c" が "a, b, or c" にフォーマットされる
- 日本語だと微妙な感じに…
lf.format([ '東京', '大阪', '福岡' ]) // => "東京、大阪、または福岡"
-
Intl.DateTimeFormat#formatRange
- "2019年5月7日から5月9日" が "May 5-7, 2019" にフォーマットされる
- flagを有効にしないと確認できなかったので日本語でどうなるかは未確認
- "2019年5月7日から5月9日" が "May 5-7, 2019" にフォーマットされる
-
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と組み合わせるとメモリに優しいキャッシュができる
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
は登録した値が開放されたときにコールバックを呼べる
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引数が必要になる