当我们在浏览器地址栏输入一个合法的 url 时, 浏览器首先进行 DNS 域名解析, 拿到合适的服务器 ip 地址后, 浏览器给服务器发送 GET 请求, 等到服务器正常返回后浏览器开始下载并解析 html. 这里仅总结浏览器解析 HTML 的过程.
HTML 页面主要由 dom,CSS,JavaScript 等部分构成, 其中 CSS 和 JavaScript 既能内联也能以脚本的形式引入, 当然 HTML 中还可能引入 img,iframe 等其他资源. 其实所有的这些资源也是以 dom 标签的形式嵌入在 HTML 页面中的, 因此本篇总结说的 HTML 解析过程就是 dom 的解析过程.
1 dom 解析过程
整个 dom 的解析过程是顺序, 并且渐进式的.
顺序指的是从第一行开始, 一行一行依次解析; 渐进式则指得是浏览器会迫不及待的将解析完成的部分显示出来, 如果我们做下面这个实验会发现, 在断点处第一个 div 已经在浏览器渲染出来了:
- <!DOCTYPE HTML>
- <HTML>
- <head>
- </head>
- <body>
- <div>
- first div
- </div>
- <script>
- debugger
- </script>
- <div>
- second div
- </div>
- </body>
- </HTML>
既然 dom 是从第一行按顺序解析, 那么我们怎么判断 dom 何时解析完成呢? 这个问题应该经常会在面试中问到, 比如一般会问:
Windows.onload 和 DOMContentLoaded 有什么区别?
其实就是想看看是不是明白 dom 树何时构建完成, 这个问题确实很重要, 尤其是对于几年前的 jQuery 技术栈来说, 因为我们使用 JavaScript 操作 dom 或者给 dom 绑定事件有个前提条件就是需要 dom 树已经创建完成. 整个 HTML 页面的 dom 解析完成时, dom 树也就构建完成了. dom 树构建完成后 document 对象会派发事件 DOMContentLoaded 来通知 dom 树已构建完成.
HTML 从第一行开始解析, 遇到外联资源 (外联 CSS, 外联 JavaScript,image,iframe 等) 就会请求对应资源, 那么请求过程是否会阻塞 dom 的解析过程呢? 答案是看情况, 有的资源会, 有的资源不会. 下面按是否会阻塞页面解析分为两类: 阻塞型与非阻塞型, 注意这里区分两类资源的标志是 document 对象派发 DOMContentLoaded 事件的时间点, 认为派发 DOMContentLoaded 事件才表示 dom 树构建完成.
1.1 阻塞型
会阻塞 dom 解析的资源主要包括:
内联 CSS
内联 JavaScript
外联普通 JavaScript
外联 defer JavaScript
JavaScript 标签之前的外联 CSS
外联 JavaScript 可以用 async 与 defer 标示, 因此这里分为了三类: 外联普通 JavaScript, 外联 defer JavaScript, 外联 async JavaScript, 这几类外联 JavaScript 本篇后面有详细介绍. dom 解析过程中遇到外联普通 JavaScript 会暂停解析, 请求拿到 JavaScript 并执行, 然后继续解析 dom 树.
对于外联 defer JavaScript 这里重点说明下为什么也归于阻塞型. 前面也说了, 这里以 document 对象派发 DOMContentLoaded 事件来标识 dom 树构建完成, 而 defer JavaScript 是在该事件派发之前请求并执行的, 因此也归类于阻塞型, 但是需要知道, defer 的 JavaScript 实际上是在 dom 树构建完成与派发 DOMContentLoaded 事件之间请求并执行的, 不过如果换个思路理解,<script > 本身也是 dom 的一部分也就不难理解为什么 defer 的 JavaScript 会在 DOMContentLoaded 派发之前执行了.
另外需要注意的是 JavaScript 标签之前的外联 CSS. 其实按说 CSS 资源是不应该阻塞 dom 树的构建过程的, 毕竟 CSS 只影响 dom 样式, 不影响 dom 结构, MDN 上也是这么解释的:
The DOMContentLoaded event is fired when the initial HTML document has been completely loaded and parsed, without waiting for stylesheets, images, and subframes to finish loading.
但是实际情况是 dom 树的构建受 JavaScript 的阻塞, 而 JavaScript 执行时又可能会使用类似 Windows.getComputedStyle()之类的 API 来获取 dom 样式, 比如:
- const para = document.querySelector('p');
- const compStyles = Windows.getComputedStyle(para);
- if (document.readystate === 'interactive'
- || document.readystate === 'complete') {
- // 调用 ready 回调函数
- } else {
- document.onreadystatechange = function () {
- if (document.readystate === 'interative') {
- // 调用 ready 回调函数
- }
- }
- }
- Page lifecycle: DOMContentLoaded, load, beforeunload, unload https://javascript.info/onload-ondomcontentloaded
- DOMContentLoaded and stylesheets https://molily.de/domcontentloaded/
- MDN: DOMContentLoaded
- MDN: readystatechange
- Replace jQuery's Ready() with Plain JavaScript
来源: https://juejin.im/post/5c1dde33f265da61776bf49a