DOM的理解

117 阅读3分钟

什么是DOM

  • html文件字节流是无法直接被渲染引擎理解的,所以要将其转化为渲染引擎能够理解的内部结构,这个结构就是DOM。简言之,DOM是表述HTML的内部数据结构,它会将web页面和js脚本连接起来,并过滤一些不安全的内容。
  • 在渲染引擎中,DOM有三个层面的作用:
    • 从页面的视角看,DOM是生成页面的基础数据结构
    • 从js脚本视角看,DOM提供给js脚本操作的接口,通过这套接口,js可以对DOM结构进行访问,从而改变文档结构、样式和内容。
    • 从安全视角来看,DOM是一道安全防护线,一些不安全的内容,在DOM解析阶段就被拒之门外。
  • DOM的解析是随着HTML边加载边解析的:
    • 网路进程接收到响应头之后,会根据响应头里的content-type字段来判断文件的类型,比如HTML类型的文件content-type就是'text/html',然后为该请求选择或创建一个渲染进程。渲染进程准备好后,网络进程和渲染进程之间就会建立一个共享数据的管道,网络进程接收到数据(字节流)之后就往这个管理到放,而渲染进程则从管道的另一端不断第读取数据,并同时将读取的数据给HTML解析器,解析器就会把数据解析为DOM。

DOM树如何生成?

image.png

  • 第一阶段:通过分词器将字节流转换成Token,分别是Tag Token文本Token,Tag Token还分为StartTag和EndTag。 image.png
  • 第二阶段是将Token解析为DOM节点,第三阶段是将DOM节点添加到DOM树中。第二第三阶段是同步进行的。
    HTML解析器维护了一个Token栈结构。遇到StartTag Token就入栈,遇到文本Token就会生成一个文本节点,然后将该节点放到DOM树中,不需要压栈。遇到EndTag Token就会查看栈顶元素是否是StartTag,如果是,就将StartTag从栈中弹出,表示该div元素解析完成。
    解析器开始工作时,会默认创建一个跟为document的空DOM结构,同时将一个StartTag document的Token压入栈底。 image.png

在解析过程中遇到js脚本,DOM解析器是如何处理的

<html>
    <body>
        <div>1</div>
        <script>
        let div1 = document.getElementsByTagName('div')[0]
        div1.innerText = 'time.geekbang'
        </script>
        <div>test</div>
    </body>
</html>
  • HTML解析器遇到js脚本,会暂停DOM的解析,JavaScript引擎介入,并执行script中的这段脚本,脚本执行完后,DOM节点内容已经被修改了,HTML解析器恢复解析过程,继续解析后续的内容,直至生成最终的DOM。

DOM解析器是如何处理跨站点资源

<html>
<body>
    <div>1</div>
    <script type="text/javascript" src='foo.js'></script>
    <div>test</div>
</body>
</html>
  • HTML解析器知道到script标签时,暂停整个DOM的解析,先下载js文件,然后再执行js代码。js文件下载过程会阻塞DOM解析。

Chrome的优化

  • 预解析操作:当渲染引擎收到字节流的时候,会开启一个预解析线程,用来分析HTML文件中包含的JavaScript、css等相关文件,解析到相关文件之后,预解析器会提前下载这些文件。

将js脚本设置为异步加载

  • async:文件加载完后马上执行,
  • defer;文件加载完后,在DOMContentLoaded事件前才执行。

CSSOM

  • 渲染引擎在遇到js脚本时,不管脚本是否操纵了CSSOM,都会执行css文件下载,解析操作,再执行js脚本,所以说,js脚本时依赖样式表的,这其实也是一个阻塞过程。
  • js会阻塞DOM生成,而样式文件又会阻塞js的执行,所以在实际的工程中需要重点关注js文件和样式表文件,使用不当会影响页面性能。

参考: JavaScript 是如何影响 DOM 生成的