JavaScript vs JavaScript:第二轮。战斗!
写完第一篇文章后,我不确定要多久才能收集到足够的主题来开展下一轮讨论。但考虑到 JavaScript 生态系统的持续发展,很多主题自然而然地就落到了我的手里。
那么,让我们开始吧。战斗!
1. 建造 vs. 不建造
几年前,我读过一篇很棒的文章(现在好像找不到了),文中说 JavaScript 正处于一个十字路口。JavaScript 这门“语言”与作者所认为的“机器”相悖。我当时几乎没能体会到其中的微妙之处,但现在站在这里,一切都说得通了。
对于我们这些老手来说,第一次接触 JavaScript 是通过浏览公共网站的页面源代码。看到喜欢的东西,我们就会直接复制。而 Web 作为一个开放平台,我们拥抱了这种自由。
15年后,我们一边努力支持一些老旧的浏览器,一边又对即将推出的新功能感到惋惜。那么我们该怎么办呢?编译。CoffeeScript,以及后来的Babel。我们渴望打造原生体验,这意味着JavaScript的使用量会越来越大,因此我们精简并优化了我们的打包代码。
如今,距离 Web 诞生已过去 25 年,我们却已经坚持了 10 多年。JavaScript 几乎占据了 JavaScript 存在时间的一半。那么,哪个才是更真实的 JavaScript?
多年来,我一直将 JavaScript 称为元语言。我们用 JavaScript 重写其他 JavaScript 的执行方式,以至于当你在 Github 仓库中看到一些源代码时,你根本不知道它到达浏览器时会是什么。
这是好是坏?不确定。但这确实存在。生态系统已经变得内生,自我膨胀。机器编写机器。我们的编译器更先进。我们的工具更复杂。如果没有这些,我们还能如何编写代码吗?我们真的需要这样做吗?
嗯,有些人认为,是时候大崩溃了。原生平台在同一时期内得到了极大的改进,比以往任何时候都更加强大。但是,它能否根据最终应用程序导入的内容,将所有并发模式代码从响应式库的核心例程中剔除?它能否跨模板分析所有状态在声明式视图中的使用情况,从而准确地确定需要向浏览器发送哪些 JavaScript?
不,差得远。
有没有想过,为什么像Vite这样没有 Bundle 的工具仍然需要预打包node_modules
?Svelte 的功能如此强大,却又如此类似于纯 HTML、CSS 和 JavaScript?工具已经变得如此根深蒂固,我们甚至都懒得去想它。它不断地“左移”,直接进入我们的 IDE。
但如果您不需要这些,那可能就没问题。
正如原作者所言,现在停下脚步已经太晚了。工具为我们描绘的开发者体验世界,我们只能在梦中想象。我们也不需要为了这个数字世界而放弃用户体验。工具越多并不意味着浏览器中的 JavaScript 越多。在接下来的几年里,我预计 JavaScript 会越来越少。少得可怜。
但 Web 的伟大之处在于,我们随时可以关掉它。index.html
有人会吗?你不会在那群人里找到我。但谁知道呢,有了导入映射和原生 ESM,你甚至可能会想公开你未压缩的源代码,以重新激励下一代 JavaScript 开发者。
2. 特定框架 vs. 无关框架
框架无关。我的意思是,这就是梦想,对吧?几十年来,我们一直在努力实现这个目标。那么,为什么我们还没实现呢?
尽管人们喜欢泛化,但如果框架都一样,就不会有这么多了。至多,与框架无关也只是迎合最低标准。这不仅仅是语法的问题。React的并发模式、Svelte的动画、Marko的自动部分合并和渐进式渲染……等等。网络越广,泛化和优化就越困难。
这些差异通常根植于基本理念和架构。每个决策都有利弊,我们不能指望一切都一致。即使你拥有一些看起来很相似的东西,比如React的 Hooks 和Solid的 Reactivity。每隔十年左右,趋势和模式就会发生变化和适应。你会乐意在你的声明式现代 SPA 框架中使用 jQuery 插件吗?
那么,与框架无关究竟是什么意思呢?嗯,它只是意味着我们有了一个新的框架。这是一种获得采用的好方法,并且可以整合类似的框架。如果所有条件都相同,你难道不会选择最高效的方案吗?如果你打算使用 Web Components 来创建跨框架的微前端设计系统,你会用React来编写吗?或者你会用Preact吗?
随着时间的推移,不可知论的东西会自然而然地巩固在最符合其理念和目标的最佳底层手段上。此时,你必须评估包装是否真的增加了足够的价值。
关键在于,当你把一个与框架无关的东西具体化时,总是有可能写出更好的版本。互操作无疑有其价值,也具有面向未来的感觉,但如果做错了,就类似于过度设计。我们贪婪地认为自己可以预测未来。
即使当足够复杂的事情被提出为标准或成为官方平台的一部分时,情况也是如此。只要有选择,一些人就会倾向于选择最有效的方式来完成某件事,或者选择符合他们所遵循的指导原则的方式。
这本身并没有错,就像任何钟摆一样,我们确实需要两个方面:实验/增长,以及整合/标准化。只是,我不确定哪一方能够带来长期的稳定性。最终,为了生存,一切都需要适应。
3. 语言原语 vs 语言组合
构图为王。或者说“永远不要打赌
JavaScript“组合”?作为一名框架作者,我非常重视这一点。组合可以让你构建简单且可扩展的东西,而不会增加复杂性。适应性远比灵活性重要。
那么这与 JavaScript 有什么关系呢?框架一直在努力将 UI 描述的经验简化为基本原则。无论采用何种方法,框架都已将响应式语言作为构建块。响应式、钩子、组合 API 都包含三个概念:
状态 - 可观察量、参考量、信号、原子
派生 - 计算量、备忘录、选择器
反应 - 效果、自动运行
即使我们不将其命名为Svelte,我们也具有相同的 3 个:
let x = 0; // state
$: y = x * 2; // derivation
$: console.log(`${y} is double ${x}`) // reaction
那么,这些都一样吗?其实不完全一样。Svelte走的是语言关键字的路线,而React则使用函数。这和以下两者的区别没什么不同:
for(let i = 0; i < list.length; i++) {
doSomething(list[i])
}
// and
list.forEach(item => doSomething(item));
有什么区别?一旦你想在这里抽象我们的列表迭代器,你就不能再用相同的for
语法调用它了。相反,我们需要使用像 这样的函数forEach
。实际上,你可以使用myForEach
完全相同的签名来做到这一点。而且这样做真是令人厌烦。
知道还有什么可组合的吗?组件。它们并非一直存在于前端框架中,但自从引入以来就无处不在。你可能在某个地方使用,list.map
但也可以以可组合的方式扩展该模式。<VirtualList>
<PaginatedList>
与循环类似for
,像Svelte这样的模板助手#each
是语言级别的,而不是可组合的。这允许使用专用且简洁的语法。但是,当你迁移到<PaginatedList>
它时,需要一种完全不同的语法(Slot Props)。而且Svelte并非孤例。大多数模板 DSL 的控制流都采用了这种方式。
所以谁在乎呢?框架提供的原语和最终用户创建的相同,这本身就非常强大。它在一致性中提供了简洁性,并使扩展感觉像原生一样。如果你用过,useState
你就知道怎么用useLocalState
。如果你用过,<For>
你就知道怎么用<PaginatedList>
。这没什么特别的。
最棒的是,如果您不喜欢可组合 API 提供的组件,您可以创建自己的组件,让它感觉像第一方组件。它让开发者拥有自己的体验,并完全根据自己的用途来驱动生态系统。React的组件和 Hooks 正是因此而取得了巨大的成功。虽然我一直使用 Svelte 作为对比,但 Svelte Stores 在这方面也同样出色。
语言级原语确实有好处。它们通常更容易分析,这有助于编译器进行优化。因此,我期待看到开发者如何在不做出太多妥协的情况下,将两者的优势结合起来。目前为止,这类东西包括Vue的ref sugar和Marko的Tags API。这绝对值得关注。
4. 运行时框架 vs. 无运行时框架
好吧,我承认。这完全是个诱饵。如果能看到一个真正无运行时、拥有强大功能集的 JavaScript 框架,那该有多有趣啊。但这些框架实际上并不存在,而且理由充分。
需要澄清的是,我指的不是那些允许不将 JavaScript 发送到浏览器的框架。我指的是那些大量使用编译来提前完成工作的框架。“消失”或“无运行时”之类的说法已经流传开来,但这只是夸张的说法。
代码复用是好事。你肯定不想完全编译掉整个框架,因为那样会导致大量无法扩展的重复代码。
通常情况下,编译掉库只是将大小从一个地方移到另一个地方。运行时节省的部分空间通常会转化为更大的组件。因此,Svelte 是小型运行时的典范,也是组件扩展性比较的典范。
React/Svelte 尺寸分析
Vue/Svelte 尺寸分析
30 个 TodoMVC 或 19 个 TodoMVC 仍然是一条达到大小等效的陡峭曲线,因此显然,这在大多数情况下对大小都是净正值。
进行这种权衡还有其他好处。更具体的代码比通用代码性能更高,并且在每个接触点上比等效的泛型方法代码更少。显然,这些权衡加起来可能会有影响,但这些权衡通常是值得的。
另外,也有一些完全基于运行时的框架,其基础运行时甚至更小。HyperApp宣称其大小约为 1kb,甚至比 Svelte 的 1.6kb 还要小。所以没有硬性规定。
事实上,即使只看bundlephobia.com 的大小,这几乎也成了毫无意义的练习。现代的 tree-shaking 和死代码消除技术可以生成比宣传的明显更小的大小。但更重要的是,你会发现许多框架中存在一种模式,即使用preact/hooks
、svelte/motion
或等子模块solid-js/store
,这些模块不会计入宣传的 bundle 大小。
所以,关键在于,真正了解框架规模的唯一方法是通过它的具体应用场景来了解。像“无运行时”这样的营销噱头,与规模讨论基本无关。有小型库,也有更小的库。
5. 渐进式增强 vs. 水合
我经常听到“渐进增强”这个词。虽然我最初真正意识到这一点是因为 Web 组件的潜力。它的理念是,你可以定义额外的行为,并在浏览器不支持某些功能或启用 JavaScript 时回退到原生行为。遗憾的是,由于 Apple 阻碍了原生内置功能的开发,Web 组件很难在这里取得成功。
如今,我听到这个术语指的是通过添加少量 JavaScript 来增强服务端渲染的页面。例如Stimulus、Alpine.js或Petite Vue。诚然,这些可以是渐进式增强,但也可以不是。仅仅添加 JavaScript 来为页面添加功能并不意味着页面在没有 JavaScript 的情况下也能正常工作。没有 JavaScript,按钮就无法保证按预期工作。
相反,任何看过今年Svelte 峰会或Remix Run 测试版预览视频的人都知道,这些框架虽然是功能齐全的单页应用,但却在关闭所有 JavaScript 的情况下展示了功能齐全的网站。当然,它们可能是 Svelte 或 React,但在我看来,这就是渐进式增强。
Alpine.js、Stimulus和其公司都是超小型框架,旨在在服务器渲染的 DOM 节点之上添加 JavaScript 功能,而不是更繁琐的客户端渲染。但所有支持服务器渲染的 JavaScript 框架也都具备这种功能。他们称之为“Hydration”。
框架可能会自上而下运行,而不是像这些小型库那样进行独立目标的混合,但这更多的是实现细节。初始化客户端状态和附加事件监听器的步骤相同。
就是这样。渐进式增强就像可访问性一样,是一种考量。这是我们作为开发者要实现的,而不是某种特定的技术。Hydration 是向服务器渲染节点添加 JavaScript 功能时不可避免的事情。唯一重要的是我们做了多少。
这让我们想到……
6. 部分补水、渐进补水和可恢复补水
所以这里有三件事。或者说,只有一件事。这就是问题所在。我敢肯定,你们中的一些人会觉得这一切让你口渴难耐。我们的 JavaScript 到底应该补充多少水分?
玩笑归玩笑,这是一个充满困惑的领域,原因在于这些描述并非指代某种具体的技术。它们描述的只是方法的特征,而非其工作原理。无论解决方案是什么,我们都需要这样做才能在服务器渲染的页面上实现交互式 JavaScript。
部分 Hydration 顾名思义,就是并非所有页面都需要 Hydration。实际上,这意味着我们不需要将所有组件代码都发送到浏览器。“Islands”(孤岛)这个术语被广泛使用,它很有道理,因为最常见的方法是将应用拆分成一个静态页面,只包含我们需要发送到浏览器的这些“Islands”组件。
渐进式水合是指根据需要对页面进行水合的能力。可能是在页面进入视野时,也可能是在交互时。即使最终需要对整个页面进行水合,通过分解水合过程,我们也可以减少初始加载的时间。
可恢复 Hydration 是一种通过在本地组件级别序列化所需数据来减少 Hydration 执行时间的技术,从而跳过 Hydration 期间进行任何计算的需要。因此,Hydration 此时的唯一工作就是添加事件处理程序。
这些技术并不相互排斥。Astro通过其手动的“岛”方法利用部分和渐进式水合,在熟悉的框架之上扩展了此功能。Qwik一直在开创可恢复水合,同时使用组件级渐进式水合,在没有显式岛的情况下很好地模拟了部分水合。Marko长期以来一直使用其编译器自动检测岛,但一直在结合可恢复水合,以便将尽可能少的代码发送到浏览器。
但这一切在机制层面上的意义却不那么清晰。归根结底,问题在于什么时候进行“hydrated”(水合)。将工作推迟到以后和根本不做之间,只有一线之隔。如果你需要在组件更新后立即重做在服务器上完成的工作,你的“hydrated”真的可以恢复吗?将 JavaScript 回滚到与 JavaScript 交互时,是否是目前尚未捕捉到的另一种成本指标?
您可能已经注意到,本节中很少提及React、Vue或Svelte等常见解决方案。部分原因是,由于单页应用的优化难度较低,多页应用框架在该领域的创新中占据主导地位。但VuePress的拆分包和React 服务器组件也在探索如何利用这些框架的优势来节省成本。
这就是 JavaScript vs JavaScript 的另一部分。我相信,在这个不断发展的生态系统中,很快就会出现新的主题。
文章来源:https://dev.to/this-is-learning/javascript-vs-javascript-round-2-fight-2m44