浏览器工作原理(22) - JavaScript是如何影响DOM树构建的?

上一篇文章我们讲了chrome性能面板的使用,了解了请求过程中的几个性能指标,这篇文章我们一起来看一下DOM树是如何生成的,本文主要有两大块内容:第一个是解析过程中遇到JavaScript脚本,DOM解析器是如何处理的?第二个是DOM解析器是如何处理跨站点资源的?

什么是DOM

从网络传给渲染引擎的是HTML文档字节流,并无法执行,需要转换为渲染引擎能够识别的DOM树,在渲染引擎中,DOM有三个层面的作用:

  • 从页面的视觉来看,DOM是生成页面的基础数据结构
  • 从JavaScript脚本的角度来看,DOM提供给JavaScript脚本一些API接口,方便JavaScript操作页面元素
  • 从解析安全的角度来看,DOM是一道拦截器,不符合标准的DOM会提前报错

总结来说,DOM就是页面的结构化描述,并可以提供给脚本一些接口,方便来操作页面元素,还具备一定的过滤功能

DOM树如何生成

在渲染引擎内部,负责将HTML文档转换成DOM结构的模块叫 HTML解析器(HTMLParser) ,那么HTML解析器是如何工作的呢?

HTML解析器是在文档边加载的过程中就开始执行解析的,也就是说加载了多少就解析多少

一起来看一下详细的流程是怎么样的,首先是网络进程接收到响应头,会根据响应头中的content-type来判断文件的类型,比如“text/html”,那么浏览器会判断这是一个HTML类型的文件,然后为这个请求创建一个渲染进程,创建好渲染进程,渲染进程和网络进程进行通信,网络进程把接受到的数据不断的通过共享数据通道传送给渲染进程,渲染进程再将数据传给HTML解析器,解析成DOM。

接下来再一起看看DOM的具体生成流程—字节流转换成DOM

在这里插入图片描述
从上图可以看出,字节流转换成DOM需要三个阶段:

第一个阶段,通过分词器将字节流转换为Token

V8执行JavaScript脚本的时候,会将代码先做词法分析,将JavaScript分解为一个个的Token,这里HTML的解析也是一样的,先进行词法分析,将字节流转换为Token,Tag Token文本Token

在这里插入图片描述

第二阶段和第三阶段同步执行,生成DOM节点,将DOM 节点添加到DOM树中

HTML解析器维护了一个Token栈结构,主要用来计算节点之间的父子关系,第一阶段生成的Token按照顺序压入栈中,startTag Token首先压入栈中,并生成DOM树,文本节点直接添加到DOM树不入栈,遇到Endtag Token,则会去栈顶开始找对应的Starttag Token,并弹出Starttag Token,表示该元素已解析完成,直到所有的Token解析完成,示意图如下:

在这里插入图片描述

JavaScript如何影响DOM生成

看一段下面的代码:


<html>
<body>
    <div>1</div>
    <script>
    let div1 = document.getElementsByTagName('div')[0]
    div1.innerText = 'example'
    </script>
    <div>test</div>
</body>
</html>

解析这段HTML文档时,遇到script标签之前都是一样的过程,遇到script,解析器会判断这是一段脚本,HTML解析器会停止当前工作,开始执行脚本,这时候JavaScript引擎开始工作,执行完脚本,修改了div1的内容,然后又继续执行DOM的解析过程

上面的脚本是添加到了HTML内部,如果脚本是引入过来的,又该如何执行呢?


<html>
<body>
    <div>1</div>
    <script type="text/javascript" src='foo.js'></script>
    <div>test</div>
</body>
</html>

这里多了一个脚本下载的过程,JavaScript的下载会阻塞DOM的解析,如何避免这种情况呢?例如利用CDN来加速资源下载,还可以把脚本压缩以减小脚本大小,还可以通过异步加载的方式来加载脚本


 <script async type="text/javascript" src='foo.js'></script>


<script defer type="text/javascript" src='foo.js'></script>

async的方式是异步加载脚本,加载完成之后会立即执行,还是有可能阻塞DOM解析。defer标记的脚本,需要在DOMContentLoaded事件之前执行,也就是HTML解析完成之后再执行,不会阻塞DOM解析

再看一个同时拥有css样式和JavaScript脚本的例子


<html>
    <head>
        <style src='theme.css'></style>
    </head>
<body>
    <div>1</div>
    <script>
            let div1 = document.getElementsByTagName('div')[0]
            div1.innerText = 'example' //需要DOM
            div1.style.color = 'red'  //需要CSSOM
        </script>
    <div>test</div>
</body>
</html>

上面的例子中,JavaScript脚本中操作了CSSDOM,所以在执行脚本内的样式操作dom之前,需要先执行完CSS样式,所以如果代码里引入了外部的CSS,必须是先下载CSS样式表,再去执行JavaScript

结论

JavaScript脚本会阻塞HTML解析,而外部CSS样式表又会阻塞JavaScript脚本的执行

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值