2023/7 の TC39 ミーティングでステージが変わった機能
- カテゴリ:
- JavaScript
- コメント数:
- Comments: 0
◆ Array Grouping がステージ 3
◆ Object.groupBy みたいな static メソッドになった
◆ Promise.withResolvers がステージ 3
◆ promise と合わせて resolve, reject 関数も取得できる
◆ Deferring Module Evaluation がステージ 2
◆ import としたモジュールの実行を遅延させる
◆ Object.groupBy みたいな static メソッドになった
◆ Promise.withResolvers がステージ 3
◆ promise と合わせて resolve, reject 関数も取得できる
◆ Deferring Module Evaluation がステージ 2
◆ import としたモジュールの実行を遅延させる
関係ない話
個人ブログですし まずは本題と関係ない話ですサブブログの方に前書いたように 最近モチベーションが出なく 何もやる気が起きないのです
楽しめるものもなく仕事ではストレスが溜まる一方
転職でもしてみようかなーと思いましたが 就活みたいなのってすごく苦手なんですよね
なんといってもめんどくさいです
それに条件に合うので絞り込んでみても たいていは聞いたこともなく興味ないところばかり
自分も使うようなウェブサービスなんかは興味がありますし そういう会社で働けるといいよね なんて思いますがそういう会社ってかなりの人気どころです
そういうところは日本中の有能な人が集まるので 私みたいな一般人はまず通らないでしょうね
現実を知ってますますウツな気分になります
周りには転職活動をして私でも知ってるような知名度のあるサービスをしてる会社に転職できてる人もいますがすごいですよねー
本題
TC39 のミーティングの時期ですね現地時間で 7/11 ~ 13 日のようです
https://github.com/tc39/agendas/blob/main/2023/07.md
すでにステージ更新が確定したものは更新されているので見てみます
https://github.com/tc39/proposals
Array Grouping
配列のプロトタイプに group という名前を追加したら一部サイトが壊れたからと一旦延期になっていたグループ化機能ですステージ 3 に戻ってきたようです
ということは問題が出なければ現状の仕様で確定みたいですね
使い方ですが 配列のメソッドではなく Object や Map の static メソッドという使い方に変わったようです
↓リポジトリからの引用
const array = [1, 2, 3, 4, 5];
// `Object.groupBy` groups items by arbitrary key.
// In this case, we're grouping by even/odd keys
Object.groupBy(array, (num, index) => {
return num % 2 === 0 ? 'even': 'odd';
});
// => { odd: [1, 3, 5], even: [2, 4] }
// `Map.groupBy` returns items in a Map, and is useful for grouping
// using an object key.
const odd = { odd: true };
const even = { even: true };
Map.groupBy(array, (num, index) => {
return num % 2 === 0 ? even: odd;
});
// => Map { {odd: true}: [1, 3, 5], {even: true}: [2, 4] }
Array.from や Object.fromEntries みたいなものと言われたらそうなのですが 配列のメソッドでメソッドチェーンで書けるのがよかったので残念なところです
pipeline operator がない現状でメソッドじゃない関数だと前から順に書いていけなくて書きづらいです
サブクラス
最近どんな議論があったのかなと issues 見てたらサブクラスの扱いに関しての話題で気になるものがありましたMap を継承した MyMap を作って MyMap で groupBy しても結果は Map になり MyMap にはならないようです
https://github.com/tc39/proposal-array-grouping/issues/58
罠っぽいけどまぁそうなんだーくらいに思ってました
よく見るとこの issue 投稿してる人が core-js の作者の人です
他の書き込みにも気になるところがあってもう少しちゃんと読んでリンクとかも見てみてると @@species が廃止されそうになってたようです
今になって知りました
まぁマイナー機能ですし ビルトインクラスを継承しようとしなければ気にすることもほぼないですしね
しかも私の場合はクラス自体をほぼ使わない書き方なのでさらにです
MDN を見ても脆弱性の原因になったり最適化の問題があって削除を検討中と警告が出てました
https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/Array/@@species
「@@species」 というのは コンストラクタを設定できるもので 配列の map みたいにそのクラスの新しいインスタンスを返すところで どのクラスにするか選べるようできるものです
何も設定しないと継承したクラスのインスタンスです
class MyArray extends Array {}
const a = new MyArray(1, 2, 3)
console.log(a.map(x => x))
// MyArray(3) [1, 2, 3]
Array を設定することで MyArray ではなく Array のインスタンスにできます
class MyArray extends Array {
static get [Symbol.species]() { return Array }
}
const a = new MyArray(1, 2, 3)
console.log(a.map(x => x))
// [1, 2, 3]
任意のものを設定できるので自身のコンストラクタや親のコンストラクタ以外でもいいです
class MyArray extends Array {
static get [Symbol.species]() { return class { foo = "bar" } }
}
const a = new MyArray(1, 2, 3)
console.log(a.map(x => x))
// {0: 1, 1: 2, 2: 3, foo: 'bar'}
たしかに複雑になりそうです
廃止されるならデフォルトの挙動は継承したクラスのインスタンスになるものかと思いましたが 元々のクラスのインスタンスになるようです
MyArray の map で Array が得られるという感じです
最近のメソッドではすでにそういう挙動になっていて 以前のメソッドと統一性がない状態でした
class MyArray extends Array {}
const a = new MyArray(1, 2, 3)
console.log(a.slice())
// MyArray(3) [1, 2, 3]
console.log(a.toReversed())
// [3, 2, 1]
そういう状況なので static メソッドである今回の groupBy も継承を無視した本来のクラスのインスタンスが得られるようです
やりたいならこういう感じで自作の継承したクラスに自分で変換すればいいだけなので 個人的には別に困らないものです
class MyArray extends Array {
toReversed() {
return MyArray.of(...super.toReversed())
}
}
class MyMap extends Map {
static groupBy(...args) {
reutrn new this(super.groupBy(...args))
}
}
ただリリースされて結構長い機能なのに本当に削除できるのかは気になりますね
最近の import attribute よりはるかに長い期間ですし
JavaScript の仕様策定する側としては継承よりもラップして使ってというスタンスみたいです
React でも継承よりも合成すべきとか言われてましたね
この考え方は好きなので良いことだと思います
低レイヤー言語だとメモリ節約とか処理効率で継承のほうがいいのかもですが 読みづらくなるだけですし
関係ない話が長くなったのでもとに戻します
Promise.withResolvers
4 月にはまだステージ 1 だったのにもうステージ 3 のようですすでにあちこちで独自に実装して使われているものを仕様化するだけで内容も凄くシンプルなので早いのでしょうね
使い方はこれだけ見れば十分わかるはずです
const { promise, resolve, reject } = Promise.withResolvers()
Promise ってコンストラクタに渡したコールバック関数内で非同期処理を行って 結果に応じてコールバック関数の引数として受け取る resolve/reject を実行するデザインです
しかし実際には promise オブジェクトと合わせて resolve, reject 関数がほしいことも多いです
promise のメソッドとして resolve があってもいいくらい
そのために結局↓みたいなユーティリティを毎回作ることになります
const createPromise = () => {
let resolve
let reject
const promise = new Promise((_resolve, _reject) => {
resolve = _resolve
reject = _reject
})
return { resolve, reject, promise}
// or return Object.assign(promise, { resolve, reject })
}
これを標準で用意してくれるので毎回準備する手間が省けます
昔の npm の問題もあってこの程度をライブラリとしてインストールすることはあまりないでしょうし かといって自分たちで用意するとなるとだいたい同じとはいえ 微妙に使い方が違うこともあります
それが揃うというのもメリットですね
ブラウザ側の実装も時間はかからないと思うので次のミーティングではもうステージ 4 になってたりするかもですね
Deferring Module Evaluation
ステージ 1 からステージ 2 になったようですこれまでステージ 1 だったこともあって今回初めて知ったものです
インポートの処理を遅延させてパフォーマンスを改善するためのものらしいです
import defer * as yNamespace from "y"
という感じで import 構文が変わります
たいていの場合はモジュールのファイルの一番上にまとめて依存モジュールの import を書きます
しかし めったに使われない関数の中でだけ使うモジュールも毎回ロードするのって無駄です
そういうときはその関数の中で動的にロードするようにすることが多いです
CJS の頃は require が同期的だったので問題なかったのですが import だと常に非同期処理になります
関数の処理が fetch するみたいな非同期処理なら別にいいのですが 同期処理だった場合は import の都合で非同期関数になってしまいます
そのせいであちこちに async と await を書かないといけないです
これは CJS で書いたコードを ESM に書き換えるときの問題点のひとつだったのですが これを改善しようとする提案です
ただ それだけなら importSync 関数を追加すればいいだけだと思うのですが 関数実行時にネットワークリクエストが発生して遅くなるのを避けることも目的であるようです
なので静的な import 構文を拡張して defer を指定するようです
遅延させるのは実行だけで ファイルのプリフェッチはしておくようです
「* as yNamespace」 のところは必ずこの形式にする必要があって デフォルトや名前付きの形でのインポートはできないようです
そうすることでエクスポートされた値にアクセスするときは yNamespace.foo のようにプロパティアクセスとなるので getter の処理としてここでモジュールの実行を行うようです
// [entrypoint.js]
import defer * as mod from "./mod.js"
const fn = () => {
console.log(mod.value)
}
// [mod.js]
console.log("mod.js")
export const value = 100
これだと mod.js の console.log は呼び出されず fn を実行して mod.value を参照したタイミングで mod.js が実行されて console.log が呼び出されます
この仕組みのために import 時に名前空間を使う必要があるようです
変数参照のタイミングでモジュールが実行というのは 一般的に想定しない動きですし 意図しないところでエラーが出そうです
個人的には mod.load() みたいなのを呼び出すほうが好みです
またトップレベル await を使うモジュールは遅延できないようで defer 対象のモジュールの依存ツリー内であってもそのファイルだけは即時実行されるようです
今でも静的 import と動的 import とトップレベル await でモジュールがどうロードされて実行されるか結構複雑なのにまた複雑になりそうです
機能的には便利で嬉しいものなんですけどね
仕様や README だけだとイメージがあまりつかめなかったですが スライドの説明がわかりやすかったです
https://docs.google.com/presentation/d/1rSsVsFsnXQZ8pEGFwAGiVbVqndr4DHEUqTGEM9Au0_4
まだステージ 2 なので変わる可能性もありますが 今後が気になる機能です
Optional Chaining Assignment
ステージ 1 になったようですステージ 1 だと使えるようになるのがかなり先だったり 仕様が大きく変わったりしそうですが 内容的にはそのまま早い段階で上の方のステージに来そうです
内容はこれができるようになるというものです
foo?.bar = 1
今だと foo がオブジェクトであっても左辺が undefined の可能性があるので
Invalid left-hand side in assignment
というシンタックスエラーになってます
こういうケースは代入できないときは代わりのことをすることが多くて if-else で処理してるので今のところあまり困ってませんが 便利になるので早い段階で使えるようなって欲しいですね
ただ polyfill だけでは済まず構文の扱いにも影響するので実装に時間がかかったりはするのかもしれません
DataView get/set Uint8Clamped methods
こっちもステージ 1 になったようですUint8Array などの TypedArray が今 11 種類もあるようです
そんなにあったんですね
それでそのうちの 10 種類で DataView の get/set メソッドが使えて 残るひとつの Uint8ClampedArray だけで使えない状況のようです
これをそろえるために Uint8ClampedArray にも DataView の get/set メソッドを使えるようにするようです
こっちも仕様に変更なくそのまま早い段階でステージが進みそうな気がします