浏览器拿到一段HTML字符串,编译不会直接把它变成你看到的编译页面。中间隔着一个叫“编译”的编译过程——虽然很多人更习惯叫它“解析”或“渲染”。
我最早意识到这件事,编译是编译写了一个 `
从字符到树
HTML编译的编译第一步,是编译把一堆字节变成一棵树。听起来抽象,编译拆开看很具体。
网络传过来的HTML是一串字节。浏览器先按编码(通常是UTF-8)把它们转成字符,再把字符按标签规则切成一个个“词”——`
` 是一个词,`class="intro"` 是一个词,`>` 也是一个词。这个过程叫词法分析。接着语法分析上场,把这些词拼成节点:元素节点、文本节点、注释节点。遇到 `
但HTML不是编程语言,它没有严格的语法。你少写一个闭合标签,浏览器不会罢工。它有一套容错规则,比如 `
` 里面不能再套 `
`,遇到嵌套它会自动闭合前一个。这些规则写在HTML规范里,每个浏览器都得遵守。所以同一段“烂代码”在Chrome和Firefox里渲染出来基本一样,不是巧合。
DOM树不是终点
解析完,你得到一棵DOM树。但DOM树只描述了结构,不知道每个节点长什么样、放在哪里。样式计算紧接着开始。浏览器把CSS规则和DOM节点匹配,算出每个节点最终的颜色、字号、边距。这一步有个细节:选择器是从右往左匹配的。`.nav li a` 会先找到所有 ``,再往上查父级是不是 `
样式算完,进入布局阶段。浏览器遍历DOM树,计算每个盒子在屏幕上的确切位置和尺寸。这里有个容易踩的坑:如果你在JS里先读 `offsetHeight` 再改样式,再读一次,浏览器会强制同步布局两次。性能就是这么掉的。
绘制与合成
布局之后是绘制。浏览器把每个节点画成像素,填充颜色、边框、阴影、文字。但绘制不是直接画到屏幕上,而是分成多个图层。比如一个带动画的元素会被提升为独立图层,由GPU单独合成。这就是为什么 `transform` 动画比 `left` 动画流畅——前者只触发合成,后者要重新布局和绘制。
最后,合成器把各图层按正确顺序拼起来,输出到屏幕。你看到页面了。
一个真实的观察
我做过一个测试:一个包含5000个 `
HTML编译不是“把代码变成页面”这么简单。它是一连串决策:容错、匹配、计算、分层。浏览器替你做了很多选择,有些是规范要求的,有些是性能权衡。理解这些,你写HTML时就会少一点“为什么没生效”的困惑,多一点“我知道它接下来会做什么”的笃定。