2012年1月,程序员加里·伯恩哈特(Gary Bernhardt)在 CodeMash 大会上做了一场不足五分钟的主题演讲《Wat》。他先演示了几行 Ruby 代码,再演示几行 JavaScript 代码,台下的笑声几乎没停过;其中 JavaScript 数组相加得到字符串 "1,23,45,6" 的那一幕,至今仍是编程圈流传最广的梗之一。但它绝不是一个段子合集——这可能是被引用最多的一次"类型系统公开课":它用最短的时间讲清了隐式类型转换、语言设计的权衡、以及"最小惊讶原则"对 API 设计者的意义。本文按视频叙事顺序逐段拆解它的笑点结构、每个"惊吓瞬间"背后的语言规范条文、这场演讲的真实影响与争议,并给出三条可以立刻用在代码评审里的建议。没看过视频,你能吸收全部知识骨架;看过视频,你会明白它为什么不是在嘲笑 JavaScript,而是在敲打所有写代码的人。
| 项目 | 内容 |
|---|---|
| 视频标题(英) | Wat |
| 标题(中译) | 《惊》(wat 为网络用语"什么鬼"的缩写) |
| 演讲者 | 加里·伯恩哈特(Gary Bernhardt),Destroy All Software 系列课程作者 |
| 所属频道/平台 | Destroy All Software 官网提供官方免费完整版;YouTube 有多个转载版本 |
| 演讲场合 | CodeMash 2012 大会(2012年1月,美国俄亥俄州桑达斯基) |
| 时长 | 约4分半钟 |
| 热度数据 | YouTube 多个转载版本累计播放达数百万量级(据公开转载页面;本文撰写环境无法实时访问视频页核实,动态数据仅供参考) |
说明:本文撰写环境无法实时访问 YouTube 页面,播放量等动态数据以量级描述为准。该演讲的官方免费版本托管在作者个人网站 Destroy All Software 的演讲页上,搜索"Wat CodeMash 2012"即可找到官方与转载版本。
《Wat》的结构像一篇极其紧凑的议论文,只是全部论据都被包装成了笑话。按叙事顺序,它分三幕。
第一幕:Ruby 的"合理"与"越界"。 伯恩哈特开场演示 Ruby:4 + 4 得 8,[1,2,3] + [4,5,6] 得到一个拼好的新数组——一切合理得令人舒适。然后他笔锋一转:[1,2,3] + 4,Ruby 抛出 TypeError: Array can't be coerced into Fixnum(数组无法被强制转换为整数)——这个报错措辞礼貌、语义清晰,观众甚至为这种"体面"鼓掌。接着他展示了 Ruby 自己的怪脾气:字符串和整数相加会报错,但字符串乘整数却完全合法,"4" * 4 会得到 "4444"。笑点在这里第一次升级:原来最"严谨"的语言,也有自己约定俗成的怪癖。这一幕的铺垫作用在结尾才会显现——它证明 wat 不是某个语言的专利。
第二幕:JavaScript 的"三次暴击"。 这是全片流传最广的部分。第一个例子:在 Ruby 里两个数组相加得到数组,而在 JavaScript 里,[1,2,3] + [4,5,6] 的结果是字符串 "1,23,45,6"——两个数组先被各自转成逗号分隔的文本("1,2,3" 和 "4,5,6"),再被当成字符串拼在一起,于是两个逗号紧贴成了一个"1,2"接"3,4"的鬼畜文本。第二个例子是著名的镜像不对称:[] + {} 的结果是字符串 "[object Object]",而把两个操作数交换位置写成 {} + [],结果却变成了数字 0。同样的两个值,换个顺序给出两种不同类型的结果——台下彻底笑场。第三个暴击之后,伯恩哈特对着屏幕轻轻吐出那个词:"Wat",全场爆笑。
第三幕:从笑话到主张。 演讲的后半段,他把"wats"明确定义为一种度量:一门语言、一个库、一个 API 里"令人惊讶的行为"的多少。他展示了几个连资深玩家也会愣住的结果,然后收束全片:下一次你设计 API,纠结某个方法到底该抛异常、该返回空值、还是该悄悄做一次隐式转换的时候,请记住你的选择会变成别人代码里的 wat。结尾他放出一张让全场哄堂大笑的照片,演讲在爆笑声中结束——但它的主张留了下来:惊讶是有代价的,而代价由使用你代码的人支付。
值得强调的是叙事节奏的设计:先 Ruby 后 JavaScript,先"合理"再"越界",先捧后摔。这个顺序让 JavaScript 的例子显得更荒诞,也悄悄完成了一个对照组实验——如果只看 JavaScript,你会以为这是场"黑 JS 大会";看完 Ruby 段落你才明白,他真正讨论的是所有语言共有的问题。
1. ECMAScript 规范里的加法算法:笑点其实是"按条文执行"的结果。 JavaScript 数组相加变成字符串,不是引擎的 bug,而是 ECMA-262 规范中加法运算符算法的严格执行结果。规范规定:+ 运算先对每个操作数执行 ToPrimitive 抽象操作——如果操作数是对象,依次尝试调用其 valueOf() 方法,若仍返回对象则改调 toString()。数组对象自身的 valueOf() 返回数组本身(仍是对象),于是规范落到 toString(),得到逗号分隔的字符串;两个操作数此时都是字符串,加法退化为字符串拼接。"1,2,3" 拼上 "4,5,6",就是 "1,23,45,6"。MDN 文档对加法运算符的描述与此一致。换句话说,JavaScript 没有失灵——它一丝不苟地执行了一条对数组场景毫无意义的规则。
2. {} + [] 得 0:一次语法分析层面的"错位"。 镜像不对称的谜底同样在规范里:当 {} 出现在语句开头时,ECMAScript 的语法把它解析为一个空的代码块(block statement),而不是空的对象字面量。于是 {} + [] 真正参与运算的只有 + []——一元加号把空数组转成数字,得 0。这在表达式上下文(比如把它赋给变量)里并不会发生。也就是说,这个 wat 的根源不在类型系统,而在语句与表达式的语法歧义,属于语言设计史上真实存在的尴尬妥协。
3. 布伦丹·艾克与"十天语言":隐式转换的历史包袱。 JavaScript 的宽容并非偶然。据其创始人布伦丹·艾克(Brendan Eich)本人多次公开回忆,这门语言1995年5月在网景公司只用了约十天就完成原型(初名 Mocha)。它服务的第一场景是网页表单:用户输入的一切都是字符串,"数字减字符串"必须能跑通,于是隐式转换成了默认世界观。伯恩哈特的演讲把这些历史包袱压缩成了几行代码的喜剧。道格拉斯·克罗克福德(Douglas Crockford)在2008年的《JavaScript: The Good Parts》中早就系统指出过这批陷阱,并给出著名建议——永远用 === 而不用 ==。可以说《Wat》是用四分钟把克罗克福德一整章的内容演了一遍。
4. Ruby 的一侧:消息传递与"最小惊讶"的民间传说。 Ruby 报错里的 "can't be coerced"(无法被强制转换)直接暴露了它的对象模型:一切运算都是向对象发送消息,对象有权拒绝。而 Ruby 社区长期以"最小惊讶原则"(Principle of least astonishment/surprise,POLS)自我标榜,创始人松本行弘也常被与这一原则联系在一起。不过"最小惊讶"在计算文化中的出处更早:据维基百科条目梳理,它在编程语境中的流行可追溯到1987年杰弗里·詹姆斯(Geoffrey James)的《编程之道》(The Tao of Programming)。《Wat》第一幕的讽刺正在于此——连以"最小惊讶"为旗号的语言,都有 "4" * 4 这样的越界行为;惊讶是相对的,取决于你站在哪种直觉上。
5. 演讲的文化辐射:从梗图到 TypeScript。 这场演讲流传的过程中恰好赶上 JavaScript 的爆发期(Node.js 于2009年发布、2011年前后开始企业化),"JavaScript 奇怪"的话语体系里有相当一部分插图都来自《Wat》。四个月后——如果从演讲当年算起——微软在2012年10月发布了 TypeScript 0.8,静态类型逐渐成为大型 JS 项目的标配;ESLint 的 eqeqeq 规则、"不要用 =="成为行业共识。一场四分钟的搞笑演讲,客观上成了类型安全运动最有效的传播素材之一。
第一,部分笑点是"解析语境"问题,不是类型系统失控。 {} + [] 得 0 只发生在语句开头这一种书写位置;在表达式语境中它同样返回 "[object Object]"。严格说这是语法歧义制造的惊吓,把它归咎于"JavaScript 类型系统混乱"并不精确。演讲作为喜剧无妨,但作为知识来源需要这层剥离——这也是看懂它和只记得它梗的区别。
第二,隐式转换是权衡,不是事故。 1995年的替代方案是让用户在脚本里手写 parseInt,对一个要抢在 Java 小程序之前跑起来的玩具语言来说,"先能跑"压倒"先严谨"。今天回头看,== 的隐式转换规则确实是负资产(所以规范社区后来用 Object.is、BigInt 等手段修补),但把三十年前的工程折衷当成"设计者愚蠢",是对历史语境的傲慢。演讲本身其实没有这种傲慢——它连 Ruby 一起打,立场是语言中立 的;放大这种傲慢的是后来的传播链条。
第三,"最小惊讶"本身经不起深究。 对 Ruby 的 "4" * 4,文本处理者觉得天经地义,数值处理者觉得毛骨悚然——惊讶因人而异,"最小"无从定义。这也是为什么这个原则在软件工程文献中常被批评为不可操作:它描述的是情绪,不是行为。真正可操作的版本是后文建议三的"让非法状态大声失败"。
第四,别把演讲读成"JavaScript 唯一有怪癖"。 这是流传中最常见的误读。演讲结构明确地先打了 Ruby——包括那个著名的报错消息——伯恩哈特本人是资深 Ruby 社区成员。他后来在 Destroy All Software 的另一场著名演讲《Boundaries》(2012年,SCNA 大会)中提出的"函数式核心、命令式外壳"架构理念,也比这场搞笑演讲更能代表他的专业水准。把《Wat》简化为"黑 JS",既误读了演讲,也错过了它关于 API 设计的真主张。
一句话总结:笑点全部真实存在、有规范条文可查;但每个笑点的"责任归属"各不相同——有的怪语法歧义,有的怪历史折衷,有的其实是设计权衡。 这正是它值得深读的原因:分清这三者,比记住十个梗有用得多。
建议一:今天就在项目里禁掉 ==。 具体做法:JavaScript 项目启用 ESLint 的 eqeqeq 规则,TypeScript 项目开启 strict 模式,代码评审时把"隐式转换"列为红线。《Wat》的全部 JavaScript 惊吓都源自隐式转换链,而生态早已给出零成本解法。这条规则的收益不是"代码更好看",而是把一整类"字符串被悄悄转成数字"的 bug 从你的代码库里永久移除。
建议二:给你的 API 做"wat 审计"。 把你写的每个公共函数拿出来,故意传入错误类型的参数,观察它是抛出清晰的异常、返回了 undefined,还是悄悄给出了一个看似合理的结果。第三种最危险——"1,23,45,6" 至少一眼假,而静默的合理才埋得下生产事故。设计守则:类型边界处做显式转换,转换失败时大声报错。这同样是"最小惊讶原则"唯一可操作的形式:让接口的行为与调用者的预期误差,可用"传入非法输入会发生什么"来测量。
建议三:用"先解析、后验证"改造参数处理。 学界与工程界有一篇流传很广的文章——亚历克西丝·金(Alexis King)2019年的《Parse, don't validate》——给出了与《Wat》教训同源的工程纪律:与其在各处零散检查"这像不像个数字",不如在入口处一次性把它解析成强类型表示,解析失败即报错,此后代码再无类型疑云。配合 property-based testing(如 JS 生态的 fast-check、Python 的 Hypothesis)随机喂入错误类型,你可以把自己的 API 变成一台"wat 探测器"。
flowchart TD
A[代码出现 a + b] --> B{a 是对象?}
B -->|是| C[valueOf 仍为对象<br/>则改用 toString]
B -->|否| D[原始值直接进入下一步]
C --> E{b 是对象?}
D --> E
E -->|是| F[valueOf 仍为对象<br/>则改用 toString]
F --> G{任一边是字符串?}
E -->|否| G
G -->|是| H[按字符串拼接<br/>数组变成逗号文本]
G -->|否| I[双方转成数字<br/>执行数值相加]
H --> J[得到 1,23,45,6 这类惊吓]
I --> K[正常结果]
这张图就是 ECMA-262 加法运算符的执行路径。惊吓全部产生在左侧支路:对象走到 toString、又有一边是字符串时,拼接就发生了。看懂这张图,视频里 JavaScript 的所有"魔法"就都变成了普通的规定动作。
==="的原始出处。本文由「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 等场景全覆盖。
