语法分析(解析器):对 Tokens 应用 HTML 的语法规则,进行配对标记、确立节点关系和绑定属性等操作,从而构建 DOM Tree 的过程。
词法分析和语法分析在每次处理 HTML 字符串时都会执行这个过程,比如使用 document.write 方法。
词法分析(标记化)
HTML 结构不算太复杂,大部分情况下识别的标记会有开始标记、内容标记和结束标记,对应一个 HTML 元素。除此之外还有 DOCTYPE、Comment、EndOfFile 等标记。
标记化是通过状态机来实现的,状态机模型在 W3C 中已经定义好了。
想要得到一个标记,必须要经历一些状态,才能完成解析。我们通过一个简单的例子来了解一下流程。
<a href="www.w3c.org">W3C</a>
开始标记:
Data state:碰到 <,进入 Tag open state
Tag open state:碰到 a,进入 Tag name state 状态
Tag name state:碰到 空格,进入 Before attribute name state
Before attribute name state:碰到 h,进入 Attribute name state
Attribute name state:碰到 =,进入 Before attribute value state
Before attribute value state:碰到 ",进入 Attribute value (double-quoted) state
Attribute value (double-quoted) state:碰到 w,保持当前状态
Attribute value (double-quoted) state:碰到 ",进入 After attribute value (quoted) state
After attribute value (quoted) state:碰到 >,进入 Data state,完成解析
内容标记:W3C
Data state:碰到 W,保持当前状态,提取内容
Data state:碰到 <,进入 Tag open state,完成解析
结束标记:
Tag open state:碰到 /,进入 End tag open state
End tag open state:碰到 a,进入 Tag name state
Tag name state:碰到 >,进入 Data state,完成解析
通过上面这个例子,可以发现属性是开始标记的一部分。
语法分析(解析器)
在创建解析器后,会关联一个 Document 对象作为根节点。
我会简单介绍一下流程,具体的实现过程可以在 Tree construction 查看。
解析器在运行过程中,会对 Tokens 进行迭代;并根据当前 Token 的类型转换到对应的模式,再在当前模式下处理 Token;此时,如果 Token 是一个开始标记,就会创建对应的元素,添加到 DOM Tree 中,并压入还未遇到结束标记的开始标记栈中;此栈的主要目的是实现浏览器的容错机制,纠正嵌套错误,具体的策略在 W3C 中定义。更多标记的处理可以在 状态机算法 中查看。
参考资料
浏览器的工作原理:新式网络浏览器幕后揭秘 —— 解析器和词法分析器的组合
浏览器渲染过程与性能优化 —— 构建DOM树与CSSOM树
在浏览器的背后(一) —— HTML语言的词法解析
在浏览器的背后(二) —— HTML语言的语法解析
50 行代码的 HTML 编译器
AST解析基础: 如何写一个简单的html语法分析库
WebKit中的HTML词法分析
HTML文档解析和DOM树的构建
从Chrome源码看浏览器如何构建DOM树
构建对象模型 —— 文档对象模型 (DOM)
CSSOM Tree
加载
在构建 DOM Tree 的过程中,如果遇到 link 标记,浏览器就会立即发送请求获取样式文件。当然我们也可以直接使用内联样式或嵌入样式,来减少请求;但是会失去模块化和可维护性,并且像缓存和其他一些优化措施也无效了,利大于弊,性价比实在太低了;除非是为了极致优化首页加载等操作,否则不推荐这样做。
阻塞
CSS 的加载和解析并不会阻塞 DOM Tree 的构建,因为 DOM Tree 和 CSSOM Tree 是两棵相互独立的树结构。但是这个过程会阻塞页面渲染,也就是说在没有处理完 CSS 之前,文档是不会在页面上显示出来的,这个策略的好处在于页面不会重复渲染;如果 DOM Tree 构建完毕直接渲染,这时显示的是一个原始的样式,等待 CSSOM Tree 构建完毕,再重新渲染又会突然变成另外一个模样,除了开销变大之外,用户体验也是相当差劲的。另外 link 标记会阻塞 JavaScript 运行,在这种情况下,DOM Tree 是不会继续构建的,因为 JavaScript 也会阻塞 DOM Tree 的构建,这就会造成很长时间的白屏。
先思考一下正向匹配是什么流程,我们用 div p .yellow 来举例,先查找所有 div 节点,再向下查找后代是否是 p 节点,如果是,再向下查找是否存在包含 class="yellow" 的节点,如果存在则匹配;但是不存在呢?就浪费一次查询,如果一个页面有上千个 div 节点,而只有一个节点符合 Rule,就会造成大量无效查询,并且如果大多数无效查询都在最后发现,那损失的性能就实在太大了。
switch (m_match) { case Id: return 0x010000; case PseudoClass: return 0x000100; case Class: case PseudoElement: case AttributeExact: case AttributeSet: case AttributeList: case AttributeHyphen: case AttributeContain: case AttributeBegin: case AttributeEnd: return 0x000100; case Tag: return 0x000001; case Unknown: return 0; } return 0;
Render Object 在添加到树之后,还需要重新计算位置和大小;ComputedStyle 里面已经包含了这些信息,为什么还需要重新计算呢?因为像 margin: 0 auto; 这样的声明是不能直接使用的,需要转化成实际的大小,才能通过绘图引擎绘制节点;这也是 DOM Tree 和 CSSOM Tree 需要组合成 Render Object Tree 的原因之一。
Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。
Rocky Linux(中文名:洛基)是由Gregory Kurtzer于2020年12月发起的企业级Linux发行版,作为CentOS稳定版停止维护后与RHEL(Red Hat Enterprise Linux)完全兼容的开源替代方案,由社区拥有并管理,支持x86_64、aarch64等架构。其通过重新编译RHEL源代码提供长期稳定性,采用模块化包装和SELinux安全架构,默认包含GNOME桌面环境及XFS文件系统,支持十年生命周期更新。
Sublime Text
Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。