第十一章:JavaScript、HTML与CSS:会动的“活文档”

欢迎回来,时空旅行者。在你的时代,文档就是文档——它们是静态的,是死在纸上或屏幕上的文本。你运行一个程序,程序结束,文档(输出)就诞生了。但在我们这个时代,“文档”自己就是程序。

最初的万维网(World Wide Web)确实是“死”的——它们只是从遥远的服务器上发来的静态文本文件,你的浏览器负责忠实地将其渲染出来1。但随后,为了让这些“死文档”动起来,为了让它们能响应你的点击、播放视频、在你滚动时加载新内容,一个(极其混乱的)“三位一体”诞生了。

本章,我们将解剖这个让现代世界得以运转的“活文档”三位一体:

  1. HTML (超文本标记语言): 网页的“骨架”,定义“万物应在何处”1
  2. CSS (层叠样式表): 网页的“皮肤”,定义“万物应是何貌”1
  3. JavaScript (JS): 网页的“神经系统”,定义“万物应如何行动”1

你可能会以为,这三者的关系是建筑师、室内设计师和电气工程师之间那种有序的专业合作。哦,不。在接下来的5000字里,你将看到,这三者的关系更像是在一场龙卷风中,由三个互相看不顺眼的醉汉,一边打架一边抢着盖房子。而我们,就住在这栋房子里。

“骨架”与“皮肤”的史前战争

在JavaScript这个(吵闹的)主角登场前,我们得先谈谈它的两个(更吵闹的)室友:HTML和CSS。它们之间的地盘之争,定义了20世纪90年代的整个互联网。

HTML的黑暗时代:用<table>搭建的“电子表格”城市

HTML,即超文本标记语言,是网页的结构。在理想情况下,它应该只负责“语义”(Semantics)——告诉浏览器:“这是一级标题”、“这是一个段落”、“这是一个列表”。

但在90年代,当CSS还只是一个美好的愿望时,人们有了一个迫切的需求:布局(Layout)。他们想把一张图片放在左边,把文字放在右边。HTML本身并没有提供这个功能。于是,程序员们(可能是受够了挫折)环顾四周,在HTML标签里找到了一个“勉强能用”的工具:<table>标签,也就是“表格”。

是的,你没听错。在90年代末到21世纪初,整个互联网的“布局”都是用HTML表格搭建的2。这相当于你决定用Excel电子表格来设计《蒙娜丽莎》。

  • 为什么是表格? 因为它(讽刺地)“有效”。表格天生就能处理复杂的“响应式”布局:你可以合并单元格、设置列宽,而且浏览器总能(相对)一致地把它显示出来2
  • 代价是什么? 代价是灾难性的。
    1. 语义灾难: 你的网页源代码变成了一堆毫无意义的<table><tr>(表格行)和<td>(表格单元格)的“俄罗斯套娃”3。这对搜索引擎和(尤其是)视障人士使用的“屏幕阅读器”来说,简直就是一场噩梦2
    2. 渲染噩梦: 浏览器必须下载整个表格(以及所有嵌套在里面的表格),才能计算出布局,然后一次性将其显示出来4。如果你访问过那个时代的网站,你一定记得那种体验:你盯着一片空白的屏幕长达数秒,然后“duang”的一声,整个(通常很丑的)页面瞬间全部出现4

这场关于“语义”和“实用”的战争(<div> vs <table>)持续了近十年。守旧派(实用主义者)认为:“用CSS实现两列布局需要5倍的时间,而且到处是Bug,而<table>就是能用!”2。革新派(语义纯粹主义者)则坚持认为:“表格是用来装数据的,不是用来布局的!”3

最终,CSS赢了。但在此之前,CSS自己也必须经历一场血腥的内战。

CSS的“浏览器战争”:闪烁、滚动与“盒模型”灾难

CSS,即层叠样式表,是网页的“皮肤”。它的发明是为了将“外观”与“结构”分离。这是一个崇高的目标。然而,它的诞生恰逢“浏览器大战”最激烈的时期——主要是网景(Netscape)和微软(Microsoft)之间为了争夺互联网霸权而进行的“殊死搏斗”。

在这场战争中,“标准”是第一个牺牲品。两家公司都疯狂地往自己的浏览器里添加私有的、不标准的“功能”,试图“锁定”开发者。这导致当时的网页设计变成了一种“黑魔法”。

网景公司(Netscape)决定,网页需要更多的“活力”。于是他们发明了<blink>(闪烁)标签5。被这个标签包裹的任何文本,都会在你的屏幕上……没错,永恒地、令人抓狂地闪烁6。这在视觉上等同于有人在你耳边不停地按喇叭。设计师们(谢天谢地)普遍鄙视它7,但它却成了那个时代“廉价感”的代名词。

展品B:微软的<marquee>标签

微软的Internet Explorer (IE) 团队看到了网景的<blink>,心想:“呵,就这?” 于是,他们以一种“帮我拿着啤酒”的姿态,推出了<marquee>(滚动)标签6

这个标签能让你的一行(或多行)文本像火车站的电子公告牌一样,在屏幕上从右向左(或从左向右)循环滚动7。如果你把<blink><marquee>结合起来,你就创造了一个90年代的“数字地狱”。这场战争的本质不是关于“标准”或“可用性”,而是关于谁能更快地推出更俗气、更不标准的“功能”7

展品C(史上最臭名昭著的Bug):IE盒模型缺陷

如果说<blink><marquee>是战争中的小规模冲突,那么“IE盒模型缺陷”(Internet Explorer box model bug)就是那场引爆了核弹的决战8

这个Bug极其隐蔽,但后果极其严重。它关乎一个最基本的问题:一个“盒子”的宽度到底是多少?

  1. W3C标准(世界其他地方)的看法: 如果你定义一个盒子width: 100px;,并给它padding: 10px;(内边距)和border: 2px;(边框)。那么这个盒子的实际总宽度是:100px (内容) + 10px (左内边距) + 10px (右内边距) + 2px (左边框) + 2px (右边框) = 124px8
  2. 微软IE 5(“怪癖模式”)的看法: 如果你定义一个盒子width: 100px;,并给它padding: 10px;border: 2px;。IE会认为,你说的100px就是全部。因此,它会把内边距和边框包含在内8。这个盒子的实际总宽度就是100px。而留给“内容”的空间只剩下:100px - 10px - 10px - 2px - 2px = 76px。

用一个(美食)比喻来解释:

  • W3C标准: 你点了一个“12英寸”的比萨。他们会给你一个12英寸的“饼底”(内容区),然后再在外面加上2英寸厚的“芝心饼边”(边框和内边距)。最后你拿到的比萨总直径是16英寸。
  • IE模型: 你点了一个“12英寸”的比萨。他们会拿一个12英寸的“盒子”(总宽度),在里面塞进2英寸的“饼边”(边框和内边距),最后只留给你一个8英寸的“饼底”(内容区)9

这个“缺陷”意味着,你写的任何CSS布局,在IE上和在其他浏览器上,看起来会完全不同8。这一个Bug,催生了整整一代“CSS黑客”——他们被迫编写各种恶心的、特定于浏览器的“CSS过滤器”或“条件注释”,来“欺骗”IE,让它正确显示布局8

当IE 6发布时,它修复了这个问题……但只在“标准兼容模式”下10。如果你不使用一个特定的HTML“文档类型声明”(DOCTYPE),IE 6会为了“向后兼容”(兼容那些依赖IE 5“缺陷”的旧网站)而故意退回到“怪癖模式”(Quirks Mode),继续使用那个错误的盒模型8

这场混乱的遗产,就是流传至今的各种关于CSS的“模因”(Meme)。

现代的创伤后应激障碍:“CSS 太棒了”

如果你在程序员的社交媒体上闲逛,你会看到两张经久不衰的图片,它们完美地总结了CSS带给人们的“创伤后应激障碍”(PTSD)。

模因一:“如何让一个DIV居中”

在CSS的黑暗时代(也就是2010年之前),想把一个元素在页面上“垂直且水平居中”,是一门名副其实的“黑暗艺术”11。这本应是布局最简单的任务,却需要各种反直觉的“黑魔法”,比如设置margin: 0 auto;(只能水平居中)12,或者使用vertical-align(但它只在特定情况下有效),或者用“绝对定位”配合“负外边距”进行复杂的数学计算。

这催生了“如何居中一个DIV”的史诗级模因11

讽刺的是,在现代CSS中,这已经(终于)变得极其简单了。你只需要在父元素上写三行代码13

.parentDiv {
 display: flex; /* 启用“弹性盒”布局 */
 justify-content: center; /* 水平居中 */
 align-items: center; /* 垂直居中 */
}

然而,这段代码花了近20年才成为“标准”14。这个模因之所以能流传至今,不是因为它现在有多难,而是因为它曾经难到令人发指12

模因二:“CSS is Awesome”

An image to describe post

这个模因更直白15。它通常是一张图:一个蓝色的方框,上面写着“CSS is”,下面紧跟着一个灰色的方框,写着“Awesome”。然而,“Awesome”这个词由于太长,直接溢出了灰色的方框,把布局撑得一塌糊涂16

这张图(以及它在T恤和马克杯上的无数变种15)完美地讽刺了CSS的本质:你(看似)简单地定义了几个规则(“这是一个方框”、“这是文字”),但这些规则之间(你不知道的)默认交互方式,却会导致毁灭性的、反直觉的(而且通常很难看)的结果17

这就是你即将认识的第三位室友——JavaScript——所要面对的“居住环境”。一个由<table>和“盒模型缺陷”拼凑起来的“骨架”,以及一套由“溢出”和“居中难题”构成的“皮肤”。


JavaScript:那个“粗糙但活力四射”的幸存者

现在,我们来谈谈这个三位一体中的“神经系统”。如果说HTML和CSS的历史是“混乱的”,那么JavaScript的历史就是一场“史诗级的闹剧”。

它是一个粗糙的、设计上有缺陷的、在极度仓促中诞生的幸存者。但它最终赢得了(几乎是)一切。

创世原罪:10天诞生与“世纪级碰瓷”

故事始于1995年。网景公司(Netscape)迫切需要一种“胶水语言”,一种能让网页设计师(而非重度Java程序员)也能轻松上手的脚本语言,以便在浏览器中实现一些简单的交互(比如“点击按钮时弹出一个警告框”)18

他们找到了Brendan Eich(布兰登·艾克)。

根据艾克本人的说法,他只花了10天时间就开发出了JavaScript的第一个版本18。10天。创造一个后来统治了世界的编程语言。

你可以想象,10天之内设计出来的语言,必然充满了妥协、捷径和(我们稍后会看到的)一些“永久性疤痕”。

史诗级的“蹭热点”

这门语言最初被命名为“Mocha”(摩卡),后来改名为“LiveScript”。但就在它即将随Netscape Navigator 2.0发布时,市场部的人想出了一个(现在看来)绝妙的(或者说无耻的)营销点子。

当时,Java语言正值巅峰,是“高级程序员”中的超级巨星19。于是,网景的市场部决定,把这个“LiveScript”改名为……JavaScript19

这个命名,是编程史上最成功的一次“碰瓷”。

  • Brendan Eich亲口承认: 使用“Java”这四个字母,“完全是出于行销上的考量”18
  • 目的: 蹭Java的名气,吸引更多人注意18
  • 真相: JavaScript和Java“完全不同,毫无关系”18
  • 核心区别: Java是一种严谨的、重量级的、“静态类型”语言(你必须提前定义变量类型);而JavaScript是一种随意的、轻量级的、“动态类型”语言(你不需要定义变量类型)18

用一个比喻来说:这就像你开了一家卖辣条的小卖部,却给它起名叫“米其林三星法式(辣条)餐厅”,只因为它俩都卖“食物”。

浏览器战争(第二幕):JScript的“克隆人”攻击

微软当然不会坐视网景拥有这种“动态网页”的武器。于是,他们故技重施:他们拿到了JavaScript,通过“逆向工程”把它(几乎)原样复制了一遍,然后给它起了个新名字,叫“JScript”19

微软把JScript内置到了Internet Explorer 3.0中。于是,“浏览器战争”进入了第二阶段:脚本战争19

现在,开发者们面临一个更可怕的局面:你有两个(几乎)一样,但在(关键的)细节上又完全不一样的脚本语言(JavaScript vs JScript)。你为网景写的代码,在IE上会崩溃;反之亦然。

这场混乱催生了一整个“行业”:JavaScript库。在21世纪初,最著名的库(比如jQuery)诞生的首要目的,甚至不是为了“简化开发”,而是为了“抹平浏览器差异”20。jQuery的核心价值,就是提供一个统一的API,然后在底层默默地为你处理“如果这是IE 6,就这样做;如果这是Netscape,就那样做”的恶心逻辑。

为什么是JavaScript赢了?(BASIC精神的真正继承者)

在90年代末,JavaScript并不是唯一的“富客户端”技术。它有两个强大的竞争对手:Macromedia Flash 和 Java Applets。

从纯技术的角度看,Flash和Applets在当时是碾压JavaScript的。它们能实现JavaScript梦寐以求的复杂动画、视频播放和高级交互。然而,它们都死了。而这个“10天”诞生的、充满Bug的JavaScript,却活了下来,并最终统治了桌面和移动端。

为什么?因为它拥有和50年前的BASIC一样的“杀手级特性”1

理由一(决定性理由):“零环境搭建”

JavaScript最大的优势,也是它战胜所有对手的唯一原因,就是:它内置在浏览器中。

你的用户不需要安装任何东西。

这个“零环境搭建”(Zero environment setup)的特性21,是它击败Flash和Java Applets的“银色子弹”。

【讣告:Flash的倒掉】

Flash曾是互联网的宠儿。它为我们带来了无数(现在看来很烦人的)动画广告、在线游戏和早期的视频网站(比如YouTube最初就是基于Flash的)。但它有一个致命弱点:它是一个私有的、第三方的“插件”22。你必须从Adobe(前身是Macromedia)那里下载并安装“Flash Player”22

真正杀死Flash的,是2007年的iPhone,以及2010年史蒂夫·乔布斯那封著名的公开信:《Thoughts on Flash》(关于Flash的思考)23

乔布斯在这封信中,系统地阐述了为什么苹果的iOS设备(iPhone和iPad)永远不会支持Flash24。他的理由(现在看来极具预见性)包括24

  1. 它是私有的,而未来是开放的: 乔布斯认为H.264和HTML5等开放标准才是未来24
  2. 安全、性能和可靠性极差: Flash是PC时代安全漏洞的“重灾区”,而且经常导致浏览器崩溃24
  3. 消耗电池寿命: Flash是出了名的“电老虎”,在(当时)电量宝贵的移动设备上,这是不可接受的24
  4. 它不是为“触摸”设计的: Flash的交互是为“鼠标悬停”设计的,而移动端是“触摸”24
  5. 它是阻碍创新的“中间层”: 它阻碍了开发者使用iOS的新特性24

乔布斯的这封“檄文”25,等于宣判了Flash在(即将到来的)移动互联网时代的死刑。

【讣告:Java Applets的惨败】

Java Applets其实比Flash更早出现。它承诺的是“一次编写,到处运行”(Write Once, Run Anywhere)。它的理念和JavaScript惊人地相似:在浏览器里运行的“小程序”。

但它也需要一个插件——一个庞大、笨重、启动缓慢的“Java虚拟机”(JVM)26

Java Applets的失败堪称一场教科书式的灾难27

  1. 安全噩梦: Applets的安全模型(“沙盒”)被证明漏洞百出27。它不断被黑客利用,成为恶意软件的“重灾区”,迫使浏览器厂商最终默认禁用了它28
  2. 启动速度: 用户点击一个Applet,必须先等待那个该死的JVM启动……这通常需要几十秒,甚至几分钟26
  3. 用户体验: 它的GUI(图形界面)使用的是Java的AWT或Swing库,这些库在浏览器中看起来(而且用起来)都像“外星人”,和网页本身格格不入,而且极其丑陋26
  4. 版本地狱: Applets对JVM的版本极其敏感。用户电脑上的Java版本(如果他们有的话)很可能与Applet需要的版本不匹配,导致其根本无法运行26

结论: Flash和Java Applets都死于“插件”的原罪22。JavaScript,这个“更差”的语言,因为它“什么都不用装”,成为了唯一的幸存者。

理由二:“即时反馈”(现代的“直接模式”)

在你的BASIC时代,你可以进入“直接模式”(Direct Mode),输入PRINT 2+2并立即得到41

JavaScript完美地复刻了这种体验。在任何现代浏览器(Chrome, Firefox, Edge)中,你只要按下F12键,打开“开发者工具”,切换到“控制台”(Console)标签页。

你就得到了一个全球部署的、最先进的JavaScript“直接模式”。你可以输入7 + "4",然后回车,它会立即告诉你答案是"74"。这种即时反馈的“可玩性”,是BASIC精神的直系延续1

理由三:“查看源代码”(全球的LIST命令)

在你的时代,你拿到一本电脑杂志,把上面的BASIC代码(包括DATA语句里的机器码)抄到你的电脑上。你想知道一个程序是怎么工作的?你只需要输入LIST命令1

JavaScript和HTML/CSS把这种“开放性”带到了全球尺度。

在任何网页上,你都可以点击鼠标右键,选择“查看网页源代码”。你就LIST了那个网页的“骨架”(HTML)和“皮肤”(CSS),以及它的“神经系统”(JavaScript)1

在21世纪初,整整一代的网页开发者(包括我),都是通过“查看别人的源代码”来学习如何编程的。这是一个(无意中)建立起来的、全球最大的“开源教室”。


欢迎来到“WTFJS”博物馆

好了,旅行者。我们已经确立了JavaScript的“幸存者”地位。现在,是时候带你去看看它在“10天创世”中留下的那些……“永久性创伤”了。

我们管这个展厅叫“WTFJS”29(“什么鬼JavaScript”)。这是每个JS开发者都必须(含泪)背下来的“怪癖”清单。这些不是Bug,它们是“特性”。

展品A:“空”的类型是什么?是“对象”。

让我们从最著名的开始。在JavaScript中,有一个值叫null,代表“空值”或“故意的虚无”。

现在,让我们在“直接模式”(控制台)里问JavaScript:null的“类型”是什么?

typeof null

你(一个来自BASIC时代的、有逻辑的程序员)会期望它返回"null"

然而,JavaScript会(非常自信地)告诉你:

"object"

null的类型是“对象”。

为什么会这样?

这就是一个Bug30。在JavaScript的最初版本中,为了性能,值的“类型”是(大致)用一个“类型标签”(Type Tag)来表示的。如果这个标签是0,代表它是一个“对象”。而null(空指针)的机器码表示,恰好就是全零(0x00)。因此,typeof null的检查(错误地)读取了那个0标签,并报告它是一个“对象”31

为什么不修复它?

Brendan Eich本人(JavaScript的创造者)承认,这是一个无法修复的Bug30。为什么?因为在它被发现时,已经有太多的网站(错误地)依赖这个Bug来写代码了(例如:if (typeof myVar === "object") {... },这段代码错误地同时匹配了objectnull)。

修复这个Bug,会让互联网上成千上万的网站崩溃30

所以,它就像我们那栋“龙卷风中盖的房子”里的一根(极其重要的)承重柱——它是歪的,它是错的,但你一碰它,整栋楼就塌了。

展品B:神秘的this(“我是谁?”)

在JavaScript中,有一个关键字叫this。在(比如)Java或C++中,this(或self)的含义非常清晰:它总是指向“当前这个对象实例”。

在JavaScript里,this的含义更像是一个哲学问题:“我是谁?我为什么在这里?”32

this的值,取决于你“如何调用”这个函数,而不是“在哪里定义”这个函数。它可以是:

  • 全局对象(window
  • 调用它的那个对象
  • undefined(在“严格模式”下)
  • 你通过.bind().apply()强行塞给它的任何东西

这个关键字的混乱,是JavaScript开发者(尤其是初学者)痛苦的主要来源之一,也是无数“编程笑话”的主题33

展品C:语言巫术之“强制类型转换”

JavaScript这个语言,有一种“讨好型人格”。它极度讨厌抛出错误。无论你给它多么荒谬的指令,它都会(拼了命地)试图给你一个答案。

它实现这一点的方式,叫做“隐式类型强制转换”(Implicit Type Coercion)29

还记得我们前面提过的7 + "4"吗?

7 + "4"  // 结果是 "74"

因为+号运算符被重载了:它既可以做“数学加法”,也可以做“字符串拼接”。当它看到一边是字符串"4"时,它决定(自作主张地)放弃数学,把数字7也变成了字符串"7",然后把它们拼了起来29

好的,这还算(勉强)能理解。那这个呢?

7 - "4"  // 结果是 3

为什么?因为-(减号)运算符没有被重载。它只认识数字。所以当它看到字符串"4"时,它(再次自作主张地)“强迫”"4"变成了一个数字4,然后成功地执行了数学运算7 - 429

现在,欢迎来到“WTFJS”的主展厅:

[] + {}  // 结果是 "[object Object]"

解释: 当+号看到数组[]和对象{}时,它决定把两边都转换成“原始值”。

  1. 数组[]变成了空字符串""
  2. 对象{}变成了字符串"[object Object]"(这是对象默认的字符串形态)。
  3. "" + "[object Object]" 结果就是 "[object Object]"

好的……那如果我们把它们反过来呢?

{} + []  // 结果是... 0 (零)

……什么?!

解释(请坐稳):

这一次,当JavaScript的解析器看到{}在一行的开头时,它没有(像你期望的那样)把{}当作一个“空对象”。

它把{}当作了一个“空代码块”(Code Block)29

所以,JavaScript“吃掉”了那个(它认为)毫无意义的空代码块,然后只执行了剩下的代码:+ []

+ [](一元加号运算符,后面跟着一个空数组)是另一个“强制转换”的触发器。+号试图把[]变成一个数字。空数组首先变成空字符串"",然后空字符串""又被强制转换成数字…… 0。

这就是为什么 [] + {}不等于{} + []。一个是你以为的“对象运算”,另一个是(你不知道的)“空代码块”和“一元加号”。

这已经不是“编程”了。这是“语言巫术”。

展品D(现代展厅):node_modules黑洞

最后,我们来看看“10天创世”的现代并发症。

JavaScript现在(通过Node.js)也可以在服务器上运行了。它有一个(非常棒的)包管理器,叫做npm。当你需要某个功能时,你就npm install那个包。npm会把这个包,以及这个包依赖的所有包,以及这些包又依赖的包……全部下载到一个叫node_modules的文件夹里。

由于JavaScript的“10天”出身,它的“标准库”(内置功能)非常非常小34。在你的BASIC时代,你有处理字符串、数学和图形的内置命令。在JavaScript里,你(在很长一段时间里)甚至没有一个内置的方法来“判断一个字符串是否以另一个字符串开头”。

后果就是: 为了实现最微小的功能(比如“判断一个数是不是奇数”),开发者都必须从npm上下载一个(看似)微小的包35

这导致了node_modules文件夹的“指数级爆炸”。

在程序员的笑话中,node_modules文件夹是“宇宙中最重的物体”36。天文学家在谈论黑洞和中子星,而程序员知道,没有什么比node_modules更重37

最经典的模因38是这样的:

程序员:“我的应用程序代码写完了!Vue组件 (719KB),CSS (34KB),助手类 (92KB)。总共 845KB!很小嘛!”

(然后他把node_modules文件夹放到了秤上)

秤:“68 GB”

这个笑话37完美地概括了JS的生态:你只是想npm install milk(安装“牛奶”),结果npm为了运行“牛奶”,给你下载了“奶牛”、“农场”、“挤奶设备”、“饲料供应合同”、“三辆拖拉机”和“整个新西兰的乳制品行业”。

这个“黑洞”35,是“10天创世”和“极小标准库”34所带来的、不可避免的、滑稽的后果。

“WTFJS”快速参考指南(又名:别在派对上问这些)

为了帮你(我们可怜的时空旅行者)在这个新世界中幸存下来,我们准备了这份速查表。

表达式(你输入的) 实际结果(JS给你的) “恐龙”的直觉(你猜的) 简短的“为什么会这样”(幽默版)
typeof null "object" "null" “一个无法修复的‘创世’Bug。别碰它,房子会塌。”30
7 + "4" "74" 错误? 或 11? + 号更喜欢当‘胶水’(字符串拼接)。”29
7 - "4" 3 错误? - 号只认识数字,所以它强迫 "4" 变成了 4。”29
[] + {} "[object Object]" 错误? “JS决定先把数组变为空字符串,然后把对象变成了它的‘身份证’字符串。”
{} + [] 0 "[object Object]"? “JS认为 {} 是一个‘空代码块’,然后把 + [] 变成了 0。别问了。”29

结论:欢迎来到这个“粗糙但充满活力”的马戏团

旅行者,你现在已经(大致)了解了“活文档”的真相。

这个支撑着现代互联网的“三位一体”(HTML, CSS, JS),根本不是什么“精密工程”的产物。它是一个充满了历史妥协、古怪bug和激烈商业战争的“缝合怪”。

  • 它的“骨架”(HTML)曾经被<table>(电子表格)扭曲了十几年2
  • 它的“皮肤”(CSS)曾经被“盒模型缺陷”8和(亮瞎眼的)<blink>标签5撕裂。
  • 而它的“神经系统”(JavaScript),是在10天之内被“缝合”出来的18,它之所以能“赢”,不是因为它设计得有多好,而是因为它的竞争对手(Flash 和 Applets)都因为“插件”的原罪而暴毙了24

它很混乱,它很古怪,但它赢了。

对于你,一个来自BASIC时代的“恐龙”程序员来说,这里有一个好消息:

JavaScript,才是你(BASIC程序员)精神的真正继承者1。它重新带回了“零设置”(浏览器就是一切)21和“即时反馈”(F12控制台)1。它那“查看源代码”的能力,就是你当年LIST命令的全球互联网版本1

它充满了“历史遗留Bug”(比如typeof null30)和反直觉的“黑魔法”(比如{} + []),这就像你当年用PEEK和POKE命令去(反直觉地)绕过ROM的Bug一样1

你并非过时。你只是需要一本“翻译手册”。现在,你拿到了。

欢迎来到这个“活文档”马戏团。准备好你的F12键。



  1. 程序员复活手册:从BASIC到AI(大纲) ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  2. Remember when we used tables to create layouts? : r/webdev - Reddit, 访问时间为 十月 27, 2025, https://www.reddit.com/r/webdev/comments/1ksmzv8/remember_when_we_used_tables_to_create_layouts/ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  3. CSS : r/ProgrammerHumor - Reddit, 访问时间为 十月 27, 2025, https://www.reddit.com/r/ProgrammerHumor/comments/5l4ulr/css/ ↩︎ ↩︎

  4. CSS vs Tables: The Debate That Won't Die - Vanseo Design, 访问时间为 十月 27, 2025, https://vanseodesign.com/css/css-divs-vs-tables/ ↩︎ ↩︎

  5. How Not to Display Your Artwork on the Web - Lines and Colors, 访问时间为 十月 27, 2025, https://linesandcolors.com/2007/05/31/how-not-to-display-your-artwork-on-the-web/ ↩︎ ↩︎

  6. September | 2009 | Thimbles & Care - UT Blogs, 访问时间为 十月 27, 2025, https://sites.utexas.edu/curtispe/2009/09/ ↩︎ ↩︎

  7. DERKÉTA - Prelinger Library, 访问时间为 十月 27, 2025, https://prelingerlibrary.org/stacks/v1/about/Stacks-Explorer-Derketa-BW.pdf ↩︎ ↩︎ ↩︎

  8. Internet Explorer box model bug - Wikipedia, the free encyclopedia, 访问时间为 十月 27, 2025, http://taggedwiki.zubiaga.org/new_content/28d0392bbe59720bc24bb4eaae791830 ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  9. File:W3C and Internet Explorer box models.svg - Wikimedia Commons, 访问时间为 十月 27, 2025, https://commons.wikimedia.org/wiki/File:W3C_and_Internet_Explorer_box_models.svg ↩︎

  10. Internet Explorer 6 - Wikipedia, 访问时间为 十月 27, 2025, https://en.wikipedia.org/wiki/Internet_Explorer_6 ↩︎

  11. How to center a div in CSS • Josh W. Comeau, 访问时间为 十月 27, 2025, https://www.joshwcomeau.com/email/2024-02-13-center-a-div/ ↩︎ ↩︎

  12. The Sarcastic Struggles of Centering a Div: A Programmer's Journey | by Konain Anjum, 访问时间为 十月 27, 2025, https://medium.com/@akonain138/the-sarcastic-struggles-of-centering-a-div-a-programmers-journey-2839dafe416c ↩︎ ↩︎

  13. Why is there so many memes about how hard centering a div is? Nowadays, it's literally one of the easiest CSS tricks there is? : r/webdev - Reddit, 访问时间为 十月 27, 2025, https://www.reddit.com/r/webdev/comments/pqbxst/why_is_there_so_many_memes_about_how_hard/ ↩︎

  14. Can somebody explain to me why centering a div is such a challenge to have an ea... | Hacker News, 访问时间为 十月 27, 2025, https://news.ycombinator.com/item?id=39361381 ↩︎

  15. CSS is Awesome | CSS-Tricks, 访问时间为 十月 27, 2025, https://css-tricks.com/css-is-awesome/ ↩︎ ↩︎

  16. Rachel Andrew on why the CSS is Awesome meme is actually a good thing and how to fix it | Ambient.Impact, 访问时间为 十月 27, 2025, https://ambientimpact.com/web/snippets/rachel-andrew-on-why-the-css-is-awesome-meme-is-actually-a-good-thing-and-how-to-fix ↩︎

  17. CSS is awesome : r/ProgrammerHumor - Reddit, 访问时间为 十月 27, 2025, https://www.reddit.com/r/ProgrammerHumor/comments/z7mge7/css_is_awesome/ ↩︎

  18. JavaScript之父大解密:創造JavaScript僅用了10天!命名僅是行銷 ..., 访问时间为 十月 27, 2025, https://www.ithome.com.tw/news/95927 ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  19. JavaScript和Node.js简史,前端未来走向何方?_大前端_Hans ..., 访问时间为 十月 27, 2025, https://www.infoq.cn/article/6ag9guf7izzxayglb9bb ↩︎ ↩︎ ↩︎ ↩︎

  20. 什么是JavaScript(JS)? - AWS, 访问时间为 十月 27, 2025, https://aws.amazon.com/cn/what-is/javascript/ ↩︎

  21. building-a-responsive-web-testing-strategy-perfecto.pdf, 访问时间为 十月 27, 2025, https://www.appservgrid.com/codegeeks/building-a-responsive-web-testing-strategy-perfecto.pdf ↩︎ ↩︎

  22. #0012: A personal perspective on the end of the Adobe Flash era – A Tinkerer's Blog, 访问时间为 十月 27, 2025, https://www.tinkerersblog.net/0012-a-personal-perspective-on-the-end-of-the-adobe-flash-era/ ↩︎ ↩︎ ↩︎

  23. The Death of a Technical Skill - John Horton, 访问时间为 十月 27, 2025, https://john-joseph-horton.com/papers/schumpeter.pdf ↩︎

  24. Comment: Seven years on from Steve Jobs' 'Thoughts on Flash,' it's ..., 访问时间为 十月 27, 2025, https://9to5mac.com/2017/05/12/opinion-flash-must-die/ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  25. The history of Flash | Kaspersky official blog, 访问时间为 十月 27, 2025, https://www.kaspersky.com.au/blog/life-and-death-of-adobe-flash/31157/ ↩︎

  26. Where did Java go wrong on the client? [closed] - Software Engineering Stack Exchange, 访问时间为 十月 27, 2025, https://softwareengineering.stackexchange.com/questions/21843/where-did-java-go-wrong-on-the-client ↩︎ ↩︎ ↩︎ ↩︎

  27. Why Wasm Wins Where Java Applets Failed - The New Stack, 访问时间为 十月 27, 2025, https://thenewstack.io/why-wasm-wins-where-java-applets-failed/ ↩︎ ↩︎

  28. Why did java disable applet? : r/java - Reddit, 访问时间为 十月 27, 2025, https://www.reddit.com/r/java/comments/rw6fbb/why_did_java_disable_applet/ ↩︎

  29. [] == ![] - WTFJS And Coercion - EmNudge, 访问时间为 十月 27, 2025, https://emnudge.dev/blog/wtfjs-and-coercion/ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  30. Why typeof null === "object" in JavaScript? A Deep Dive into a Quirky Bug - DEV Community, 访问时间为 十月 27, 2025, https://dev.to/m0slah/why-typeof-null-object-in-javascript-a-deep-dive-into-a-quirky-bug-n6p ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  31. Why does typeof(null) return "object", but you can't assign properties to it? - Stack Overflow, 访问时间为 十月 27, 2025, https://stackoverflow.com/questions/20480729/why-does-typeofnull-return-object-but-you-cant-assign-properties-to-it ↩︎

  32. Found this in LinkedIn : r/ProgrammerHumor - Reddit, 访问时间为 十月 27, 2025, https://www.reddit.com/r/ProgrammerHumor/comments/wfthwn/found_this_in_linkedin/ ↩︎

  33. r/ProgrammerHumor - noSuchThingAsAnIntuitiveProgrammingLanguage - Reddit, 访问时间为 十月 27, 2025, https://www.reddit.com/r/ProgrammerHumor/comments/1f1xuz1/nosuchthingasanintuitiveprogramminglanguage/ ↩︎

  34. Even the guys on our Node team joke that the node modules folder is abhorrently, 访问时间为 十月 27, 2025, https://news.ycombinator.com/item?id=20223381 ↩︎ ↩︎

  35. node_modules: The Node.js black hole - Alpha Coder, 访问时间为 十月 27, 2025, https://alphacoder.xyz/node-modules/ ↩︎ ↩︎

  36. Heaviest Objects In The Universe : r/ProgrammerHumor - Reddit, 访问时间为 十月 27, 2025, https://www.reddit.com/r/ProgrammerHumor/comments/6s0wov/heaviest_objects_in_the_universe/ ↩︎

  37. node_modules Memes - ProgrammerHumor.io, 访问时间为 十月 27, 2025, https://programmerhumor.io/memes/node_modules ↩︎ ↩︎

  38. The Black Hole Called Node_modules - ProgrammerHumor.io, 访问时间为 十月 27, 2025, https://programmerhumor.io/javascript-memes/the-black-hole-called-node_modules-ta0o ↩︎