2019-01 の TC39 meeting
まとめ
決まったこと
Web compatibility issues / Needs Consensus PRs
Amending the memory model to support ARMv8 LDA/STL codegen (slides) JavaScript の Atomic な仕様が ARMv8 LDA/STL としてコンパイルすることを想定していないため、その環境では遅くなるとのこと。
Annex B.3.5 によると catch ブロックの中で var による再宣言は許されるが、for-of を使った場合再宣言が許されない。
code: (js)
// これは OK
try {} catch (e) { var e; }
// これは SyntaxError
try {} catch (e) { for (var e of whatever) {} }
ES2015 の策定当時は Web の互換性的に問題なければ可能な限り厳し目の仕様にしたが、この場合は実装を難しくする割にそんなに恩恵が無いように見えるので許してあげていいのでは? という提案。コンセンサスが得られた。
class の name は ClassDefinitionEvaluation の後でセットされるので Decorators と Class Fields で困る。
code: (js)
class C {
static foo = this.name; // この時点では this.name は ""
}
C.name === "C";
C.foo === ""; // 😭
よってname プロパティの設定は ClassDefinitionEvaluation の中でしようという提案。コンセンサスが得られた。
前回に引き続き Dynamic Modules のために仕様を修正するやつ。レビュー中かな。
Array#sort に引き続き %TypedArray%#sort も安定ソートとする提案。コンセンサスが得られた。SharedArrayBuffer とかで意味がある感じなのかな。
Set のコンストラクタではそのインスタンスのもつ "add" メソッドを使って初期化する仕様になっている。これによって継承したときに柔軟性をもたせることができるが、一方で無駄にオーバーヘッドを生む問題になってしまっている。とりあえず考慮に値するということで議論が始まるっぽい。
以前あった %ArrayIterator%#next を非 writable, configurable にして高速化したいっていうのに似ている。これは結局イテレート初回の "next" メソッドをキャッシュ化することで落ち着いたが。 Stage 4 (ES2019)
Object.entries の逆をする函数。無事 Stage 4 になった 🎉
JSON.stringify の生成する JSON をちゃんと ECMASctipt のサブセットにする提案。無事 Stage 4 になった 🎉
code: (js)
// こうだったのが
JSON.stringify('\uD800'); // '"<U+D800>"'
// こうなる
JSON.stringify('\uD800'); // '"\\ud800"'
すべての実行環境で String#{trimLeft, trimRight} が既に実装されている(仕様にはない)が、String#{padStart, padEnd} の経緯同様に String#{trimStart, trimEnd} にしようっていう提案。 無事 Stage 4 になった 🎉
なお String#{trimLeft, trimRight} は Web 互換性のためにエイリアスとして Annex B に残る。
Break the Web を引き起こして一時はどうなるかと思われていたが、Chrome が影響の少なさからか取り下げない決定をしてそのまま Stage 4 になった感じ。まあ highchart.js で行のラベルが出なくなってもそんなに問題ないか。 Stage 3
Community (developers and educators) feedback over globalThis, advocating for Global (slides) グローバルオブジェクトを取得するやつ。globalThis から今度は Global が提案された。
そもそも globalThis は微妙で
- the name says “global”, but the true identity is more like “default this” anyway
- this is one of the most common confusions for new learners
- Strict-mode is preferred over non-strict-mode (modules, classes, etc)
- this-aware functions should not be encouraged to be invoked without a proper this binding (needing the non-strict-mode default)
- in strict-mode, this defaults to undefined in that case anyway: exception
- the "default global this" behavior of the object in question is quite uncommon, and should be further discouraged. It's a niche corner case at best. We should not highlight or encourage “default global this”.
いっその事 Global ってしたらネームスペースっぽくていいんじゃね? といった感じっぽい。
Private Fields 周りで議論が散らかってしまっていたので再度ゴールを明確にするプレゼンがなされた。
* Class controls who sees private (👍 refactoring private)
* Analogous to public (👍 refactoring public ⇔ private)
* Analogous to internal slots (👍 polyfilling built-ins)
* Keep the mental model simple (👍 learning)
* Class controls how private operates (👍 predictable)
* Optimizable as much as public (👍 fast)
Overloading method parameters between BigInt and Number: Just Say No (?) (slides) 今まで number, string を入れることを想定していたメソッドに bigint をそのまま入れられるようにするか、それとも分けるかが話された。
code: (js)
// number
let fmt = new Intl.NumberFormat("en");
fmt.format(3) // "3"
fmt.format("5") // "5"
fmt.format({valueOf() { return 5 }}) // "5"
// bigint (overload)
fmt.format(3n) // "3" ???
fmt.format({valueOf() { return 5n }}) // "5" ???
// bigint (separate)
fmt.formatBigInt(3n) // "3" !!!
fmt.formatBigInt({valueOf() { return 5n }}) // "5" !!!
なんで分けるかという話になっているかというと、string が入ってきたときに number に丸める仕様になっているのでこれを変えることは出来ない。一方で bigint だと丸めないっていう感じになると危険なのではないかとのこと。
code: (js)
fmt.format("987943287529743907509271") // "987,943,287,529,744,000,000,000"
fmt.format(987943287529743907509271n) // "987,943,287,529,743,907,509,271"
一方でこの Intl.NumberFormat がレアケースなだけで、他も分けるのは微妙なのではという意見もある。
Stage 2
例によって複雑になりすぎたので、別のドキュメントを作ってそれを使って進めていくことになった。
サブクラスの扱いをどうするかという話がなされた。
case-insensitive であったり、特定の型しか入れないみたいな用途で Set のサブクラスを作った場合、そのオブジェクトが持つ #has を使うべきかどうかといった感じ。
後述する Richer Keys が入ると解決されるのではとのこと。
fulfilled, rejected に関わらずすべての Promise が完了したら fulfilled する函数。Stage 2 になった。
Collection に toKey, toValue を入れる提案と compositeKey, compositeSymbol の提案。
前者は
code: (js)
new Map([], {
toKey(key) {return normalizedKey;},
toValue(value) {return normalizedValue;},
});
new WeakMap([], {
toKey(key) {return normalizedKey;},
toValue(value) {return normalizedValue;},
});
new Set([], {
toValue(value) {return normalizedValue;},
});
new WeakSet([], {
toValue(value) {return normalizedValue;},
});
みたいな感じで Collection に入れる直前に割り込んで key, value を変えることが出来る。
後者は引数に入れた値に対して一意の key を発行できる。
code: (js)
compositeKey(...parts: ...any): Object.freeze({__proto__:null}); compositeSymbol(...parts: ...any): Symbol(); 少なくとも引数の1つは Object でなければならない(GCのため)。また引数の順番が違っても別物扱いされるらしい。
前者のみ Stage 2 になった。
Stage 1
スタックトレースがそのまま見れる状態だとセキュリティ上よくないので、手を加える API を用意する話。
~ を使うと Numeric Literals を自由に拡張できる提案。とりあえず仕様が出来てきたっぽい。
code: (js)
4525~i // Imaginary numbers
235435.461~m // User-defined Decimal
300~px // CSS Typed OM
なんか見た目が気持ち悪いな……。
code: (js)
// Object.getOwnPropertyDescriptor でそもそも writable がない
console.log(Object.getOwnPropertyDescriptor(Object.prototype, "__proto__").writable); // undefined
Object.prototype.__proto__ = 1;
console.log(Object.prototype.__proto__); // null
どういう API を提供するかは決まってないが、とりあえず Stage 1 になった。
It's not clear what the API for this would be. Two possible designs (a non-exhaustive list):
* Two new Reflect methods (and corresponding traps) for setting and querying prototype immutability
* How would / could / should these be made consistent with existing traps, particularly the isExtensible trap?
* A new options-bag parameter to preventExtensions and isExtensible which would specify that these should only apply to the [[Prototype]]
* Harder to feature detect; if you pass the option on an implementation which didn't support it, you'd freeze more than intended
ユーザーが言語、地域、文字を選択するときの多言語対応のための API。
code: (js)
// Get display names of region in English
let regionDisplayNames = new Intl.DisplayNames('en', {type: 'region'}); // Get region names
// Get display names of region in Traditional Chinese
regionDisplayNames = new Intl.DisplayNames('zh-Hant', {type: 'region'}); // Get region names
console.log(regionDisplayNames.of('419')); // "拉丁美洲" Stage 1 になった。
[[Prototype]] を汚染されたとしても狙い通りのコンストラクタを継承できるテクニックとして Reflect.construct を使う方法が知られている。
code: (js)
class A {
constructor() { this.x = 1; }
}
class B extends A {
constructor() {
const instance = Reflect.construct(A, [], new.target);
instance.y = 2;
return instance;
}
}
B.__proto__ = class { };
const instance = new B();
console.log(instance.x); // 1
console.log(instance.y); // 2
しかしこの方法だと Class Fields があったときにちゃんと初期化出来ない問題がある。
code: (js)
class A {
constructor() { this.x = 1; }
}
class B extends A {
y = 2;
constructor() {
const instance = Reflect.construct(A, [], new.target);
return instance;
}
}
B.__proto__ = class { };
const instance = new B();
console.log(instance.x); // 1
console.log(instance.y); // undefined
それを解決するために new.initialize というメタプロパティを用意するという提案。
code: (js)
class A {
constructor() { this.x = 1; }
}
class B extends A {
y = 2;
constructor() {
const instance = Reflect.construct(A, [], new.target);
new.initialize(instance);
return instance;
}
}
B.__proto__ = class { };
const instance = new B();
console.log(instance.x); // 1
console.log(instance.y); // 2
Stage 1 になった。
グローバルに Iterator というネームスペースを用意し、Iterator に関する色々な便利なものを提供する提案。
- Iterator global namespace
- syncPrototype => %IteratorPrototype%
- asyncPrototype => %AsyncIteratorPrototype%
- from(value)
- Tries to grab Symbol.iterator if it exists
- If the iterator is a proper iterator inheriting from
%IteratorPrototype% it will be returned, otherwise a wrapper
will be returned.
- of(...items)
- Create an iterator from items. Basically
return Iterator.from(items)
加えて %IteratorPrototype% と %AsyncIteratorPrototype% を拡張する。
Additions to %IteratorPrototype%
- filter(callbackfn)
- map(callbackfn)
- take(n)
- Returns an iterator that yields the first n elements. Useful for making
an infinite iterator finite.
- reduce(callbackfn)
- Consume the entire iterator, using callbackfn as a reducer.
- collect()
- Create an array from the iterator
- Iterator.of(1, 2, 3).collect() // [1, 2, 3]
- This is in the same space as Array.from, but is included to keep symmetry
with Iterator.asyncPrototype.collect
Additions to %AsyncIteratorPrototype%
- filter(callbackfn)
- map(callbackfn)
- take(n)
- Returns an iterator that yields the first n elements. Useful for making
an infinite iterator finite.
- reduce(callbackfn)
- Consume the entire iterator, using callbackfn as the reducer.
- collect()
- Create an array from the iterator
- asyncIt.collect().then((items) => { console.log(items[2]); })
うーん、%ArrayIterator% は %IteratorPrototype% を継承していないようだけどどうするんだろう。
Stage 0
code: (js)
const foo = {
get bar() {
return this.#bar++;
}
};
private.initialize(foo.#bar, 0);
これって Private Symbols なんじゃね?
Withdrown
前回は特に Champions が現れずに、そのまま無くなるはずの Private Symbols がまた議題に出た……がコンセンサスが得られなかったらしい。残念。
その他
Module specifier for builtins (slides) code: (js)
// URL っぽくして IANA に登録できるようにする
import {readFile} from ‘nodejs:fs’
// NPM の Scope を尊重する
import {readFile} from ‘@nodejs/fs’
// その他
import {readFile} from ‘nodejs::fs’
import {readFile} from ‘%nodejs:fs’
import {readFile} from nodejsfs
// 文字列を使わない場合
import {readFile} from nodejs:fs
import {readFile} from nodejs::fs
import {readFile} from <nodejs:fs>
総括
だいぶ ES2019 に入って順調に見える一方で、Stage 3 に上がる予定だった Decorators が上がらなかった。仕様が膨れ上がってレビューをするのが困難にならなければいいが。