编程史上很少有人用五分钟改写一个行业的自嘲文化,Gary Bernhardt做到了。2012年初,他在CodeMash大会上做了那段后来被无数人称为“WAT”的演讲:先用几个Ruby的小例子垫场,再用JavaScript的加法运算符连环演示[] + []、[] + {}、{} + []逐层失控,最后一句"wat wat wat……Batman!"成为流传至今的行业梗。这条不到五分钟的视频之所以值得深读,是因为它用喜剧的外壳提出了一个严肃的命题:语言的怪癖不是bug,而是规范、历史与向后兼容共同作用的必然产物。本文按视频的叙事顺序逐层拆解,补上视频没讲的规范细节与语言设计史,再给出三条可迁移到日常工程中的认知工具。
| 项目 | 内容 |
|---|---|
| 视频标题 | WAT |
| 中文译名 | 《WAT》 |
| 主讲人 | Gary Bernhardt(技术 screencast 品牌 Destroy All Software 创办人) |
| 出处 | CodeMash 2012 大会现场演讲录像 |
| 发布时间 | 2012年初(据公开资料,精确日期以官方收录页为准) |
| 播放量 | 百万级(官方页面与各转载镜像累计,为量级描述而非实时值) |
| 时长 | 不到5分钟 |
| 类型 | 技术喜剧 / 语言设计分析 |
编辑注:本文撰写时无法实时访问视频页面,表中日期与热度为据公开资料的量级描述,精确数值请以官方收录页为准。视频内容要点为该演讲稳定流传的标准版本,可与公开转写和社区讨论交叉验证。
WAT的叙事结构非常精确。前半段讲Ruby,举的都是“优雅语言也有规则缝隙”的小例子——比如方法调用括号的省与不省在某些语法位置会产生截然不同的解析结果。这段铺垫的真正作用是建立对比:连以“最小惊讶原则”自居的Ruby,也存在让你惊讶的角落。然后话锋一转进入JavaScript,节奏从“偶尔的意外”直接跳到“系统性的荒诞”。理解这个结构很重要:它不是无差别嘲讽,而是有对照组的论证——语言的怪癖是程度问题,而程度由设计历史决定。
视频JS部分的核心机制只有一个:+运算符同时承担“数值加法”和“字符串拼接”两种语义。当两边都是数字时它做加法;当任一边是字符串时它做拼接;当两边都不是时,引擎要先把它们“转 primitive”再判断走哪条路。这条判定路径在ECMAScript规范里写得明明白白:加法运算符先对两个操作数执行ToPrimitive抽象操作(默认依次尝试valueOf和toString),若任一边得到字符串,则两边都转字符串做拼接;否则两边转数字做加法。
用这套规则回看视频里的连环演示,每个结果都严格可推导:
[] + []:空数组没有valueOf可用,转成字符串得空串,空串拼空串,结果"";[] + {}:数组转成"",对象转成"[object Object]",拼接后就是这串字符;true + true:布尔不是字符串,走数值路径,1 + 1 = 2。同一行代码字面上的“加号”,在引擎内部是一个分叉的决策树。 视频的高明之处在于它不解释这棵树,只演示树的果实——荒诞感来自观众脑中把+默认理解为算术,而规范说的是“先问类型,再定语义”。
视频里最让观众崩溃的一幕是:[] + []得空串,[] + {}得对象字符串,可把花括号挪到行首,{} + []却得到0。这不是转换规则变了,而是语法层面的身份变了:在语句起始位置,{}被解析成一个空语句块而不是空对象字面量,于是整行实际执行的是一元加号作用于空数组——+[]把空数组转成数字,空数组转字符串是"",转数字就是0。
换句话说,WAT里最“耸人听闻”的例子,恰恰是解析上下文歧义而非类型转换失控:同样的代码放进表达式位置(比如赋值号右边),结果就回到"[object Object]"。视频把这一点留给了观众自己去发现——这也是它作为喜剧的取舍:笑点优先,归因其次,但严格的答案一直躺在规范里。
视频还顺手戳了一下NaN:它是"Not a Number"的缩写,typeof NaN却返回"number",而且NaN不等于包括自己在内的任何值。这两条都源自IEEE 754浮点标准的设计——NaN是浮点体系里合法的表达式产物(比如0/0、无穷减无穷),用特殊位模式表示“运算失败”这件事本身;而“NaN不等于自身”是为了让数值比较的硬件电路保持简单一致。语言层的typeof照抄了浮点世界的户口登记:NaN确实住在数字类型里。这又是一层“怪癖背后有规范”的例证:你觉得荒诞的地方,往往是两个规范体系咬合的缝。
视频的终章是那句名场面:Array(16).join("wat")会生成一个长度为16的稀疏数组,元素全是空洞,join用"wat"填缝,于是得到15个"wat"连成的字符串;而把参数换成"wat" - 1,字符串减数字触发数值转换得NaN,join就吐出15个"NaN"。Bernhardt随即念出:"wat wat wat wat……Batman!"全场爆笑。
这个收尾在结构上完成了闭环:一行代码同时踩中了稀疏数组、字符串数值转换、join语义三个独立机制,每个机制单独看都合理,叠加起来就成了表情包。它传递的元信息是:语言的怪癖通常是多个各自合理的局部规则的正交组合,而不是某一条规则的疯狂——这也是为什么“背怪癖清单”没有用,理解机制才有用。
JavaScript的历史起点是1995年5月:网景公司的Brendan Eich在约十天内写出了这门语言的原型(项目名Mocha,随后叫LiveScript),同年12月,网景与Sun达成市场合作,语言改名为JavaScript——尽管它与Java只有营销上的亲戚关系。行业里流传的那句玩笑“JavaScript之于Java,好比雷锋之于雷峰塔”,说的就是这段历史。十天的工期意味着语法要大量借用现有语言(C风格的语句、Java风格的表面语法、Self的原型继承、Scheme的函数语义),而隐式类型转换规则则更多是快速工程决策的结果。1997年6月,ECMA国际发布了第一版ECMAScript标准(ECMA-262),从此这些快速决策被铸进了国际规范。
理解这条时间线,就能理解WAT的深层结论:怪癖不是恶意,是历史速度的化石。 十天写出的转换规则,因为“不能破坏网页兼容性”的行业铁律,此后三十年一条都不能改。
WAT implicitly触到的理论框架是编程语言的标准分类学:静态/动态描述类型检查发生在编译期还是运行期,强/弱描述语言允许多少隐式类型转换。类型系统研究里被引最多的经典文献之一,是Luca Cardelli与Peter Wegner 1985年发表的《On Understanding Types, Data Abstraction, and Polymorphism》,它奠定了这套讨论的坐标系。在这个坐标系里,JavaScript是典型的“动态+弱”组合——类型在运行期检查,且转换高度隐式;Ruby是“动态+强”——类型运行期检查,但1 + "1"会直接抛错而不是悄悄猜测你的意图。这正是视频选择Ruby做对照组的理论依据:荒诞感的分母不是“有没有规则”,而是“规则替你做多少决定”。
视频之后,规范本身也在演化。ES5(2009)收紧了部分转换行为;ES2015(ES6)引入了著名的Symbol.toPrimitive,允许开发者自定义对象参与转换时的行为;现代引擎对语句位置{}的解析行为也有了更一致的处理。与此同时,社区用===严格相等运算符绕开了==的转换迷宫,ESLint的eqeqeq规则把这条纪律制度化。值得注意的时间巧合:WAT演讲的同年10月,微软发布了TypeScript 0.8——一门专门给JavaScript加静态类型的语言。可以说,2012年前后,整个生态用工具链集体回应了WAT提出的问题。
Ruby社区长期标榜POLS(Principle of Least Surprise,最小惊讶原则),由语言之父松本行弘(Matz)推崇的设计哲学:语言行为应当符合直觉,能猜到。WAT用Ruby做开场垫场是有深意的——即便持有这条哲学的语言,在语法歧义上仍有缝隙;而JavaScript从没把“不惊讶”列为设计目标。两种哲学没有绝对优劣,但它们产出的语言性格截然不同。
视频列举的所有输出结果,在对应的运行环境下全部严格可复现,且都能从ECMA-262规范逐步推导,这一层没有任何夸大。它对生态的实际影响也真实可查:“wat”成为开发者社区的通用梗,衍生出专门收集JS怪癖的社区项目;用===替代==成为几乎所有JS编码规范的标准条款。一段五分钟的喜剧能在事实上塑造一个行业的代码习惯,这本身就是罕见的传播学案例。
首先是归因简化。{} + []的0本质是语法解析的位置歧义,不是类型转换失控——把这两类问题混在一起演示,戏剧效果拉满,但让不少观众把“语法歧义”记成了“转换混乱”。其次是样本偏差。演讲选取的都是刻意构造的边角案例,日常业务代码极少写出Array(16).join("wat" - 1)这样的表达式。第三是时效性。ES2015之后,转换行为可被开发者显式控制,现代代码库配合linter与TypeScript,隐式转换触发的真实缺陷已大幅下降。以2026年的工程标准看,WAT描述的荒诞更多是“历史遗留”而非“现行风险”。
“弱类型是否必然导致事故”本身在语言设计圈是长期争论:Python同样有隐式的truthiness转换([]为假),Lisp的全真值方案则认为“显式转换义务”本身就是负担。WAT没有参与这场辩论的义务,但如果把它的笑点直接等价于“弱类型不行”的结论,就超出了视频实际支持的范围。
这套方法完全可迁移:任何语言里遇到反直觉行为,第一反应应当是找它的规范定义(对JS来说就是ECMA-262和MDN对ToPrimitive、加法运算符的算法描述),把“这语言疯了”重构为“这条规则+这条规则+位置上下文,推出了这个结果”。机制思维一次投入,终身复用——下次你在别的语言里撞见隐式转换、运算符重载或解析歧义,你已经知道去哪里找答案。
加法运算符的双重身份是所有怪癖的根源,这给API设计(包括你自己写的业务函数)一个清晰教训:一个函数名只应承载一种语义。update既改状态又发请求、get既查缓存又回源、+既算术又拼接——每一次“顺手复用”都在为未来的WAT时刻埋雷。设计接口时先问一句:这个调用会不会因为参数类型的细微不同而走上完全不同的路径? 会,就拆开。
JS生态的答卷是===、eqeqeq、TypeScript——用工具把共识固化成强制约束。团队同理:凡是“大家都要记得”的规则,最终一定会有人不记得;能写成lint规则、类型标注或CI检查的约定,就不要写在口头规范里。WAT真正的遗产不是嘲笑,而是把语言缺陷转化为工程基础设施的完整示范。
flowchart TD
A["表达式 a + b"] --> B{"两边都是数字?"}
B -->|是| C["数值相加<br/>非法运算则得 NaN"]
B -->|否| D["先做 ToPrimitive<br/>依次尝试 valueOf 与 toString"]
D --> E{"任一边是字符串?"}
E -->|是| F["两边转字符串拼接<br/>如数组加对象得 object Object"]
E -->|否| G["两边转数字再相加<br/>如 true 加 true 得 2"]
C --> H["NaN 不等于任何值<br/>包括它自己"]
F --> I["花括号在行首变语句块<br/>解析歧义叠上转换规则"]
G --> J["多个局部规则正交组合<br/>才有五分钟的 WAT"]
本文由「51学通信」技术团队出品。51学通信由通信行业18年老兵爱卫生(微信ID: gprshome201101)维护,聚焦5G/6G核心网、3GPP标准解读、通信前沿技术深度分析。 出版作品有《5G核心网原理与实践》《IMS原理与实践》等4本通信专业书籍。知识星球「51学通信」提供深度技术答疑和行业洞察。
[!tip] 51学通信知识星球 星主:爱卫生(微信:gprshome201101)——公众号「51学通信」创办者,19 年通信行业运维与讲师经验,著有《GPRS网络信令实例详解》《EPC原理与实践》《5G核心网原理与实践》三本纸质书(各大电商平台有售)。 星球定位:深耕 5G 核心网、IMS、NFV/AI、云原生等 ICT 领域,主打运维岗位与信令分析。 当前规模:5200 篇主题 / 990 个回答,198 元/年。 加入福利:加入星球后请加爱卫生微信,拉您进知识星球年度陪伴与答疑群——不限量回复您的各种问题,另有群内分享。 AI 加持:51学通信知识星球已作为 skill 接入 WorkBuddy,使用 deepseek-4-flash 在海量星球帖子中精准找贴、找视频、找 FAQ 和案例,做总结、找文档和 PPT 等场景全覆盖。
