我没有时间
0:00二零一二年,丹麦奥胡斯。
镜头里有两把椅子。左边坐着 Anders Hejlsberg,右边坐着 Lars Bak。
他们面前的问题,听起来像一个普通的技术问题:JavaScript 应该怎样变得更适合大型应用?
可回答到一半,Lars 谈起的却是时间。
采访者: Is that not reasonable?
Lars Bak: Oh, it is reasonable if you have an infinite amount of time. So I'm getting close to pension age. And, all respect for the committee doing JavaScript, but if you have seen the adoption of new standards in JavaScript, I don't have time.
[001]
保留采访者的问题、无限时间前提、对标准委员会的限定和最后一句,避免把“I don't have time”剪成脱离语境的宣言。
我没有时间。
[001]
保留采访者的问题、无限时间前提、对标准委员会的限定和最后一句,避免把“I don't have time”剪成脱离语境的宣言。
先不要急着把这句话听成不耐烦。
四年前,Lars 刚刚带领团队为 Chrome 造出 V8。那台新的 JavaScript 引擎,让网页里原本缓慢的脚本开始承担越来越完整的应用。
可三年以后,他又参与公开 Dart:一门新语言,一台新虚拟机,以及一个更大胆的设想,让未来的 Chrome 不只认识 JavaScript,也直接认识 Dart。
[002]
两份同期官方公告分别证明 Dart 发布时 Chrome 集成尚属探索,以及 Dartium 技术预览后来把最终纳入 Chrome 写为目标。
二零一一年十月,Google 的公告写得很谨慎:Dart VM 还没有集成进 Chrome,团队计划探索这个选项。四个月以后,Dartium 技术预览已经把这台 VM 装进实验版本的 Chromium,并把目标写得更清楚:经过测试和反馈,最终纳入 Chrome。
[002]
两份同期官方公告分别证明 Dart 发布时 Chrome 集成尚属探索,以及 Dartium 技术预览后来把最终纳入 Chrome 写为目标。
一个刚刚让 JavaScript 变快的人,为什么又觉得,只让它变快还不够?
三年以后,那台 VM 没有进入正式 Chrome。
但它留下了一个位置。
一个曾经被写进计划,最后又空下来的位置。
这里是《原代码》。
第二期,《跨过山丘》。故事从两把椅子和一句“我没有时间”开始。要知道 Lars 在赶什么,得先回到他最熟悉的地方:语言下面,那些使用者通常看不见的机器。
把等待藏在语言下面
2:38要理解 Lars 为什么会提出这项计划,先得看他一直站在哪里。
一九八八年,他从丹麦奥胡斯大学取得计算机科学硕士学位,参与 BETA Mjolner System。此后是 Self、Smalltalk 和 Java HotSpot。语言的名字换了一次又一次,他所在的位置却很少改变。
他总在语言下面。
这里的“下面”不是高低之分,而是软件的层次。程序员写变量、对象、函数和类;再往下一层,要有东西把这些规则真正运行起来。
它常被叫作 runtime,也就是运行时。虚拟机,VM,是运行时的一种核心形态。
它不是机房里的一台物理机器,而是一台由软件造出来的机器:安排对象怎样进入内存,函数怎样被调用,代码怎样变成处理器能执行的指令,也负责把不再使用的内存收回来。
这一层做得好,语言表面的写法不必改变,程序却可以启动更快、运行更快、停顿更少。
其中一项关键技术叫 JIT,Just In Time compilation,即时编译。程序先运行,虚拟机观察哪些代码反复执行,再把这些常走的路径编译成更快的机器指令。
它让 VM 可以在不修改语言入口的情况下,继续提高既有程序的执行速度。这一点,后来会成为 V8 与 Dart 最重要的差别。
这条履历很容易被讲成个人天才传说。但 Self、HotSpot、V8,以及后来的 Dart VM,都是团队工程。Lars 的连续性不在于独自发明了哪台机器,而在于二十多年里,他总在追问同一件事。
二零零六年,Google 的浏览器项目仍然保密。Lars 希望留在丹麦,于是 V8 的早期小组没有搬进加州总部,而是在奥胡斯附近工作。
[003]
V8 官方后来的回顾里,那个地点很具体:Lars 家的农场旁,一间附属建筑。
[003]
他们的目标也很直接。
做出地球上最快的 JavaScript runtime。
当时的网页正在改变。Gmail、Google Maps 这样的 Ajax 应用,把越来越多工作放进浏览器。
Ajax 不是一门语言,而是一组让网页不用整页刷新,也能在后台取数据、更新局部内容的做法。
页面开始像应用。JavaScript 的性能也就不再只是动画顺不顺,而逐渐变成浏览器能承载多复杂软件的上限。
二零零八年九月二日,Chrome 和 V8 同一天公开,V8 同时开源。
[003]
随后几年,JavaScript 引擎进入一场密集竞赛。WebKit 有 SquirrelFish,后来改名 Nitro;Mozilla 有 TraceMonkey;Microsoft 重做 Chakra。各家都在 JIT、对象表示和垃圾回收上推进。
可就在 Chrome 发布一个月后,Lars 接受丹麦媒体采访,谈的已经不只是机器速度。
[004]
这份 V8 发布后的同期采访支撑 Lars 对运行中试验和人的反馈时间的长期关注;它不证明后来的 Dart 或 Flutter 决策由此单独产生。
他说,对多数应用来说,最后百分之二十到三十的性能并不是最重要的。真正昂贵的,是程序员的生产力。
[004]
这份 V8 发布后的同期采访支撑 Lars 对运行中试验和人的反馈时间的长期关注;它不证明后来的 Dart 或 Flutter 决策由此单独产生。
他还描述了两种写程序的节奏。一种是修改代码,重新启动,看看能不能运行;不行,就再来一次。另一种是让系统保持运行,程序员直接在里面试验。
[004]
这份 V8 发布后的同期采访支撑 Lars 对运行中试验和人的反馈时间的长期关注;它不证明后来的 Dart 或 Flutter 决策由此单独产生。
这听上去像是“最快 JavaScript 引擎”故事里的一句反话。其实不是。对 Lars 来说,机器更快的价值,从来不只是一张跑分表。
它还应该让人的等待更短。
[004]
这份 V8 发布后的同期采访支撑 Lars 对运行中试验和人的反馈时间的长期关注;它不证明后来的 Dart 或 Flutter 决策由此单独产生。
二零一六年,Lars 和长期合作者 Kasper Lund 回看这段历史时说,创新、开源和友好竞争,让 JavaScript 的速度提高了两个数量级以上。
[005]
Lars Bak: So competitors, browser competitors, they had to at least match the performance. Otherwise, they looked like they couldn't do JavaScript. So I think this friendly, open competition for JavaScript has created the world we have today with browsers that are across the board very fast. And I'm very pleased with that.
[005]
这是一句参与者的概括,不是一张可以精确归因的成绩单。
[005]
但第一座山确实被跨过去了。JavaScript 不再只是浏览器里做表单校验和页面装饰的慢脚本,它开始承担编辑器、地图、协作工具和越来越完整的应用。
不过,V8 的成功有一个很容易被忽略的条件。
网站不需要为它改代码。
开发者昨天写 JavaScript,今天还是写 JavaScript。Chrome 把自己的执行层换掉,用户更新浏览器,就获得了新引擎。其他浏览器不需要接受 Google 设计的一门语言,也可以用自己的方法把同一段 JavaScript 跑快。
V8 改变的是共同入口下面的机器,不是入口本身。
Chrome 的成功当然不只属于 V8。稳定性、安全、产品设计、分发和更新机制共同构成了 Chrome。但这段经历留下了一个很容易被 Dart 继承的判断:只要下面的机器足够好,世界未必需要先改变。
Dart 接下来要做的,恰恰是改变入口本身。
两只钟
8:14V8 把 JavaScript 的执行速度向前推了一大步,却没有自动让大型 JavaScript 应用变得更容易开发。
先说好的一面。写一段脚本,放进页面,刷新浏览器,就能看到结果;同一套代码有机会跨操作系统运行,也不要求用户先安装应用。Kasper Lund 把这些称为当时 Web 的 good parts。它们正是 JavaScript 成为共同入口的原因,也是任何新语言都很难替代它的原因。
问题出现在应用继续长大以后。一个名字要靠上下文猜,一个公共对象被改动,影响沿着许多文件传到看不见的地方;多个团队都在工作,却没有人能把整个程序同时装进脑子里。
大型团队当然可以用框架、命名约定和构建工具补上结构。问题不是 JavaScript 不能写大项目,而是项目越大,越多看不见的脚手架要由项目自己发明,再由后来的人继续维护。
这些就是 Dart 团队在二零一一年列出的“bad parts”:结构难推理、启动慢、缺少类型记录意图,工具支持也相对弱。这是创建者对当时 Web 的诊断,不是对所有 JavaScript 项目的客观判决。
[006]
Dart 初始公告支撑结构化大型应用、类型与工具目标,以及 VM 直接执行和编译到 JavaScript 的双路径;“bad parts”仍按创建者诊断呈现。
几年以后,Lars 和 Kasper 把问题压缩成一句话:JavaScript 已经快了两个数量级以上,这让程序员变得更好了吗?他们给出的答案是否定的。
这也是 Dart 的心理起点。
二零一一年十月,Google 公开 Dart。它不是给 JavaScript 加几处语法,也不只是再造一台虚拟机。它想把刚才那些问题,分别放进语言、VM、库和编辑器里解决。
[006]
Dart 初始公告支撑结构化大型应用、类型与工具目标,以及 VM 直接执行和编译到 JavaScript 的双路径;“bad parts”仍按创建者诊断呈现。
与此同时,Dart 仍然保留两条运行路线:有 Dart VM 的地方直接运行,没有 Dart VM 的浏览器则把 Dart 编译成 JavaScript。
[006]
Dart 初始公告支撑结构化大型应用、类型与工具目标,以及 VM 直接执行和编译到 JavaScript 的双路径;“bad parts”仍按创建者诊断呈现。
“编译成 JavaScript”,可以理解成翻译。开发者写 Dart,编译器把它改写成浏览器已经认识的 JavaScript,再交给现有引擎。没有原生 Dart VM,程序仍然有一条进入 Web 的退路。
但 Dart 最初更独特的承诺,仍然来自那台 VM。
语言、类型、VM、快照、编译到 JavaScript。每一项单独看都有工程理由。把它们放在一起,Dart 得到了一次从表面到底层重新安排的机会,也把一整套新选择压到了采用者面前。
[006]
Dart 初始公告支撑结构化大型应用、类型与工具目标,以及 VM 直接执行和编译到 JavaScript 的双路径;“bad parts”仍按创建者诊断呈现。
因为原生 Dart 不只要求程序员换一种写法。
它还希望浏览器多接受一种语言,多维护一台 VM,多连接一套网页接口,多承担一种安全与兼容边界。
二零一一年,Lars 曾把 Dart 的未来类比 V8:V8 开源以后,其他产品也拿去使用;如果 Dart 的实现足够好,开发者试过以后喜欢,其他平台也可能把它集成进去。
这不是一个应该在知道结局以后被嘲笑的判断。Dart 当时还没有走完,编译到 JavaScript 的路线也确实存在。
Dart 与 V8 的分岔到这里才真正出现:V8 把复杂变化藏在共同入口下面;Dart 则把变化带到了入口处,要求使用者先做一次选择。
一年以后,这个分岔出现在开场那两把椅子之间。
二零一二年,丹麦奥胡斯。
一段四十三分钟的同场采访里,坐在 Lars Bak 左边的,是 Anders Hejlsberg。
Anders 同样来自丹麦。他先后主导过 Turbo Pascal、Delphi 和 C#。到这场采访发生时,他刚刚带领团队公开 TypeScript。
他不是因为没有重做语言的经验,才选择留在 JavaScript 里面。恰恰因为他做过,这个选择才更值得注意。
两个人刚刚把同一个问题摆到 Web 面前:JavaScript 应用越来越大,开发者需要更好的结构、类型信息和工具。
[007]
Anders 的原声和 TypeScript 发布说明共同界定 TypeScript 从 JavaScript 出发、编译后仍回到 JavaScript 的兼容路线。
先听 Anders 怎样给 TypeScript 划出边界。
Anders Hejlsberg: Well, the compiled output is JavaScript, so it sort of starts and ends with JavaScript.
[007]
Anders 的原声和 TypeScript 发布说明共同界定 TypeScript 从 JavaScript 出发、编译后仍回到 JavaScript 的兼容路线。
Anders 说,TypeScript 从 JavaScript 开始,也以 JavaScript 结束。
[007]
Anders 的原声和 TypeScript 发布说明共同界定 TypeScript 从 JavaScript 出发、编译后仍回到 JavaScript 的兼容路线。
这句话就是它的边界说明。
TypeScript 是 JavaScript 的 typed superset,也就是带类型的超集。合法的 JavaScript 可以直接作为起点;开发者加入类型标注、接口和工程结构;编译以后,类型被擦掉,交给浏览器的仍然是 JavaScript。
[007]
Anders 的原声和 TypeScript 发布说明共同界定 TypeScript 从 JavaScript 出发、编译后仍回到 JavaScript 的兼容路线。
这意味着 TypeScript 的编辑器和编译器可以先给开发者带来价值,不必等待 Chrome、Firefox、Safari 和 Internet Explorer 同时安装一种新运行时。
[007]
Anders 的原声和 TypeScript 发布说明共同界定 TypeScript 从 JavaScript 出发、编译后仍回到 JavaScript 的兼容路线。
Lars 听完,给了它一个很短的评价。
Lars Bak: I actually looked at it yesterday, but I have not coded in it, of course. It seems like a safe bet, right?
[008]
同场采访证明 Lars 在未实际编码 TypeScript 的限定下称其为 safe bet;Microsoft 的同期文章提供渐进演进与 Web 兼容的另一方语境,不能据此推导胜负因果。
Lars 把这条路线称作 a safe bet,一场更稳妥的赌注。
[008]
同场采访证明 Lars 在未实际编码 TypeScript 的限定下称其为 safe bet;Microsoft 的同期文章提供渐进演进与 Web 兼容的另一方语境,不能据此推导胜负因果。
这里的 safe 不是怯懦。它说的是风险放在哪里。
[008]
同场采访证明 Lars 在未实际编码 TypeScript 的限定下称其为 safe bet;Microsoft 的同期文章提供渐进演进与 Web 兼容的另一方语境,不能据此推导胜负因果。
TypeScript 接受 JavaScript 运行时和既有生态,牺牲一次彻底重做的空间,换取兼容和立即可用。
Dart 同时改变语言、VM、工具和浏览器,承担更高的采用风险,换取从语义到底层执行都能重新安排的空间。
两个人都知道 JavaScript 有问题。Anders 的问题不是 JavaScript 有没有坏,而是它有没有坏到值得整个 Web 把它换掉。
采访继续谈到标准演进。Lars 说,如果拥有无限时间,渐进改善当然合理。
[008]
同场采访证明 Lars 在未实际编码 TypeScript 的限定下称其为 safe bet;Microsoft 的同期文章提供渐进演进与 Web 兼容的另一方语境,不能据此推导胜负因果。
然后他说出了开场那句话。
我没有时间。
听到这里,它已经不再只是一句性格鲜明的回答,更不能被剪成傲慢宣言。
Lars 当时谈到 ES4 的折戟,谈到 JavaScript 标准落地的速度,也谈到 Web 正在和 iOS、Android 这样的封闭移动平台竞争。他担心的是,如果共同标准只能缓慢改变,Web 会不会在等待中失去下一代应用。
[008]
同场采访证明 Lars 在未实际编码 TypeScript 的限定下称其为 safe bet;Microsoft 的同期文章提供渐进演进与 Web 兼容的另一方语境,不能据此推导胜负因果。
从这个角度看,Dart 的 clean break,也就是和旧设计做一次更彻底的切割,不只是对整洁的追求,也是一种时间判断:与其等旧系统逐步修补,不如先造出一套完整的新系统,再用结果说服别人。
[008]
同场采访证明 Lars 在未实际编码 TypeScript 的限定下称其为 safe bet;Microsoft 的同期文章提供渐进演进与 Web 兼容的另一方语境,不能据此推导胜负因果。
Lars 想绕过等待,可 Web 恰恰不能只靠效果说服:一个厂商今天加入的能力,会成为所有厂商明天的兼容责任。
两把椅子,到这里变成了两只钟。TypeScript 接受 Web 已经在走的时间;Dart 想把语言、VM 和工具链一起拨快。
接下来要看的,不是谁更勇敢,而是一只由许多人共同维护的钟,能不能由其中一个人先动指针。
空位与开关
15:39如果把二零零八年的浏览器竞争拍成一场赛车,故事会很简单:引擎更快,产品更快,市场选择更快的那个。
可到了 Dart,这个比喻开始失效。第二座山没有清楚的终点线,因为所有参赛者还在共同铺路。
速度仍然重要,只是“快”开始被放进更多共同约束里:相同代码在不同浏览器里是否得到相同结果,旧网站还能不能运行,漏洞会不会越过安全边界,新能力能不能被多个实现验证。
Dart 后来成为 ECMA-408 标准,却没有因此自动跨过这座山。ECMA-408 规定 Dart 语言可以怎样实现,不代表各家浏览器已经同意把它装进去。
[009]
ECMA-408 官方页证明四个正式版本,Dartium 公告证明技术预览真实存在;两者都不能单独证明其他浏览器采用原生 Dart VM。
到二零一五年,ECMA-408 已经发布四版,装有 Dart VM 的技术预览浏览器 Dartium 也公开存在。可主流浏览器里,仍然没有第二个原生 Dart 实现。
[009]
ECMA-408 官方页证明四个正式版本,Dartium 公告证明技术预览真实存在;两者都不能单独证明其他浏览器采用原生 Dart VM。
二零一三年,Lars 已经公开承认这层差别。
我已经明白,做一门语言,比做一台虚拟机难得多。
虚拟机可以藏在执行层下面,语言却直接进入人的习惯、团队的代码、公司的投资和生态的长期承诺。
他还说,即使在 Google 内部也没有一根魔法棒,能让所有项目自动采用 Dart。他们仍要一个团队、一个团队地证明价值。
第二座山到这里才显出形状:Dart 缺少的不是语言、VM 或又一项 JIT 优化,而是让许多彼此竞争、也彼此依赖的人愿意共同承担它。
二零一五年三月二十五日,Dart 官方博客发布《面向整个 Web 的 Dart》。页面没有戏剧性的告别,只是把路线重新写了一遍:团队不再把 Dart VM 集成进 Chrome,Web 开发转而集中在编译到 JavaScript、跨浏览器调试和统一部署体验上。
[010]
2015 年官方公告直接证明终止 Chrome 原生 VM 集成计划,并把 Web 路线集中到编译 JavaScript、跨浏览器调试和统一部署;它没有宣布 Dart 项目停止。
终止的是浏览器原生 VM 计划。
[010]
2015 年官方公告直接证明终止 Chrome 原生 VM 集成计划,并把 Web 路线集中到编译 JavaScript、跨浏览器调试和统一部署;它没有宣布 Dart 项目停止。
不是 Dart 项目停止,不是可以在浏览器之外独立运行的 Dart VM 消失,也不是 Dart 从此退出 Web。
[010]
2015 年官方公告直接证明终止 Chrome 原生 VM 集成计划,并把 Web 路线集中到编译 JavaScript、跨浏览器调试和统一部署;它没有宣布 Dart 项目停止。
但 Chrome 里那个曾经为 Dart 准备的位置,空了下来。
几年工程投入以后,那台在 Dartium 中真实运行过的 VM 没有进入正式 Chrome。开场里那个位置,就在这一天真正空了下来。
[010]
2015 年官方公告直接证明终止 Chrome 原生 VM 集成计划,并把 Web 路线集中到编译 JavaScript、跨浏览器调试和统一部署;它没有宣布 Dart 项目停止。
官方公告给出的直接理由是,实际用户最终都要把 Dart 编译成 JavaScript,团队需要更好的 JavaScript 集成、更一致的跨浏览器调试,以及一条统一的部署路径。
[010]
2015 年官方公告直接证明终止 Chrome 原生 VM 集成计划,并把 Web 路线集中到编译 JavaScript、跨浏览器调试和统一部署;它没有宣布 Dart 项目停止。
其他浏览器对单厂商语言、双运行时和长期治理的顾虑,是理解背景的重要材料。但我们没有 Google 内部的决策权重表,不能把这一天写成“TypeScript 赢了”,也不能写成一场宫廷政变。
更不能把故事写成,Dart 失败以后,Google 才找来 Flutter 把它救走。
这个流传很广的版本有一个小问题:时间顺序不对。
要看清它,得把日历往回拨六周。
一个名叫 Sky 的移动 UI 实验,早在二零一四年十月就已经提交第一版代码。
[011]
三段源码历史共同证明 Sky 先以 JavaScript 开始,并在 Chrome 路线公告前完成 Dart 切换;它们不保存会议室里的完整选型理由。
它最初使用 JavaScript。
[011]
三段源码历史共同证明 Sky 先以 JavaScript 开始,并在 Chrome 路线公告前完成 Dart 切换;它们不保存会议室里的完整选型理由。
二零一五年一月底,仓库里出现一个名为 sky use dart 的实验开关。
[011]
三段源码历史共同证明 Sky 先以 JavaScript 开始,并在 Chrome 路线公告前完成 Dart 切换;它们不保存会议室里的完整选型理由。
接下来十几天,工程师把 Dart 绑定层搬进来,接上 VM 和快照,再把 V8 绑定层和 V8 本身断开。二月十日,一个由 Dart 驱动的旋转方块已经跑了起来。
[011]
三段源码历史共同证明 Sky 先以 JavaScript 开始,并在 Chrome 路线公告前完成 Dart 切换;它们不保存会议室里的完整选型理由。
两天以后,正式代码变更的最后一条写道:
拨下开关,让 Sky 使用 Dart。
[011]
三段源码历史共同证明 Sky 先以 JavaScript 开始,并在 Chrome 路线公告前完成 Dart 切换;它们不保存会议室里的完整选型理由。
这是二月十二日。距离 Chrome 公告,还有六周。
[011]
三段源码历史共同证明 Sky 先以 JavaScript 开始,并在 Chrome 路线公告前完成 Dart 切换;它们不保存会议室里的完整选型理由。
Sky 不是在 Chrome 放弃 Dart VM 以后才出现的救援项目。它先独立存在,先用 JavaScript,又在两条路线都还没有确定命运时,主动改用 Dart。
[011]
三段源码历史共同证明 Sky 先以 JavaScript 开始,并在 Chrome 路线公告前完成 Dart 切换;它们不保存会议室里的完整选型理由。
为什么?仓库保存了动作,却没有替我们保存会议室里的全部理由。今天没有一份完整的同期评分表能替我们回答。
[012]
Eric Seidel 的后见回忆提供 JavaScript 启动问题和 co-evolve 解释;正文保留回忆与同期工程记录之间的证据层级差异。
公开源码提交证明迁移发生过。二零二一年的参与者回忆给出后见解释:JavaScript 原型随着层次增加,启动变慢;团队评估多种语言,最后选择了能够和框架共同演进的 Dart。
[012]
Eric Seidel 的后见回忆提供 JavaScript 启动问题和 co-evolve 解释;正文保留回忆与同期工程记录之间的证据层级差异。
Chrome 公告一个多月以后,Eric Seidel 在 Dart Developer Summit 展示了已经迁移到 Dart 的 Sky。演示开场特意强调,屏幕上的界面不是由 Java 绘制,而是由 Dart 驱动。
[013]
Eric Seidel 的现场演示直接证明 2015 年 4 月的 Sky 画面已由 Dart 驱动;它不能证明 Chrome 放弃 Dart VM 的原因。
Eric Seidel: What you see here, it doesn't look super exciting. The exciting part is that there is no Java drawing this. This is drawn by Dart.
[013]
Eric Seidel 的现场演示直接证明 2015 年 4 月的 Sky 画面已由 Dart 驱动;它不能证明 Chrome 放弃 Dart VM 的原因。
这段声音仍然不能回答 Sky 当初为什么选 Dart。它能证明的是,到那个时间点,另一块屏幕已经真的由 Dart 画了出来。
[013]
Eric Seidel 的现场演示直接证明 2015 年 4 月的 Sky 画面已由 Dart 驱动;它不能证明 Chrome 放弃 Dart VM 的原因。
这里的 co-evolve,共同演进,是关键。
[012]
Eric Seidel 的后见回忆提供 JavaScript 启动问题和 co-evolve 解释;正文保留回忆与同期工程记录之间的证据层级差异。
Sky 选择的不是一门已经完成、以后只等着调用的语言。
它选择了一台现成的 VM,一套 JIT、垃圾回收、快照和开发工具,也选择了一个可以继续一起修改这些东西的团队。
[012]
Eric Seidel 的后见回忆提供 JavaScript 启动问题和 co-evolve 解释;正文保留回忆与同期工程记录之间的证据层级差异。
故事到这里发生了第一次转向。三月二十五日,Chrome 留下一个空位;可二月十二日,Sky 已经在另一边拨下开关。
接下来的问题不再是“谁愿意接受 Dart”,而是更少见的一件事:当一个真实产品把语言带进自己的世界,它会怎样反过来改变这门语言。
Flutter 把时间写进 Dart
21:25Sky 后来改名 Flutter。
这个名字今天常常和 hot reload、跨平台 UI、widget 连在一起。但在二零一五年,它先把一组很具体的新约束交给 Dart。
最直接的一条来自 iOS。
[014]
三份工程材料连接 iOS 发布限制、开发期 JIT、发布期 AOT 与 2015 年预编译快照实现;不能把后来成熟能力倒写成最初即完整存在。
Dart VM 原本擅长 JIT:程序先运行,虚拟机观察真实行为,再把热点代码编译得更快。
可普通 iOS 应用不能在设备上随意生成和执行新的机器代码。对依赖 JIT 的运行时来说,这不是慢一点的问题,而是发布边界。
[014]
三份工程材料连接 iOS 发布限制、开发期 JIT、发布期 AOT 与 2015 年预编译快照实现;不能把后来成熟能力倒写成最初即完整存在。
Flutter 因此需要 AOT,Ahead Of Time compilation,预先编译。
[014]
三份工程材料连接 iOS 发布限制、开发期 JIT、发布期 AOT 与 2015 年预编译快照实现;不能把后来成熟能力倒写成最初即完整存在。
JIT 是边跑边修路;AOT 是出发以前,就把要走的路铺成机器指令。
前者看得见真实运行情况,适合开发和试验。后者不必等程序热起来,也不需要在设备上临时生成代码,更适合发布、启动和受限制的平台。
在 Flutter 的原生移动开发与发布里,这正好形成了两只钟。它们不必争出谁更先进,只负责两种不同的等待。
开发时使用 JIT。应用保持运行,修改后的代码可以快速进入现有的 Dart VM;界面状态不必每次清空,程序员不用从首页重新走一遍。
[014]
三份工程材料连接 iOS 发布限制、开发期 JIT、发布期 AOT 与 2015 年预编译快照实现;不能把后来成熟能力倒写成最初即完整存在。
这后来成为 Flutter 最有辨识度的开发体验:stateful hot reload,保留状态的热重载。
发布时使用 AOT。代码提前编译,不在用户设备上临时生成机器指令,追求启动、体积和可预测性能。
[014]
三份工程材料连接 iOS 发布限制、开发期 JIT、发布期 AOT 与 2015 年预编译快照实现;不能把后来成熟能力倒写成最初即完整存在。
这套组合不是 Dart 在 Flutter 到来以前就摆在货架上的成品。二零一五年七月,Dart SDK 才加入早期预编译器;十月,Flutter engine 在 iOS 上切换到预编译快照,让设备启动时不必每次从空白开始。
[014]
三份工程材料连接 iOS 发布限制、开发期 JIT、发布期 AOT 与 2015 年预编译快照实现;不能把后来成熟能力倒写成最初即完整存在。
Flutter 带来的变化还不止“能不能发布”。
它改变了 Dart VM 认为什么叫性能。
一台服务器常常关心 throughput,也就是一秒钟总共完成多少工作。手机界面更敏感的是 latency:用户点下去以后要等多久,屏幕会不会因为一次停顿而掉帧。
吞吐量看机器做了多少。延迟看人等了多久。
二零一六年的 Dart SDK 源码提交里,Flutter 不再只是宣传页上的应用案例。Flutter Gallery、Nexus 4、主线程停顿、启动、快照大小和初始内存占用,直接出现在虚拟机的验收记录里。
[015]
commit 明确记录 Nexus 4 / Flutter Gallery 的 weak-handle processing 最大值 17.7ms 到 1.1ms;正文同时限制该数字只属于当时设备、应用和指标。
其中一次改动测量垃圾回收的一个子步骤。测试设备是 Nexus 4,工作负载是 Flutter Gallery;主线程上的最长耗时,从十七点七毫秒降到一点一毫秒。
[015]
commit 明确记录 Nexus 4 / Flutter Gallery 的 weak-handle processing 最大值 17.7ms 到 1.1ms;正文同时限制该数字只属于当时设备、应用和指标。
这个数字不能扩写成“Dart VM 普遍快了十六倍”。它只属于那台设备、那个应用、那项指标。
[015]
commit 明确记录 Nexus 4 / Flutter Gallery 的 weak-handle processing 最大值 17.7ms 到 1.1ms;正文同时限制该数字只属于当时设备、应用和指标。
十七点七毫秒,听起来几乎小到不值得讲。可一块六十赫兹的屏幕,每一帧大约只有十六点七毫秒。虚拟机里一个平时没人看见的垃圾回收步骤,已经足以独占甚至越过一帧。
[015]
commit 明确记录 Nexus 4 / Flutter Gallery 的 weak-handle processing 最大值 17.7ms 到 1.1ms;正文同时限制该数字只属于当时设备、应用和指标。
Flutter 把这段停顿变成了一个可以测量、可以修改、可以在源码提交里验收的问题。
[015]
commit 明确记录 Nexus 4 / Flutter Gallery 的 weak-handle processing 最大值 17.7ms 到 1.1ms;正文同时限制该数字只属于当时设备、应用和指标。
在 Web 中,Dart 要证明的是,整个生态为什么应该多接受一门语言。
在 Flutter 中,问题变成了,这个界面为什么刚才卡了一下。
前一个问题需要浏览器厂商、标准组织、网站和工具链共同承担。后一个问题,框架团队、VM 团队和语言团队可以在同一套代码、同一台设备、同一项性能测试上直接协作。
二零一六年,Lars 和 Kasper 在演讲后半主动打出一张标题为《与 Flutter 的蝴蝶效应》的幻灯片。
[016]
Lars 公开说明 Flutter 性能问题可以进入 Dart VM 修复,并提到为 Flutter 完成大量 VM 与 AOT 工程;正文不把它扩大为个人创建或单独决策。
架构图里,Dart VM 位于 Flutter 栈的底层。Lars 说,如果 Flutter 出现性能问题,他们可以到 Dart VM 里修。
[016]
Lars 公开说明 Flutter 性能问题可以进入 Dart VM 修复,并提到为 Flutter 完成大量 VM 与 AOT 工程;正文不把它扩大为个人创建或单独决策。
Lars Bak: There's always a blame game when it comes to performance, like these are the guys that's not performing and stuff like that, and it's never ending. The cool thing about this is if there's a performance problem, we can actually fix it in the Dart VM to get good frame rates. And for the Flutter system, we have actually done a lot of engineering in the Dart VM. We've done ahead-of-time compilation.
[016]
Lars 公开说明 Flutter 性能问题可以进入 Dart VM 修复,并提到为 Flutter 完成大量 VM 与 AOT 工程;正文不把它扩大为个人创建或单独决策。
这段声音不是装饰。它让“共同演进”变成一个很具体的动作:界面卡住以后,团队可以沿着技术栈向下走,走进 Dart VM,再在那里动手。
[016]
Lars 公开说明 Flutter 性能问题可以进入 Dart VM 修复,并提到为 Flutter 完成大量 VM 与 AOT 工程;正文不把它扩大为个人创建或单独决策。
它能证明,到二零一六年,Lars 已经公开把 Flutter 当成 Dart 未来的重要部分。它不能证明 Flutter 是他创建的、选择 Dart 由他一人决定,或后来所有语言变化都属于他。
[016]
Lars 公开说明 Flutter 性能问题可以进入 Dart VM 修复,并提到为 Flutter 完成大量 VM 与 AOT 工程;正文不把它扩大为个人创建或单独决策。
Flutter 到这里已经先改写了 Dart VM。等待不再以标准和浏览器版本计算,而是以一次启动、一帧画面和一次触摸计算。
而这种来自使用者的压力不会停在虚拟机里。它还会向上走,进入类型系统、编译器,甚至语法。
早期 Dart 的类型更像给工具的提示。真实的大型工程很快暴露出张力:如果类型只是一条建议,开发者和编译器究竟敢多相信它?到了 Dart 2,类型逐渐成为双方都能依赖的契约。
[017]
这些来源共同证明 Dart 2 的类型变化受多条工程压力影响,以及 Dart 2.3 的集合语法直接围绕 Flutter UI 构建;避免简化为 Flutter 单独改造 Dart。
这个变化不能简单写成 Flutter 改造了 Dart。strong mode 的起点早于 Flutter 的 AOT 工作,Web 编译器、Google 内部大型应用和编译工具整合都在施加压力。Dart 2 是多种使用者共同作用的结果。
[017]
这些来源共同证明 Dart 2 的类型变化受多条工程压力影响,以及 Dart 2.3 的集合语法直接围绕 Flutter UI 构建;避免简化为 Flutter 单独改造 Dart。
但 Flutter 确实让“使用者反过来修改语言”变得格外可见。
Flutter 把按钮、文字、布局乃至整个页面这样的界面构件统称为 widget。Dart 2.3 加入 spread、collection if 和 collection for。
[017]
这些来源共同证明 Dart 2 的类型变化受多条工程压力影响,以及 Dart 2.3 的集合语法直接围绕 Flutter UI 构建;避免简化为 Flutter 单独改造 Dart。
用普通话说,开发者可以在创建一组 widget 的地方,直接展开内容,直接写条件和循环。原本要绕到外面准备列表的 UI 逻辑,回到了界面结构所在的位置。语言开始顺着人搭建一块屏幕时的思路生长。
[017]
这些来源共同证明 Dart 2 的类型变化受多条工程压力影响,以及 Dart 2.3 的集合语法直接围绕 Flutter UI 构建;避免简化为 Flutter 单独改造 Dart。
这些不是决定语言命运的宏大宣言。
它们更像长期使用留下的磨痕:一段写法被重复太多次,一种不便积累得足够久,最后语言本身向使用者让出一点位置。
Flutter 到这里已经不只是 Dart 的使用者。它参与改变了 Dart 看待启动、延迟、类型和代码形状的方式。
到二零一八年,留下来的已经不是二零一一年的 Dart。故事也终于可以回到开场:回到 Lars,回到 Chrome 里那个空位,也回到他为什么总想缩短等待。
一秒以内
28:46二零一八年八月,Dart 2 正式稳定。四个月以后,Flutter 1.0 发布。
[018]
两份官方发布公告分别确认 Dart 2 stable 与 Flutter 1.0 的时间顺序。
但 Lars 已经离开了 Google。
他在这个故事的结尾缺席了。
公开履历通常把离开时间放在二零一七年。我们没有找到同期声明,不知道精确月份和原因。能确认的只有两端:二零一六年,他还在公开介绍 Dart 与 Flutter;到二零一八年五月,丹麦媒体已经把他称为 Google 奥胡斯开发部门的前负责人。
他没有留下对 Dart 的最终判词。这份缺席不是一道等待我们破解的谜,也不能替任何胜负叙事作证。
二零一八年七月,他和已经合作接近二十年的 Kasper 再次出现在丹麦媒体的报道里。他们正在开发面向物联网的 Toit,以及一台配套的虚拟机。我们不知道这些选择有多少来自 Dart 的经验,Lars 没有这样解释过。
[019]
这份离开 Google 后的采访证明 Lars 与 Kasper 已投入 Toit,并再次把一秒以内的反馈循环与程序员试验联系起来;它没有解释离职原因。
但那次采访留下了另一句话。
[019]
这份离开 Google 后的采访证明 Lars 与 Kasper 已投入 Toit,并再次把一秒以内的反馈循环与程序员试验联系起来;它没有解释离职原因。
Lars 说,他们的理念是:如果从编程到运行中的系统给出反馈,整个循环能压到一秒以内,就会鼓励程序员去试验,去想出新的东西。
[019]
这份离开 Google 后的采访证明 Lars 与 Kasper 已投入 Toit,并再次把一秒以内的反馈循环与程序员试验联系起来;它没有解释离职原因。
十年前,Chrome 和 V8 刚刚发布时,他也说过类似的话:系统应该保持运行,程序员应该能直接在里面试验;真正昂贵的,不是最后那一点机器性能,而是人的时间。
十年过去,语言变了,产品变了,公司也变了。那个问题没有变。
怎样让修改更快得到回答?
到 Flutter 1.0 发布时,这个问题已经有了一种很具体的形状。
[020]
Flutter 官方材料支撑调试模式下 VM、状态保留与一秒以内 hot reload 的产品表述;正文明确这不是 Lars 一个人的发明。
调试模式下,应用仍然运行在 Dart VM 里。程序员保存文件,更新后的代码进入正在运行的虚拟机。
[020]
Flutter 官方材料支撑调试模式下 VM、状态保留与一秒以内 hot reload 的产品表述;正文明确这不是 Lars 一个人的发明。
应用状态保留下来,手机上的界面随之改变。Flutter 当时的官方说明把热重载时间写成,一秒以内。
[020]
Flutter 官方材料支撑调试模式下 VM、状态保留与一秒以内 hot reload 的产品表述;正文明确这不是 Lars 一个人的发明。
这就是 stateful hot reload,保留状态的热重载。
[020]
Flutter 官方材料支撑调试模式下 VM、状态保留与一秒以内 hot reload 的产品表述;正文明确这不是 Lars 一个人的发明。
它不是 Lars 一个人的发明,也不是对 Dart 浏览器路线的补偿。把它说成个人翻身,会抹掉 Flutter、Dart、VM 和工具团队共同完成的工作。
[020]
Flutter 官方材料支撑调试模式下 VM、状态保留与一秒以内 hot reload 的产品表述;正文明确这不是 Lars 一个人的发明。
但它确实让 Lars 很早以前反复追问的那件事,在另一块屏幕上有了回声。
也许这才是故事最后出现的答案。V8 用更快的机器抬高了旧入口;Dart 没能让整个 Web 接受新入口,却在 Flutter 里被真实使用者重新塑形。
Chrome 里的位置仍然空着,Web 也没有把自己的时间交给 Dart。但 Lars 想缩短的那段距离,从修改代码到得到回答,却留在了一块手机屏幕上。
回答他的,不是浏览器,而是一次 hot reload。
片尾
31:43你刚刚收听的是《原代码》第二期,《跨过山丘》。
本期使用的项目公告、源码提交、标准文档、采访与演讲,都整理在本期 show notes 里。
对于没有可靠材料支持的离职原因和内部决策权重,我们选择把它们留为空白。
《原代码》不追求更新频率,追求每一期都能回到原始材料,重新理解一段已经被结果写得过于简单的技术历史。
感谢你的收听。
我们下期再见。