Slide 1

Slide 1 text

TC39 で提案されている ECMAScript 最新仕様 pixiv Inc. petamoriken 2019.8.20 #9 FukuokaJS LT

Slide 2

Slide 2 text

2 自己紹介 ● 主にフロントエンドエンジニア ● ActionScript 3.0 を JavaScript に書き直したり WebGL を駆使したサイトを作ったり PHP のコードをリファクタリングしたり ● ECMAScript とか DOM API を追うのが好き petamoriken 課題解決部

Slide 3

Slide 3 text

3 Ecma International TC39 とは ● JavaScript の言語仕様 ECMAScript を策定する専門委員会 ● 提案一覧は GitHub 上で管理されている ○ https://github.com/tc39/proposals ● だいたい2ヶ月おきに会議を開いている ○ アジェンダ: https://github.com/tc39/agendas ○ 議事録: https://github.com/rwaldron/tc39-notes

Slide 4

Slide 4 text

4 ECMAScript の策定プロセス ● Stage 1 Proposal ○ 担当者が決まる ● Stage 2 Draft ○ 最初の Spec テキストが作られる ● Stage 3 Candidate ○ Spec テキストが完成し、レビューが完了する ● Stage 4 Finished ○ polyfill ではない2つの実装で互換性が確認される

Slide 5

Slide 5 text

5 議論に参加するには ● 既存の提案については該当のリポジトリの issues へ ● 新規提案については ES Discuss というメーリングリストへ ○ ここ数日は Modulo Operators がホットトピック ○ Float16Array ■ polyfill 書いたら Stage 1 になった ○ Map#assign ■ 提案したらより包括的な Collection Methods が Stage 1 になった

Slide 6

Slide 6 text

具体的に各 Stage の提案を 10個ほど紹介していきます 6

Slide 7

Slide 7 text

Stage 4 (ES2020) 3 out of 3 7

Slide 8

Slide 8 text

8 String#matchAll ● 正規表現のキャプチャを含めてマッチした文字列を得るメソッド const reg = /t(e)(st(\d?))/g; const str = "test1test2"; str.match(reg); // -> ["test1", "test2"] ● String#match だとキャプチャ出来ない

Slide 9

Slide 9 text

9 String#matchAll ● RegExp#exec だとループ処理を書く必要がある let match; while (match = reg.exec(str)) { // reg.lastIndex プロパティを書き換えることで状態を保持している console.log(match); } // -> ["test1", "e", "st1", "1", index: 0, input: "test1test2", groups: undefined] // -> ["test2", "e", "st2", "2", index: 5, input: "test1test2", groups: undefined]

Slide 10

Slide 10 text

10 String#matchAll ● String#matchAll を使うと安全に得られる for (const match of str.matchAll(reg)) { // Iteratorの内部で状態を保持している(RegExp#execよりも安全) console.log(match); } // -> ["test1", "e", "st1", "1", index: 0, input: "test1test2", groups: undefined] // -> ["test2", "e", "st2", "2", index: 5, input: "test1test2", groups: undefined]

Slide 11

Slide 11 text

11 Dynamic Imports ● 動的にモジュールを読み込むことができるシンタックス ● 読み込みに成功したら以降その結果をキャッシュする ● 読み込み失敗時にはキャッシュされない ○ ちょうど一昨日その変更が入った Normative: change idempotency for HostImportModuleDynamically https://github.com/tc39/ecma262/pull/1645

Slide 12

Slide 12 text

12 Promise.allSettled ● Promise が全て fullfilled か rejected のときに fullfilled する Promise を返すスタ ティックメソッド ● Promise.all は ○ Promise が全て fullfilled のときに fullfilled ○ Promise が1つでも rejected のときに rejected

Slide 13

Slide 13 text

Stage 3 3 out of 14 13

Slide 14

Slide 14 text

14 Weak References let obj = { foo: "foo" }; const weakRef = new WeakRef(obj); console.log(weakRef.deref()); // -> { foo: "foo" } // { foo: "foo" } への参照をなくす obj = null; // しばらく経って GC によって回収されたら null になる console.log(weakRef.deref()); // -> null

Slide 15

Slide 15 text

15 Weak References ● GC された後処理用のコールバックを登録出来る const finalization = new FinalizationGroup((holdings) => { for (const holding of holdings) { console.log(holding); // -> "bar" } }); // コールバックに渡す引数とともに登録する finalization.register(obj, "bar");

Slide 16

Slide 16 text

16 Weak References ● GC は実装によって挙動が異なるためロジックとして扱うことは非推奨 ○ GC の順番保証はされず、タイミングを制御することは出来ない ○ ブラウザでタブを閉じたときに後処理用のコールバックは実行されない ○ 例えば GC されたら Local Storage などに退避させて、また必要になったら取り出す などの処理はバグる恐れがあるのでしてはいけない ● あくまで過剰なメモリ使用を削減したり、メモリリークを防ぐなどのために使うことを推奨して いる

Slide 17

Slide 17 text

17 Nullish Coalescing Operators ● 主にオプションのデフォルト値周りが書きやすくなる演算子 interface Options { width?: number; height?: number; duration?: number; } const options: Options = { width: 100, duration: 0, };

Slide 18

Slide 18 text

18 Nullish Coalescing Operators ● null, undefined のときにデフォルト値を受け取るようにする const width = options.width != null ? options.width : 256; const height = options.height != null ? options.height : 256; const duration = options.duration != null ? options.duration : 500; ● Nullish Coalescing Operators で書きやすくなる const width = options.width ?? 256; const height = options.height ?? 256; const duration = options.duration ?? 500;

Slide 19

Slide 19 text

19 Optional Chaining ● オプション自体が null の場合に対処しやすくなる interface Options { width?: number; height?: number; duration?: number; } const options: Options | null = null;

Slide 20

Slide 20 text

20 Optional Chaining ● オプション自体が null の場合を考慮しないと TypeError を引き起こす const width = options.width ?? 256; // Uncaught TypeError: Cannot read property 'width' of null ● Optional Chaining で null, undefined に対処する const width = options?.width ?? 256; const height = options?.height ?? 256; const duration = options?.duration ?? 500;

Slide 21

Slide 21 text

Stage 2 3 out of 17 21

Slide 22

Slide 22 text

22 Decorators ● クラスやメソッドに @foo などを付与することで処理を追加するシンタックス ● 最新仕様は今年3月に登場した3代目 Built-in Decorators ○ TypeScript の experimentalDecorators は初代の仕様のままで全然追随出来てい ない ○ Babel は2代目の仕様のまま ● 詳しくは Qiita の ESNext Stage 2 Decorators の変遷と最新仕様 という記事へ

Slide 23

Slide 23 text

23 Iterator Helpers ● Iterator/AsyncIterator を変換・操作するメソッド ● 遅延評価のため Array から Array を作るよりも無駄にならない ○ next メソッドで値を取り出すときに処理がなされる ● 詳しくは uhyo さんの JavaScriptのイテレータが持つメソッドをそろそろ知っておきたい人 が読む記事 へ const iterator = [1, 2, 3, 4, 5].values(); const array = iterator.take(3) .map((value) => value * 2) .toArray();

Slide 24

Slide 24 text

24 Explicit Resource Managements ● 明示的にリソースの後処理をするシンタックス ● 具体的なシンタックスについてはまだ未定 try (const fileHandle = acquireFileHandle()) { // スコープを抜けるときに // fileHandle[Symbol.dispose] が呼ばれ開放処理を行う } ● 詳しくは uhyo さんの try-using文を用いるJavaScriptの超モダンな“リソース管理” へ

Slide 25

Slide 25 text

Stage 1 1 out of 48 25

Slide 26

Slide 26 text

26 Emitter ● 今年6月に登場した期待(?)の新人 ● Push-based Streams を扱えるインターフェースとして提案されている const emitter = new Emitter(); emitter.each((value) => console.log(value)); // -> 42 emitter.next(42);

Slide 27

Slide 27 text

27 Emitter ● EventTarget から Emitter を作る Emitter.run( Emitter.on(document, "click"), Emitter.filter((e) => e.target.tagName === "BUTTON"), Emitter.map((e) => ({ x: e.clientX, y: e.clientY })), (coords) => console.log(coords), );

Slide 28

Slide 28 text

28 Emitter ● Stage 1 Observable (RxJS) vs Emitter ○ どちらも Push-based Streams を扱える ○ Observable は Subscription と双方向にやりとりするが Emitter は単方向 のみ ■ RxJS で言うところの Hot な状態のみを扱える ■ つまり Emitter は Observable よりも扱える領域は狭いと言える

Slide 29

Slide 29 text

29 Emitter ● Iterable, AsyncIterable (Pull-based Streams), Promise も幅広く扱うことが出来る ● Emitter を経由して Iterable から Array を作る ○ Emitter.reduce でコレクションに変換できる const iterator = [1, 2, 3, 4, 5].values(); const array = Emitter.run( iterator, Emitter.until(3), Emitter.map((value) => value * 2), Emitter.reduce([]), );

Slide 30

Slide 30 text

30

Slide 31

Slide 31 text

31 Emitter ● まだ Stage 1 なのでよくわからないが現段階で API が結構やばめ ● 引数の型によって動作が変わる ○ Emitter.reduce(0) は number が流れてきたら足し、それ以外だと 1足す ○ Emitter.reduce("") は流れてきたものを string にキャストし連結する ○ Emitter.reduce([]) は流れてきたものを Array#push する ○ Emitter.reduce({}) は流れてきたものを Object.assign する ● もし非同期のものが流れてきた場合はどうなるんだろう ……

Slide 32

Slide 32 text

32 終わりに ● みなさんも TC39 の動向を追ってみてはいかがでしょうか? ● 個人的に Scrapbox に TC39 meeting を日本語でまとめているのでよければどうぞ