第97次TC39会议回顾
为期三天的第97次TC39会议于昨天(2023年7月13日)结束。本次会议上提案方面的重要更新并不是很多。我个人认为值得一提的有几个。
Source phase import
第一个是source phase import提案进入stage 3。该提案允许用import source foo from "./foo.wasm"
语法和对应的动态import形式import.source("./foo.wasm")
以WebAssembly.Module
形态导入wasm,得到一个已经编译但未实例化的wasm模块对象,然后可用WebAssembly.instantiate(foo)
来实例化。
这里上下文关键字source
含义表示模块的source
阶段。即已经加载并编译,但未实例化(附加上下文信息,与其他模块链接,以及初始化),因此source可以在多个worker中共享并产生各自的模块实例。
整个模块加载可以分为五个步骤:
- resolve:根据模块的specifier(如果是相对url如
./foo
还要考虑当前模块的url),确定模块的实际url - fetch/compile:根据url去获取模块并编译
- attach evaluation context:附加上下文,比如
globalThis
、import.meta
等 - link:与其他模块进行符号链接(即确定import进来的东西实际来自哪里)
- eval:执行模块,得到包含实际导出值的模块对象
我们现在已有的import语句即为eval之后的最后阶段。新的一系列提案允许介入中间阶段,进行细粒度的模块控制。如resolve步骤之后对应import asset x
模块资源提案,fetch/compile步骤之后即为source阶段,attach evaluation context和link步骤之后则对应import defer * as x
延迟初始化提案(见本文下一节)。
source phase import提案目前会开箱支持wasm。除了wasm外,将来也可以以对应的ModuleSource形态导入JS或其他模块。通过ModuleSource对象可能可以得到模块源码、import/export绑定的名字等信息。
不过source
的命名是个遗留问题,主要是很多人觉得source
这个名字有点迷惑。我的经验也是如此,之前在介绍这个提案时,很多开发者的第一反应是误以为会得到一个源码字符串。但实话说,我也想不出更好的名字。提案早期曾使用module
关键字,与wasm用例对应,但是也是一个容易产生混淆的命名。虽然WebAssembly.Module
表示一个未实例化的module,但是在JavaScript语境下,module
通常表示一个已经实例化的module,甚至是最终的包含实际导出值的模块对象。所以提案后来改用了source
关键字。其他的一些候选关键字如 instantiable
、handle
、parsed
、compiled
、compile
、raw
、code
、detached
、abstract
、syntax
等似乎也半斤八两或者干脆更差。综合概念准确性和开发体验(单词简单、容易拼写)等因素,我个人会选择source
或abstract
。对本话题有兴趣的朋友可以给我留言,或直接在提案仓库的对应 issue Bikeshedding the source
keyword · Issue #53 · tc39/proposal-source-phase-imports (github.com) 中发表高见,注意该问题的讨论截止日期是下周五(2023年7月21日)。
Deferred import evaluation
第二个是deferred import evaluation提案进入stage 2。也就是前面提到的import defer * as x
,会把模块的初始化延迟到第一次访问x.foo
,这对于复杂应用来说可能会有一定的性能提升。例如Babel转译器的维护者报告说,一个preset可能包含了大量的transformer实现但实际transpile过程中可能并未用到,当他们重构为采用某种形式的延迟初始化之后,在一些用例中获得了超过20%的性能提升。
这里有几点要注意。
首先由于使用导入本身通常是同步的,所以只能延迟同步的初始化,而不能延迟加载、解析、链接等所有可能需要异步的步骤。
其次,由于要求同步,因此使用了top-level await的模块也不能被延迟。目前提案的语义是具有tla的模块会提升并立即执行(另一个方案是遇到tla直接报错,但是有些代表反对),但非tla模块仍然会延迟。这意味着如果你的模块用了tla,其本身就无法延迟,但可以通过把模块拆细,拆出来更多的同步模块仍然可以被延迟。当然拆分代码是有开发成本的,而且阅读体验会下降,所以也许得等待webpack等打包器提供针对性的自动拆包优化。【BTW,我一直认为当前的top-level await的设计是严重失误,这里的问题(虽然不算特别严重)只是又一个例证。但现在也没办法了,只能看看将来能不能出点提案补救一下。】
然后,要求使用* as x
语法是因为,如果是命名导出的话,访问一个简单符号foo
可能产生模块初始化对很多人来说感觉太magic,而x.foo
是属性访问,就不至于太magic。当然我是认为延迟加载本来就是magic,从开发体验角度考虑,最好不要强制要求必须用* as x
语法。
最后,尽管前面说了所有异步步骤不能延迟,但引擎理论上是可以做某些优化的 —— 只要预先已经知道了某些模块的性质,比如nodejs下如果知道所import的是commonjs模块,由于commonjs整个是同步的,所以其实是可以延迟加载的。
Optional chaining assignment
第三个是Optional chaining assignment提案进入stage 1。
foo?.bar
的optioinal chaining语法已经广为使用,此提案将其推广到赋值如:foo?.bar = value
,语义(粗略地)相当于if (foo != null) foo.bar = value }
。
看上去这个提案是挺好的,不过JS(或者说任何复杂的编程语言)的暗面在于当一个特性与其他特性(尤其是那些历史遗迹特性)结合时可能会感受完全不同。
比如由于短路语义,如果foo
为nullish时,value
部分并不会被执行。这在大多数情况下似乎都很ok,包括foo?.bar += value
和foo?.bar++
,但++foo?.bar
就略奇怪,感受上是右侧的运算符影响了左侧的运算符是否生效。因此当前草案不允许和++
运算符一起用。
另一个问题是返回值。foo?.bar = value
的返回值在foo为nullish时是undefined
。也就是到底是否赋值这个信息会从返回值上泄漏。乍看似乎也无所谓(因为短路语义本身就是可以通过副作用观测到的),但考虑a=b=foo?.bar=42
的例子就比较诡异了,开发者可能期待a和b总是为42,特别是当修改代码时,假设原本代码是a=b=42; if (foo) foo.bar=42
,然后重构为a=b=42; foo?.bar=42
,然后重构为a=b=foo?.bar=42
—— 这最后一步重构是错误的,但开发者可能很难注意到。
一个简单粗暴但我很赞成的解法是让foo?.bar = value
只能作为statement expression来使用(说人话就是不能作为表达式用),上述最后一步重构就会扔SyntaxError从而保护开发者。实际上assignment可作为表达式这个事情本身就是弊大于利(比如常见的if(x = v)
误用),对coding style比较严格的团队通常会通过linter禁用。
总体来说,这个提案的用处也不是很大,也有一些额外问题要处理,但考虑这个提案是对已经广泛使用特性的自然扩展,且在其他现代语言(如Kotlin)里也早已有之,故我还是支持的。
Stop coercing things
一个对未来有全面影响的是关于「coercing」的讨论。coercing即隐式类型转换。比如我在推上写过的[1,2,3].at("start")
返回1(JS好智能!),但[1,2,3].at("last")
也返回1(好吧,还是很蠢!)的讽刺笑话,其原因就是JS会对参数做隐式转换,在这个例子里就是需要一个整数,如果传入非整数就进行隐式转换:字符串会被转换为数字,如果不是合法数字就得到NaN,然后转整数,NaN会转为0。
本次讨论中,委员会的大多数人(也包括我)赞同这类隐式转换是历史遗迹,将来新提案应该缺省不做coercing,如果参数不符合要求应该抛TypeError或RangeError。不过还是有少数人对某些具体原则存在疑虑,会在下次会议继续讨论。
Proposal meta-review
最后一个是对一些较长时间没有更新的stage 3和stage 2的提案进行了总体上的review,如果有人对一些提案的未来感兴趣,可以去看会议纪要(目前尚未公开,但估计9月之前会公开),只是说某些提案的近况可能会令人失望,比如do expression提案,看上去当前的champion已经弃疗。甚至他认为委员会最好不要在语法相关的提案上浪费时间了。
怎么说呢,考虑到某些语法提案推进之困难,这种消极反应我也能理解,但是我认为委员会应该找到如何推进语法提案的合理原则和流程,而不是摆烂(要么像do expression那样弃疗,要么像pipeline operator那样强行推进一个社区争议极大的提案)。
以上就是本次会议的回顾。有任何想法欢迎给我留言,或在JSCIG的讨论区发起讨论。
【题图是我远程参加会议的截图,左侧列了参与TC39会议实时讨论的渠道,右侧是本次会议所在城市挪威第二大城市卑尔根市的有趣雕像,雕像人物为现代戏剧之父亨里克·易卜生,其著名剧作包括《玩偶之家》、《人民公敌》、《野鸭》。尤其《玩偶之家》因为鲁迅先生的《娜拉走后怎样》在中国广为人知。】
转载自:https://juejin.cn/post/7255496592425451580