作为一个程序员,我是从切图开始职业生涯的。行业内一般把我这种编写用户界面 (UI) 的岗位,叫做前端开发。工作几年后我发现了个奇怪的现象,那就是整个前端圈子里,虽然大家常常谈 UI,但很少有人谈 GUI。

这话要从何说起呢?前端圈子里从上游到下游,强调的都是 UI 这个概念:

  • 最上游的开源基础库,清一色地宣称自己是 UI Framework 或 UI Library。
  • 业内同行交流时各种 PPT 里的架构图,常常在最上面画个名叫 UI 的模块。
  • 与非技术同事们协作时,大家关心的主要也是 UI 设计稿和 UI 的开发排期。

但由于工作需要,我接触了一些浏览器之外的 UI 框架。有意思的是,这时候我经常能看到 GUI 这个概念了。比如 Qt 框架的核心模块名就是 QtGui,GTK 和 wxWidget 都将自己定义为 GUI 库,更不要说名字里带着 GUI 的 Dear ImGUI 了。那么问题来了,说自己做的是 GUI 还是 UI,有很大的差别吗?

别小看 G 这个一字之差,这里的差别未必比 Java 和 JavaScript 的差别来得小。GUI 的 G 是什么的缩写?Graphics。少了这个 G 意味着什么?意味着今天的应用层 UI 开发,已经不太需要关心如何渲染 Graphics 图形了

很多前端同学可能会对此有意见,认为只要熟读了 Vue 或 React 等现代前端框架的源码,就已经对渲染这件事了如指掌了。如果你也这么认为,那么不妨问自己下面这么几个问题:

  • 浏览器是如何将布局数据计算为像素数据的,你能实现出原理类似的渲染器吗?
  • 浏览器在各个平台上的文字排版渲染结果是否一致,你能解释原因吗?
  • 你所负责的前端应用,其渲染性能还有多大的提升空间,你能量化地证明吗?
  • 你能设计实现出类似 RN 和小程序那样的 Hybrid 方案吗?
  • 你能自己控制 GPU 渲染管线,实现渲染的硬件加速吗?

它们不是孔乙己式的钻牛角尖面试题,而是对我所在的前端团队而言,非常具备探索乐趣和商业价值的实际问题。当开发普通的 Web 应用时,你大可不必关心它们。但如果你希望挑战 Web 的极限,在浏览器里跑起像 Office 和 PS 那样复杂的应用时 ,这些问题多半是绕不开的。在我的理解里,这些问题的答案,多数和现在浏览器里某种具体的 UI 框架关系不大,是更为「底层」的问题。

什么,难道 Web App 里还有比 React 和 Vue 源码更底层的东西吗?那就是 GUI 里的这个 Graphics,一个人类已经探索了四十多年的领域。

从 1979 年乔布斯造访施乐 PARC 算起,科技巨头们在 GUI 上的努力从未停止过。某种程度上说,前端开发的历史,不是从 jQuery 甚至 JavaScript 的诞生开始的,而是从 Macintosh 时代的上古平台开始的。当你用这种方式来回顾几十年来的 GUI 软件开发框架时,相信你会对软件工程架构的进步有全新的认识。

在我目前的理解里,GUI 应用的架构实现,依次走过了这些重要的阶段:

  • 过程化绘制时代 - 直接调用 drawLine / drawRect 风格的 API 来绘制像素。在我的树莓派里,只要 include X11 的头文件,就能用 C 语言体验这种硬派的 GUI 开发了。HTML5 中的 Canvas,其实也属于这种风格。
  • 面向对象抽象时代 - 纯粹过程化的代码,并不利于维护事件驱动的业务逻辑。比如,你需要自行计算来判断出某次点击应该选中哪个 UI 元素。在 MFC 和 GTK 的时代,人们实现了面向对象风格的 UI 框架。按钮、输入框等 UI 控件具备了实例方法,能更好地组织代码。
  • 界面与样式分离时代 - 用 C++ 系语言的代码来描述 UI,很容易写出面条式的丑陋代码。因此人们又引入了 XML 风格的语言,专门来表达嵌套式的界面。DirectUI 和 HTML / CSS / JS 基础上的经典 Web,都是这个时代的产物。
  • MVC 与 MVVM 时代 - 如何维护日益复杂的 UI 交互逻辑?许多框架引入了软件工程中的 MVC 和 MVVM 等设计模式。这个时代的代表产物有苹果的 Cocoa 和微软的 WPF,以及 Web 上的 Angular 1 框架。
  • 声明式组件化时代 - 为什么我们必须编写连接 UI 布局语言和业务逻辑的面条代码呢?Facebook 的 React 提出了新的 UI 开发思路。通过 JSX,很容易用 JS 来编写嵌套的、声明式的、更易维护的 UI 组件,并借助 JavaScript 的动态性来实时调试 UI。当前风口上的 Vue,Flutter 和 SwiftUI,都明显地借鉴了这种思想。

理解了 GUI 技术架构的演进后,就不难明白一些口水问题的答案了。例如在我一个关于拖控件式编程的知乎回答里,有些人反对我的论据是「拖控件比写代码更高效」。没错,拖控件属于界面与样式分离时代的产物,这确实比面向对象抽象时代编写面条代码更好。但到了声明式组件化时代,编写代码反而比拖控件更高效而方便了。

看起来一切都在进步,一切都欣欣向荣。长期被原生开发者们鄙视的 Web,甚至引领了最新的 UI 开发技术浪潮。但这有什么问题呢?

问题就是,Graphics 被封装得太好了,以至于都不需要被关心了

在最早的 GUI 时代,计算机甚至是没有 GPU 的。你绘制的文字和图像,都是内存里的 Bitmap。这时的 UI 框架作者们,必须认真考虑如何优化窗口化的 2D 渲染,并实现文字渲染、布局算法、点击测试等基础功能。而到了装机必备独立显卡的时代,如何用 GPU 来实现硬件加速的渲染,也变成了一个新的重要课题。最后,现代 GUI 应用基本都需要一个异步非阻塞的 Main Loop 配合子线程来保证渲染不卡顿,这也需要对操作系统的多线程技术有充分的理解。

可以看到,要成为原生 GUI 库的作者,多半需要扎实的操作系统和计算机图形学基础。但在今天流行的 Web 和 App 平台,这些计算机基础知识,已经被藏在厂商提供的 API 后面了。而流行的 React 和 Vue 等 UI 框架,其输入和输出都已经是相当结构化的数据,更不涉及困难的 Graphics 部分。虽然大多数场景下这些 API 是够用的,但当你需要更多的自由、更多的掌控时,你会发现自己被限制住了:你能用 JavaScript,直接读写某个 DOM 元素在某个像素下的颜色吗

这种能力上的限制,不仅影响了前端工程师的长期技术成长,还使得社区的风气都变得有些古怪。React 的声明式 UI 确实有用,但在其影响下函数式编程被社区奉为圭臬,某些布道者甚至连 for 循环都要抵制,这就有些舍本逐末了——要想掌握原生的图形渲染管线,几乎必须掌握非常命令式的编程。在渲染能力的限制下,整个社区的发展都倾向于关心代码自动格式化、强类型提示、编写更优雅的状态管理代码等层面的东西。这些技术方向固然有用,但多半只能增量地提高开发效率,并不能创造出全新的用户体验。

怎样突破标准化 API 的限制,创造出全新的用户体验呢?我很喜欢举 Photopea 的例子,这是个浏览器内的 PS 替代品(其国内版是我们部署的 ps.gaoding.com)。当我在捷克与作者 Ivan 交流时,他告诉我一般的 Web App 可能有 90% 的代码是第三方框架实现的,但他的 Photopea 则有 90% 的代码都需要自己写。他将 PS 中各种重要的算法实现到了 Web 上,最终获得了 WOW 级别的图像编辑体验。他明确地告诉我,他并不认为自己是一个 Front End Developer,而是一个 Programmer,能根据需要去学习并解决具体的技术问题。他对技术的态度,对我个人有很大的影响。

当然,我相信 99.9% 以上的前端同学,都不需要重复发明这么多轮子,更不需要在浏览器里实现一个 PS。但是现在国内的前端业界,对 GUI,对 Graphics,以至于对基础的计算机科学的理解,真的足够吗?很多抱怨 35 岁焦虑的同行根本没有认识到,前端框架快速变迁的真正原因,在于 GUI 背后的整个产业链,还具备巨大的发展和改进空间,仍然处于朝气蓬勃而充满机遇的时期。当下一个机遇到来的时候,它一定只会给有准备的人。

回顾计算机产业的历史,几个对普通人影响最为深远的进步,都是和 UI 分不开的的:微处理器浪潮普及了命令行 CLI;个人 PC 浪潮普及了桌面 GUI;Internet 浪潮普及了 Web 网页;移动端浪潮普及了掌上 GUI。这些十年一遇的变革,深刻地影响了我们的日常生活。我相信在未来,Web 应用一定会更多地使用到浏览器之外的能力,和传统的 Native 技术栈有更多的连接和融合。需要对整个 GUI 系统及其背后的计算机科学有着更深刻的了解,才能把握住这样的机会。

虽然我现在的 Title 是一个「前端工程师」,但我真正的理想,是成为一个自由的人。在职业生涯中,我追求的是发挥创造力的自由。当我入门时,浏览器给了我巨大的自由。但当我追求对渲染更高程度的掌控时,浏览器反而限制了我的自由。我确实很熟悉 JavaScript,但我也随时乐意拥抱不同场景下更合适、更优秀的技术栈,以发挥技术自由而非语言偏见作为准则,向更多的 GUI 开发者们学习。

GUI 技术栈大团结万岁!