了解DOM

什么是DOM?

DOM 是表述 HTML 的内部数据结构,它会将 Web 页面和 JavaScript 脚本连接起来,并过滤一些不安全的内容。

为什么需要DOM?

浏览器引擎无法识别HTML文件字节流,所以需要将HTML文件字节流转换成浏览器引擎识别的内部结构,它就是DOM。

DOM如何使用?

DOM提供了HTML文档结构化的表述。
DOM提供给 JavaScript 脚本操作的接口,通过这套接口,JavaScript 可以对 DOM 结构进行访问,从而改变文档的结构、样式和内容。

DOM树如何生成?

HTML 字节流转换为 DOM 结构,需要经过HTML解析器的处理。HTML解析器是浏览器渲染引擎内部的一个模块。
HTML解析器的工作就是将网络或者本地磁盘获取的HTML网页和资源从字节流解析成DOM树结构:

  1. 网络进程接收到响应头之后,会根据响应头中的 content-type 字段来判断文件的类型,比如 content-type 的值是“text/html”,那么浏览器就会判断这是一个 HTML 类型的文件。
  2. 然后为该请求选择或者创建一个渲染进程。
  3. 渲染进程准备好之后,网络进程和渲染进程之间会建立一个共享数据的管道。
  4. 网络进程接收到数据后就往这个管道里面放,而渲染进程则从管道的另外一端不断地读取数据,并同时将读取的数据丢给 HTML 解析器。
  5. 字节流转换为 DOM 需要三个阶段。
  6. 阶段一,通过分词器先将字节流转换为一个个 Token,分为 Tag Token 和文本 Token。Tag Token 又分 StartTag 和 EndTag。
  7. 第二个和第三个阶段是同步进行的,需要将 Token 解析为 DOM 节点,并将 DOM 节点添加到 DOM 树中。HTML 解析器维护了一个 Token 栈结构,该 Token 栈主要用来计算节点之间的父子关系,在第一个阶段中生成的 Token 会被按照顺序压到这个栈中。

解析器开始工作时,会默认创建了一个根为 document 的空 DOM 结构,同时会将一个 StartTag document 的 Token 压入栈底。
然后经过分词器解析出来的第一个 StartTag html Token 会被压入到栈中,并创建一个 html 的 DOM 节点,添加到 document 上。
通过分词器产生的新 Token 就这样不停地压栈和出栈,整个解析过程就这样一直持续下去,直到分词器将所有字节流分词完成。

举个?:


<html>
    <body>
        <div>吃饭</div>
        <div>睡觉</div>
    </body>
</html>
复制代码
  1. 先创建根为 document 的空 DOM 结构,然后将 StartTag document 的 Token 压入栈底。
  2. 分词器解析出StartTag html,然后创建 html的DOM节点,挂载到document下, 然后将 StartTag html 的 Token 入栈。
  3. 分词器解析出StartTag body,然后创建 body的DOM节点,挂载到html下,然后将 StartTag body 的 Token 入栈。
  4. 分词器解析出StartTag div,然后创建 div的DOM节点,挂载到body下,然后将 StartTag div 的 Token 入栈。
  5. 分词器解析出div 的文本 Token,然后创建文本节点,挂载到div下,文本 Token 不需要压入到栈中。
  6. 分词器解析出EndTag div,这时候 HTML 解析器会去判断当前栈顶的元素是否是 StartTag div,如果是则从栈顶弹出 StartTag div。
  7. 分词器解析出StartTag div,然后创建 div的DOM节点,挂载到body下,然后将 StartTag div 的 Token 入栈。
  8. 分词器解析出div 的文本 Token,然后创建文本节点,挂载到div下,文本 Token 不需要压入到栈中。
  9. 分词器解析出EndTag div,这时候 HTML 解析器会去判断当前栈顶的元素是否是 StartTag div,如果是则从栈顶弹出 StartTag div。
  10. 分词器解析出EndTag body,这时候 HTML 解析器会去判断当前栈顶的元素是否是 StartTag body,如果是则从栈顶弹出 StartTag body。
  11. 分词器解析出EndTag html,这时候 HTML 解析器会去判断当前栈顶的元素是否是 StartTag html,如果是则从栈顶弹出 StartTag html。
  12. 最后document 出栈。

如果两段 div 中间插入了一段 JavaScript 脚本,那就有点儿小变化。

<html>
    <body>
        <div>吃饭</div>
        <script>
            let div1 = document.getElementsByTagName('div')[0] 
            div1.innerText = '打游戏'
        </script>
        <script type="text/javascript" src='foo.js></script>
        <div>睡觉</div>
    </body>
</html>
复制代码
  1. 分词器解析到第一条script 时。HTML解析器会暂停下工作,JavaScript 引擎介入,执行 script 标签中的这段脚本。
  2. 分词器解析到第二条script 时。需要先下载这段 JavaScript 代码。这会需要重点关注下载环境,因为 JavaScript 文件的下载过程会阻塞 DOM 解析,通常下载很耗时,而且会受到网络环境、JavaScript 文件大小等因素的影响。
  • 在chrome 浏览器中做了优化,会有一个预解析操作,渲染引擎收到字节流之后,会开启一个预解析线程,用来分析 HTML 文件中包含的 JavaScript、CSS 等相关文件,解析到相关文件之后,预解析线程会提前下载这些文件。
  • 还可以使用 CDN 来加速 JavaScript 文件的加载。
  • 压缩 JavaScript 文件的体积。
  • 如果 JavaScript 文件中没有操作 DOM 相关代码,就可以将该 JavaScript 脚本设置为异步加载,通过 async 或 defer 来标记代码。async 标志的脚本文件一旦加载完成,会立即执行;而使用了 defer 标记的脚本文件,需要在 DOMContentLoaded 事件之前执行。
  1. 脚本执行完成后,HTML 解析器恢复解析过程,继续解析后续的内容,直至生成最终的 DOM。

如果有引用外部 CSS 文件,或者通过 style 标签内置了 CSS 内容,那么渲染引擎还需要将这些内容转换为 CSSOM。因为 JavaScript 有修改 CSSOM 的能力,所以在执行 JavaScript 之前,还需要依赖 CSSOM。所以 CSS 在部分情况下也会阻塞 DOM 的生成。

HTML 预解析器识别出来了有 CSS 文件和 JavaScript 文件需要下载,然后就同时发起这两个文件的下载请求,这两个文件的下载过程是重叠的,所以下载时间按照最久的那个文件来算。不管 CSS 文件和 JavaScript 文件谁先到达,都要先等到 CSS 文件下载完成并生成 CSSOM,然后再执行 JavaScript 脚本,最后再继续构建 DOM。

<html>
    <head>
      <link href="theme.css" rel="stylesheet">
    </head>
    <body>
        <div>吃饭</div>
        <script>
            let div1 = document.getElementsByTagName('div')[0] 
            div1.innerText = '打游戏'
        </script>
        <script type="text/javascript" src='foo.js></script>
        <div>睡觉</div>
    </body>
</html>
复制代码
© 版权声明
THE END
喜欢就支持一下吧
点赞0 分享