人物采访 / T SALON

编辑内容

于航谈 WebAssembly 与前端成长:从运行原理到长期积累

回顾 2022 年 T Chat 的两段录播:从 WebAssembly 的运行原理、语言生态、生产案例与提案进展,写到于航的技术经历、写书与表达、团队协作、职业选择,以及学习、架构能力和成长机会。

于航在 T Chat 分享 WebAssembly 与前端成长的访谈封面
于航在 T Chat 分享 WebAssembly 与前端成长的访谈封面

本期视频发布于 2022 年。文中的技术状态、个人经历与观点均为当时情况。

这场 T Chat 先讨论技术,再回到做技术的人。第一段是于航的 WebAssembly 年度分享,从 JavaScript 的执行过程讲到模块、工具链、应用案例与生态进展;第二段通过主持人提问,展开他进入前端行业、写书、练习表达,以及对工作环境和长期成长的看法。

当期自我介绍页写明,于航(Jason Yu)在 PayPal 从事软件开发,主要关注前端,也研究 WebAssembly、C/C++、汇编和系统编程。他是《深入浅出 WebAssembly》的作者,曾开设《WebAssembly 入门课》等专栏。第一段 01:50–04:50

技术分享:WebAssembly 解决了哪一层问题

从 JavaScript 的执行过程说起

于航先用 JavaScript 引擎的执行流程解释背景:源代码需要经过解析,形成相应的内部表示,再由解释执行、运行时信息收集和优化编译等环节配合完成运行。引擎会关注频繁执行的代码,并在一定假设下生成更适合执行的机器码;如果假设失效,执行路径可能需要回退。

他用 x + y 说明动态类型带来的处理工作。同一个加号,遇到数字、字符串或对象时,涉及的行为并不相同;引擎要在执行过程中处理类型及转换规则。循环中某个变量的类型发生变化,也可能影响此前的优化假设。分享由此引出的问题是:如果程序的一些类型信息能够提前确定,执行准备过程能否有所不同?第一段 04:50–12:10

这是一种理解技术差异的切入方式。它不能单独证明 WebAssembly 在所有应用中都比 JavaScript 快,具体结果仍取决于工作负载、调用方式与运行环境。

抽象指令、模块文件与宿主环境

接下来,于航从几个层面解释 WebAssembly。它包含面向抽象机器的指令,可以作为不同语言的编译目标;这些指令及模块信息通常编码在 .wasm 二进制文件中,文件头也包含用于识别格式的信息。它不对应某一种固定的物理处理器,最终执行仍需要运行环境处理。

在浏览器里,配套的 WebAssembly API 负责加载、实例化和调用模块。与从 JavaScript 源码开始的流程相比,Wasm 模块已经具有明确的低层表示和类型信息;运行环境仍需对模块进行解码、验证,并通过编译或解释等方式执行,不能把分享中的流程示意理解成二进制文件无需处理就能直接运行。

这里也建立了一个贯穿后续案例的区分:WebAssembly 模块与运行它的宿主是两个层面。浏览器可以是宿主,浏览器之外的运行时也可以是宿主;模块怎样接入外部能力,要结合宿主提供的接口理解。第一段 12:10–17:20

一个加法函数怎样进入浏览器

为了让模块结构可见,于航展示了 WebAssembly 的文本表示 WAT。示例声明一个接收两个整数参数、返回整数的函数,取出参数后执行加法,再以 add 的名字导出。参数类型、函数体和导出关系都在这个小例子里出现了。第一段 17:20–21:50

实际开发通常不会靠手写这些指令完成复杂业务。分享随后改用 C++ 编写同样的加法函数,通过 Emscripten 构建模块。演示中的 EMSCRIPTEN_KEEPALIVE 用于保留需要导出的函数,extern "C" 则与 C++ 的符号命名问题有关。它们帮助解释了为什么一个普通函数在跨越编译与调用边界时,还需要额外处理。

浏览器侧的过程分为三步:获取模块的二进制数据,调用 WebAssembly.instantiate 实例化,再通过导出对象调用 add。这个例子把编写函数、构建模块和使用模块连在了一起。第一段 25:00–29:55

Emscripten 不只负责生成一个文件

在展示编译工具时,于航也解释了 JavaScript 胶水代码的作用。C/C++ 程序可能依赖文件读写、标准库或图形接口,而浏览器并不会自动具备与本地程序相同的运行环境。工具链除了生成 Wasm,还需要帮助连接这些依赖。

他用文件操作举例:程序调用的文件接口,可以通过浏览器中的虚拟文件系统等机制实现;某些图形能力也可以由浏览器接口承接。因此,移植工作的关键不只是把语言编译过去,还包括原有程序依赖的能力怎样在目标环境中得到对应实现。分享介绍的是这些适配思路,并不意味着宿主提供了完整操作系统或任意本地文件访问权限。第一段 21:50–25:00

语言支持有不同的实现路径

讲到语言生态时,于航区分了两种常见路线。一种是把 C/C++、Rust、AssemblyScript 等语言的程序编译成 WebAssembly;另一种是先把解释器或运行时编译成 WebAssembly,再通过它执行另一种语言的程序。

这解释了为什么 Python、JavaScript 等语言也会出现在 Wasm 应用的讨论中。例如,嵌入一个编译为 Wasm 的 JavaScript 引擎,可以让某个系统接受脚本作为扩展逻辑。多一层运行时并不代表这种方案没有用途,关键在于应用需要什么执行方式与边界。因此,看到一门语言出现在支持列表里,还要进一步看它究竟以哪条路径运行。第一段 30:00–33:10

从多媒体到云端:生态扩展也带来选型问题

随后,分享按领域介绍了当时的探索。面向 WebAssembly 设计的语言,尝试降低直接使用较底层语言的门槛;图像处理、音视频处理等场景,则把已有的计算逻辑和编解码能力带到网页中。仿真器的例子展示了另一种组合:用 Wasm 实现被模拟系统的核心逻辑,再配合浏览器的画布和事件处理,形成可以交互的应用。

云端部分涉及把 Wasm 工作负载接入容器调度环境;游戏领域关注引擎、物理计算等模块的复用和执行效率。区块链、物联网以及编译器、虚拟机等工具也被列入探索范围。它们共同说明,当时的讨论已经超出了单纯给网页加速的范围。第一段 33:10–40:45

但于航对实验性的前端框架保留了具体疑问。使用 Rust 等语言编写前端应用,需要考虑与 DOM 的交互方式,也需要考虑团队的学习成本和维护能力。如果掌握关键实现的人离开,其他成员能否接手?一种方案能够实现功能,并不足以证明它适合某个团队长期采用。这也是分享中从技术可能性转向工程选择的重要一段。第一段 38:30–40:15

eBay:把不同识别实现组合起来

eBay 的网页商品条码识别案例,把代码复用讲得更具体。分享中的架构图列出 ZBar、自有库和 JavaScript 库,由不同 Worker 执行。eBay 的原始工程文章说明,这些实现有各自擅长和不足的情况,因此最终方案组合多种识别方式,让能够取得有效结果的实现提供结果。

WebAssembly 在这里帮助已有的原生库进入网页流程,JavaScript 实现则继续参与其中。工程价值同时来自代码复用与方案组合,并不只取决于单个函数是否运行得更快。当期幻灯片称作二维码扫描;eBay 原始工程文章描述的是 UPC 商品条码识别。第一段 40:50–44:10;eBay 原始案例

Shopify:性能、执行边界与语言选择

Shopify 的案例转向平台如何接受可编程业务逻辑。商家或合作方可以根据自己的需要提供优惠等规则,平台则要执行这些外部提交的程序。这套在 Shopify 2020 年文章中介绍的历史架构,以 AssemblyScript、WebAssembly 模块和 Lucet 执行服务连接这一过程。

于航从几个方面解释这套设计。外部代码需要受到执行环境和宿主接口的约束;平台也要考虑处理业务请求的效率;以 Wasm 为中间形式,还可以给上层语言留下选择空间。分享同时提到了模块的导入关系,以及提前编译等执行准备方式。第一段 44:10–49:20

Shopify 在 2020 年的原始工程文章为这一历史方案提供了背景。这里的要点是平台怎样组织执行边界、依赖和业务接口,不能据此推导出沙箱里的所有操作都天然安全。本文也没有把这套架构改写成 Shopify 今天的产品实现。Shopify 当年的说明

四类提案,分别补足什么能力

讲完应用,于航回到标准与实现进展。他先介绍基础版本之后的提案及其推进过程,再选择四类能力展开。分享中的阶段与数量属于当年的快照,更值得保留的是它们分别试图解决什么问题。第一段 49:20–59:50

引用类型与 externref。 示例让一个浏览器对象的引用进入 Wasm 模块,再通过模块导入的 JavaScript 函数完成操作。这个引用对 Wasm 来说是不透明的:能够传递它,并不等于模块已经理解 DOM 或能直接操作任意对象。示例通过宿主函数修改页面文字,展示的是宿主与模块怎样协作。第一段 50:40–55:10

线程与原子操作。 分享讨论多个实例使用共享线性内存的工作方式:不同 Worker 可以执行任务,但访问共享数据时需要处理并发协调问题。原子操作为这类协调提供基本能力,并不自动替程序消除所有数据竞争。第一段 55:10–56:35

固定宽度 SIMD。 相比逐个处理数据,向量指令可以让一条操作作用于多个数据通道。于航用成组数字运算解释这一点,并联系到图像像素、音视频等重复计算较多的场景。这里讨论的是数据并行能力,不能把向量的位宽误读成固定数量的数据通道。第一段 56:35–58:35

批量内存操作。 内存复制、填充等工作如果只能拆成许多细小操作,会给生成代码和执行带来负担。分享用 memory.copy 等能力说明,为什么标准需要直接表达整块数据的操作。这是对模块底层操作能力的补充。第一段 58:35–59:50

WASI:从接口声明走到具体实现

WASI(WebAssembly System Interface)部分,把关注点放在浏览器之外的系统能力上。于航先回顾传统程序的路径:标准库提供接口,接口最终需要通过特定操作系统的实现完成文件、内存等资源操作。表面相似的调用,在不同平台下会落到不同实现。

再看 Wasm,应用可以面向一组系统接口编写,运行时则负责把这些接口接到所在平台。分享把应用侧的接口声明,与 Wasmtime 中的具体实现放在一起对照,说明两者怎样对应。这个层次关系也解释了为什么模块本身可移植,并不意味着它能脱离宿主独立获得文件或其他系统资源。

WASI 与核心计算指令的讨论由此形成分工:前者关注程序怎样接入外部系统能力,后者关注模块内部怎样表达和执行计算。分享还提到相关接口按不同领域推进,不能把当年的规划当成所有能力已经完成。第一段 59:50–64:45

工具、社区调查与当时的使用建议

最后一部分先谈工具。即使某项能力已经进入提案或规范,也要检查目标浏览器、运行时是否支持;调试体验同样影响落地。于航介绍了当时在浏览器中调试 C/C++ 编写的 Wasm 应用的进展,并提醒开发者关注工具链与具体环境。他还提及 WebAssembly 2.0 草案、Docker 与 WasmEdge 的结合、Wasmtime 1.0,以及模块连接、接口类型和社区治理等生态动态。第一段 64:45–67:10

分享展示的社区调查,从使用语言、未来意愿、应用场景和改进诉求几个角度观察趋势。于航的解读是:Rust 受到较多关注;JavaScript 的出现,需要结合把脚本引擎编译成 Wasm 的路线理解;Web 应用仍是重要场景,Serverless 和容器化等方向也在发展。开发者关心的既有线程、垃圾回收等能力,也有 API、调试与构建工具的完善。这里反映的是当时调查与讲者的解读,并非整个行业的市场份额结论。第一段 67:10–71:40

他的收尾建议是持续关注,并结合业务与团队条件选择使用。已有生产案例能证明某些问题有了可行解法,但具体项目仍要回答自己的需求、维护能力和运行环境问题。第一段 71:40–72:19

成长对谈:方向是在经历中逐渐形成的

从 VB、个人网站到前端工作

主持人首先问,于航为什么选择前端。于航的回答从早期接触编程讲起:一位家人给他的 VB 书,让他发现计算机除了运行游戏,还能通过代码完成自己想做的事情。随后,他接触脚本和 JavaScript,开始折腾个人网站与博客,逐渐进入 Web 开发。

到了大学,他又因为竞赛做过 Android 相关开发,毕业初期也尝试过相应工作。后来,工作环境的变化把他带到使用 PHP 做网站的团队,既写接口,也处理页面、样式等内容,再逐步把重心放到前端。WebAssembly 则是工作之外持续投入的一条技术兴趣线。第二段 00:00–08:50

他没有把这段经历讲成一开始就设计好的路线。主持人把关键节点概括为选择时,他进一步说明:有些选择来自过去的接触和熟悉感,有些则是对现实环境的反应,并不是每一步都经过理想化的长期规划。一次不适合自己的工作体验,也可能促使人换一个方向。

对刚接触技术的人,他认为前端容易较快呈现可见结果:做出一个页面或自己的博客,能带来成就感,推动继续探索。这解释了前端对他的吸引力,但他也承认,不同背景的人可能有不同的入行理由。第二段 05:00–10:10

写书的难处:写给谁,怎样保证准确

谈到《深入浅出 WebAssembly》,于航说自己最初是在研究过程中写博客,后来编辑联系他,才有了把内容组织成书的想法。对他而言,这也是把一个新技术系统介绍给中文读者的机会。真正开始后,困难首先出现在读者定位:内容如何兼顾前端读者所需的背景,又不让已经熟悉底层知识的读者觉得过于简单?

博客可以围绕一个问题展开,一本书却需要安排知识顺序、背景和深度。于航当时能参考的材料有限,只能从自己的理解、官方资料和社区内容中筛选,再决定哪些内容必须解释清楚。这种组织工作,与把已经写过的文章拼在一起有很大区别。第二段 10:10–12:40

第二个难处是内容核实。他回忆,早期资料常面向标准或实现的参与者,对普通开发者并不容易理解。他会在 Stack Overflow、GitHub 等渠道提出问题,也曾把积累的问题整理后向相关工具开发者请教。得到答复还不算结束,必要时仍要读规范、做实验,反向检查自己的理解和书里的表述。

因此,写书既需要持续输出,也需要反复验证。主持人随后提炼出一条可参考的起点:先写博客,逐步形成质量稳定的内容,再考虑更大的写作项目。书名和于航的作者身份,也可与他在 2020 年撰写的课程开篇词相互印证。第二段 12:40–16:00;于航的课程开篇词

写作和分享,都从能讲清一个小问题开始

主持人接着问,有技术积累的人怎样把内容写出来、分享出来。于航给出的办法是观察与练习:读书、读别人的文章,留意对方怎样组织章节和层次,再把自己刚学到的内容写出来,反复回看是否通顺、顺序是否合适。

练习不一定从长文开始。一个小知识点也能形成完整表达:先说明是什么,再补充必要背景,最后讲它在项目中有什么作用,或者引出哪些值得继续研究的问题。多次写作和重读,可以逐渐建立检查自己内容结构的能力。他也用自己写书前后不同章节的体验说明,组织能力是在过程中改善的。第二段 16:00–18:40

面对公开分享,他建议先从人数较少、主题较具体的沙龙练起。开始时准备逐字稿可以帮助稳定内容;随着熟悉程度和情绪控制能力提高,再逐渐把讲稿压缩为关键点,按照自己的思路展开,而不是一直依赖念稿。这需要持续练习,没有让人立刻从紧张变成熟练的捷径。

他还把练习场景延伸到日常会议:能否把自己的判断和方案向同事解释清楚,同样是在训练表达。分享能力不只发生在正式舞台上,工作中的一次说明也可以成为练习。第二段 18:40–21:40

兴趣与技术能力,不必被刻板印象捆在一起

对话中段转向轻松的话题。主持人从技术圈里一些人的特别爱好聊起,试着讨论兴趣与技术能力之间是否存在联系。于航没有认可这样的因果解释:认识的几个人恰好兼具某种爱好和技术能力,并不足以得出普遍关系;对当事人来说,它可以像游戏、追剧等活动一样,只是工作之外的一种兴趣和放松方式。

主持人随后把话题放回对程序员的刻板印象,提到自己认识的人在音乐、绘画、表达等方面的不同兴趣。两人的交流呈现的是一个更具体的群体:人们工作时的专注,不足以概括其全部生活;技术角色之外,也可以有多样的个人面貌。第二段 21:40–25:40

团队文化:共同目标、多元背景与业务取舍

进入工作话题后,主持人请于航比较不同环境中的体验。于航以自己当时在 PayPal 的团队为例,谈到工作与生活的平衡,以及围绕共同目标讨论事情的方式。他对团队的积极感受,来自同事和负责人愿意投入具体工作,也来自内部规范、培训与反馈渠道对不当行为的约束。这些是他在那一时期的个人观察。

国际化协作带来的另一个变化是多元背景。不同地区的同事可能有不同的沟通习惯和工作方式,不能预设所有人都遵循自己熟悉的一套。于航认为,这既需要尊重和适应,也会让合作中接触到的思路更丰富;至于是否喜欢这样的环境,则因人而异。第二段 25:40–30:10

谈到工程投入,他描述了一种围绕主营业务分配资源的取向。支付和相关业务需要的能力,会由团队持续建设;办公、协作等其他需求,则可能通过购买现成产品或外部服务解决。对他而言,关键是技术投入为哪些业务目标服务,而不是所有系统都由公司自己重做一遍。

这不等于团队不做技术基础建设。他也谈到前端与服务端工具、基础设施和标准社区等方面的参与,只是这些工作仍有业务目标和投入尺度。他也提及了股票、保险等员工配套安排。第二段 30:10–33:40

选择工作环境之前,先了解真实的日常

于航也讲了自己的适应过程。刚进入新的环境时,他会担心一些在此前经历中显得重要的小事,后来逐渐理解团队对个人自驱和相互信任的期待。适应并不只是在技术上交付任务,还包括理解别人怎样看待沟通、安排和责任。

主持人补充,另一类团队可能提供不同的技术深度和成长机会,因此不能只凭一种氛围判断所有人的最优选择。于航的回应是,先了解真实工作状况,再考虑自己是否适合。听说某家公司好,或者看到别人换到一种环境,并不足以替代对自己生活方式、文化适应和工作需求的判断。第二段 33:40–36:10

岗位准备:技术基础之外,还要能理解和回应

当主持人问到候选人需要哪些能力,于航先把讨论拉回具体岗位。按他当时的面试经验,问题应围绕简历中的经历和团队实际需要展开,技术能力与基础知识仍然重要。他提到算法基础,也强调不能只看候选人是否记住某类题目的答案。

沟通能力会在问答过程中体现:是否理解问题,回应是否对准重点,能否接住同事或负责人提供的信息与反馈。这些表现关系到进入团队后能否协同解决问题。第二段 36:10–38:00

语言要求则与岗位职责和协作环境相关。于航谈到,跨地区工作需要基本的英文读写,也需要在沟通问题时听懂信息、给出回应;承担更大管理或领导职责时,表达还会涉及受众、语气和信息组织。主持人随后结合自己的经历补充:能够直译中文想法,仍可能不足以完成同样细腻的英文沟通。

对于有相应求职方向的人,于航建议提前、持续地练习词汇、听力和表达,而不是临近需要时期待短期突变。这些内容是当时对能力准备的讨论,不是今天的 PayPal 招聘标准或统一门槛。第二段 38:00–41:50

学习可迁移的知识,也按需要深入源码

最后一个主要问题,是前端开发者可以怎样继续成长。于航把回答扩展到一般的软件开发:持续学习时,应特别关注能跨语言、框架和项目使用的知识,比如算法、架构设计和编程方式。

他用跟随框架版本学习作对比。如果只记住某一版代码在哪里、怎样实现,当框架换了实现,或者需要换到另一个框架时,已有知识可能难以迁移。理解设计关系与背后的方法,才更容易在新问题中使用。

不过,这个观点并不否定读源码。他明确给出了深入具体实现的情境:工作中需要解决问题、自己的项目需要,或本人确实感兴趣。先建立较稳定的知识基础,再按问题和兴趣进入易变化的实现细节,是他在这段回答中强调的学习顺序。第二段 41:50–44:55

架构能力从代码开始,成长也需要机会

于航随后提醒,不要把架构师的称号与脱离编码、大规模系统设计简单画等号。抽象、分层、组件之间的关系、可维护性和可扩展性,同样存在于一个普通功能或项目里。如果这些关系在代码层面都处理不好,很难仅凭一个头衔完成更大范围的架构工作。

因此,架构能力可以从日常编码开始练:让混乱的逻辑变得清楚,划分恰当的责任与层次,处理组件关系,并让后续修改有可承受的成本。理解微观结构之后,再逐渐把思考范围扩展到服务与系统。第二段 44:55–46:45

谈到行业未来,他没有给出确定的热点押注。他以技术发展常常慢于早期预期为例,说明预测某个方向何时爆发并不容易。在这种不确定中,能主动推进的是积累基础能力、硬技能与软技能,保持解决问题的竞争力。

与此同时,能力提升也不自动兑换成某个职位。于航讨论了组织结构与岗位空间的影响:即使一个人已经具备能力,也可能暂时没有适合的角色或机会。因此,个人能做的是持续准备、理解环境,并在机会出现时争取承担相应责任。这段回答将成长放回了个人努力与组织条件共同作用的现实中。第二段 46:45–49:53

查看本期原视频与分段信息:于航:WebAssembly 2022 回顾与前端专家成长 →