探索下一代浏览器语言:JavaScript是否仍是最佳选择?

发表时间: 2023-01-19 12:16

【CSDN 编者按】提及前端,JavaScript 是一门避不开的编程语言。不过在浏览器领域,本文作者认为直接使用 JavaScript 未必是最佳选择,同时在开发过程中,使用编译为 WebAssembly 的语言以及编译为 JavaScript 的语言都有一定的缺点,那么,下一代浏览器语言会是什么样,不妨通过本文的讨论初探一下。

原文链接:
https://uptointerpretation.com/posts/the-next-browser-language/

声明:本文为 CSDN 翻译,未经允许,禁止转载。


作者 | Nicholas Yang
译者 | 弯月 责编 | 屠敏
出品 | CSDN(ID:CSDNnews)

假设你正在编写前端代码,可选择的编程语言有多少种?

我认为这些语言可大致分为三大阵营:直接使用 JavaScript、编译为 WebAssembly 的语言以及编译为 JavaScript 的语言。

直接使用 JavaScript 需要的工具最少,但代价是调试难度非常大,而且也不方便阅读。尽管新手可以选择 JavaScript,但除了对“极简主义”的痴迷之外,我看不到太多好处。

编译为 WebAssembly 的语言虽然在不断发展,但仍然处于起步阶段。通常,这类编程语言会产生很大的二进制文件,因为大多数语言都需要提供额外的运行时。互操作性仍然是一个白日梦。即便两种语言都能编译成 WebAssembly,也不意味着二者之间一定可以相互交谈。而且这些语言仍然需要赶上数十年来为 DOM 编写的各种 JavaScript 库。WebAssembly 也没有类似于 React 或 Svelte 的库。不要误会我的意思,WebAssembly 有自己的使用场合。如果你想在浏览器中运行包含大量计算的原生代码,WebAssembly 是完美的选择。但我不推荐日常前端开发选用 WebAssembly。

还有编译成 JavaScript 的语言。这类语言实在不成气候,我们不能对这个问题闭口不谈。ClojureScript、Elm、ReScript、Dart 等语言都有很不错的社区,但我不认为它们的市场份额一定会扩大。这很可惜,毕竟编译成 JavaScript 的语言可能是在浏览器中获得良好体验的最有效方式。它们允许你访问 JavaScript 所没有的功能,比如静态类型、强类型、不变性、宏等。此外,它们还允许你访问 JavaScript 以及广泛的 JavaScript 生态系统, 而且它们不需要打包大型运行时。

有人认为有了 WebAssembly,人们可能就不愿意将其他语言编译成 JavaScript,因为 Wasm 才是浏览器编程时的“正版”编译目标。但我不同意这种观点。我们需要更多可以编译成 JavaScript 的语言。在文本中,我想介绍一下我心目中未来的语言是什么样子。


TypeScript


说起编译成 JavaScript 的语言,就不得不提到 TypeScript。TypeScript 是一种很棒的语言,显著提高了开发人员的体验,增加了类型安全,发展出了更好的工具,而且替换成本也非常低。考虑到整个 JavaScript 生态系统的现状以及与类型检查 JavaScript 本身的难度,我认为 TypeScript 团队取得了非凡的成就。

然而,一些针对 TypeScript 的批评也很公正。总结起来主要有两大症结:性能和合理性。需要注意的是,TypeScript 团队并非不知道这两种批评意见。然而,这是 TypeScript 团队在开发之初做出的明确权衡。在我看来,当时团队为了能够实现 TypeScript 而选择这些权衡是非常明智的。

话虽如此,性能可以说是 TypeScript 最常被提及的问题。TypeScript 本身也是用 TypeScript 实现的,这个实现非常复杂。目前这个类型系统实际上是一种迷你编程语言,这会导致类型检查的速度非常缓慢。

第二个问题是合理性。这个问题不太有人经常提及,但专注于编程语言的开发人员却经常抱怨。TypeScript 有很多“洞”,如 allowJs 配置选项、any 类型和交集类型,因此这个类型系统无法确保代码是类型安全的。可以说,TypeScript 编写的代码依然可能在运行时出错。除此之外,TypeScript 的类型推断只能处理最简单的情况。很多时候,你必须明确标注类型。

然而,这两个问题都是深思熟虑后的权衡结果。让编译器自己编译自己是测试 TypeScript 的最好办法。开发人员必须从语言的角度了解 TypeScript。更具体地说,他们必须体验编写大型 JavaScript 代码库的感受,然后逐步在代码库内加入类型。TypeScript 选择了不实现严格的合理性,这样开发人员就可以在现有的 JavaScript 代码库中逐步采用 TypeScript,这也意味着开发人员只需要使用一个 any 类型就可以摆脱添加类型时的挫败感。

可以说,TypeScript 是第一个纯粹为了改善开发者体验(而非语义)的语言。它没有添加任何运行时结构,对性能没有任何影响。相反,它添加了一个类型系统,更重要的是,它教会了社区如何使用类型、构建高质量工具、建立正确的文化。这本身就是一个不可思议的壮举。


下一个浏览器语言


所有这一切都表明,TypeScript 在 10 年前做出了一些对语言产生巨大影响的权衡。而现在,随着时间的流逝,我认为是时候出现一种新的语言,并做出一系列新的权衡了。具体来说,我想要一种具有合理性、类型推断,并且能够快速编译的编程语言。

但是,这些选择带来的相应的权衡是什么?

首先,为了合理性,这门语言不会尝试对各种 JavaScript 模式进行类型检查,相反它会成为一门单独的语言,使用更简单的类型系统编译成 JavaScript。它会把现有的 JavaScript 代码视为与之互操作的外部代码,对 JavaScript 代码进行运行时类型检查,而且它还会用不同的原生语言实现。

为什么我想要这样的一种语言?

首先,我喜欢编写具有合理性且相对简单的类型系统的语言。我想要一种既可以在浏览器中运行,也可以在现有Web生态系统中运行的语言。编译为 WebAssembly 的语言常常忽略 Web 生态系统的其余部分。他们想在浏览器中逐个像素地描绘原生 UI。虽然我认为这是一个很好的目标,但不是我的目标。我想使用这种语言来构建普通的日常网站。我不想要一种纯函数式语言,我想要一种具有传统 C 风格语法的语言,我想要一种语言来体现我在“工具的工具”方面的想法。

为什么我认为现在这个时间点很合适?答案很明显,现在是开始学习一门语言的第二佳时间,第一佳时间是 10 年前。但我也认为 JavaScript 社区在过去十年中发生了很大变化。人们学习了 TypeScript,并习惯了接收编译器的反馈,也习惯了对数据进行建模。人们开始使用 Rust、Swift 和 Kotlin 等语言。人们开始懂得优秀的工具的意义。这并不是说十年前人们会拒绝类型安全的语言,只不过当时很难广泛采用。


ReScript/ReasonML


有些人可能觉得我描述的这种语言听起来非常像 ReScript/ReasonML。

没错,二者在某些方面确实有重叠。但是,我的语言在理想情况下应该对 JavaScript 代码和特征进行运行时类型检查。运行时类型检查有助于实现良好的互操作。使用其他 JavaScript 库会相对容易一些。而且,我相信 traits 更适合用户,它们可以映射成其他语言的特性,如 Java 接口和 C++ 概念。利用 traits 可以轻易实现一些特性,比如通过 Display trait 实现输出任何类型。这个功能虽然看起来很浅显,但可以避免一些本不应该出现的易用性方面的奇怪问题,例如“如何输出这个?”或者“为什么整数加法需要使用+,而浮点小数的加法需要使用+.”等。此外,我希望删除一些额外的东西,如对象、链表、多态变体等。根据上一次的尝试,我也不喜欢 ReScript 的开发人员体验和错误消息。

也就是说,我不排除 ReScript 有可能成为正确选项的可能性。由于我的经验来自多年前,所以我认为可以再给这门语言一次机会。


类型安全


我希望我的语言能用一种更系统的方法来实现类型安全。具体来说,我想采用 Rust 中实现不安全块的方式来实现与 JavaScript 之间的互操作。也就是说,如果想调用 JavaScript,需要将代码包装在一个不安全的块中。这可以作为一个明确的标志,表明你需要更仔细地阅读这段代码。接下来只需为这些不安全的 JavaScript 库调用编写绑定即可。刚开始的时候,这个过程可以是手动的,但希望将来出现类似于 bindgen 和 cxx 之类的功能。

在 JavaScript 中,使用不安全块的概念似乎是一个奇怪的选择。JavaScript 的不安全和C的不安全并不一样。但很多人没有意识到的是,安全性指的并不仅仅是网络安全

安全指的是能够放心使用一个值、无需担心它可能为 的能力。安全指的是能够利用类型对领域进行建模,并且能确信建模是正确的能力。安全指的是可以随意改变其内容,而无需担心引入错误或混乱的能力。JavaScript 由于其动态的特性,本质上就是不安全的。而 Rust 的不安全块可以让用户在拥有安全区的前提下,访问大规模的不安全代码。基于浏览器的语言也应当如此。

至于运行时检查,我相信为这个功能付出的额外开销是值得的。我们已经有许多在 JavaScript 中进行模式验证的先例了,只不过还没有通用的规则。现在的模式验证一般是自动推断出一种能够在运行时出错的语言类型,或者对 JavaScript 值进行模式匹配。


WebAssembly


我对 WebAssembly 未来的发展依然持乐观态度。虽然新功能从建议到实现需要很长时间,但这可能是为了保证其质量。但我认为 WebAssembly 不一定会成为浏览器的通用运行时。可能这一点会有改变,但我认为 WebAssembly 更像是一种硬件加速器。当用户需要使用适合硬件特性的强大计算时(比如固定宽度整数、静态函数调用等),就会使用 WebAssembly ,就像是想用并行计算的用户会使用GPU一样。我认为,这种模型有实现异构计算的潜力,比如部分代码编译成 JavaScript、部分代码编译成 WebAssembly。这一点可以由用户主动控制,也可以自动实现,甚至可以通过即时编译实现。也许,通过对 JavaScript和Wasm 代码的控制,编译器可以最小化两者之间的交互,从而提高性能。甚至可以做成类似于将代码发给 WebGPU 之类的机制。

有可能有了这种模型,编写需要大量计算的程序(如机器学习模型、视频游戏、渲染软件)就更容易了。

这种将代码编译成 WebAssembly 和 JavaScript 的概念也可以体现在语言中。我希望能明确指定整型和浮点型,最好 Rust 的 usize 之类的索引类型。这样,如果代码被编译成 WebAssembly,就能享受 WebAssembly 的固定宽度整数带来的好处。另一种可能性是,可以建立语言的一个子集,该子集可以编译成 Wasm,并限制一些动态特性,如闭包、垃圾回收等。要访问该子集,需要使用另一种 unsafe 风格的块(或许可以是 strict 块),或者也可以让子集通过 dynamic块访问外部代码。这些都是假设,但我认为值得一试。


实现


该语言很可能用 Rust 实现。主要是因为我很喜欢 Rust,我相信 Rust 带来的算术数据类型、相对较快的代码、有限但够用的可修改性和丰富的库非常适合编写编译器。

如果 WebAssembly 进化得足够好,其性能接近原生代码,我就会考虑用该语言的一个子集来编译这个编译器本身,将其编译为快速的 WebAssembly。但至少 Rust 编写的编译器应该够用几年了。


结论


你也许注意到了,类型安全和 WebAssembly 这两节实际上是在讨论系统语言的思想,如不安全块、硬件加速等,想办法将它们应用到基于浏览器的语言上。这种语言的设计就是这样的。一些很有趣的编程语言都是在系统层次上实现的。我希望能在浏览器中实现它们。

虽然本文标题是《下一代浏览器语言》,但我想澄清一点,这并不是单一的一门语言。我希望出现多种语言,尝试多种思路。希望本文能抛砖引玉,激发读者在浏览器语言领域创新的兴趣。

为了忘却的纪念——2022 Linux 内核十大技术革新功能 | 年终盘点