浏览器中的页面是如何渲染生成的

北京最大青春痘医院 http://pf.39.net/bdfyy/bjzkbdfyy/210312/8740638.html

DOM树:JavaScript是如何影响DOM树构建的?

什么是DOM

从网络传给渲染引擎的HTML文件字节流是无法直接被渲染引擎理解的,所以要将其转化为渲染引擎能够理解的内部结构DOM。DOM提供了对HTML文档结构化的表述。在渲染引擎中,DOM有三个层面的作用。

从页面的视角来看,DOM是生成页面的基础数据结构。从JavaScript脚本视角来看,DOM提供给JavaScript脚本操作的接口,通过这套接口,JavaScript可以对DOM结构进行访问,从而改变文档的结构、样式和内容。从安全视角来看,DOM是一道安全防护线,一些不安全的内容在DOM解析阶段就被拒之门外了。

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

DOM树如何生成的

渲染引擎内部的**HTML解析器(HTMLParsr)**模块将HTML字节流转换为DOM结构。

HTML解析器并不是等整个文档加载完后再解析的,而是网络进程加载了多少数据,HTML解析器便解析多少数据。

具体流程

网络进程接收到响应头后,会根据响应头中的contnt-typ字段来判断文件的类型,比如contnt-typ的值是“txt/html”,那么浏览器就会判断这是一个HTML类型的文件,然后为该请求选择或者创建一个渲染进程。渲染进程准备好之后,网络进程和渲染进程之间会建立一个共享数据的管道,网络进程接收到数据后就往这个管道里面放,而渲染进程则从管道的另外一端不断地读取数据,并同时将读取的数据传给HTML解析器。字节流==DOM第一阶段,通过分词器将字节流转换为Tokn。Tokn分为TagTokn和文本Tokn,TagTokn又分为StartTag和EndTag第二阶段和第三阶段同步进行,需要将Tokn解析为DOM节点,并将DOM节点添加到DOM树中。HTML解析器维护了一个Tokn栈结构,该栈主要用来计算节点之间的父子关系,在第一个阶段中生成的Tokn会被按照顺序压到这个栈中。具体的处理规则如下所示:①如果压入到栈中的是StartTagTokn,HTML解析器会为该Tokn创建一个DOM节点,然后将该节点加入到DOM树中,它的父节点就是栈中相邻的那个元素生成的节点。②如果分词器解析出来是文本Tokn,那么会生成一个文本节点,然后将该节点加入到DOM树中,文本Tokn是不需要压入到栈中,它的父节点就是当前栈顶Tokn所对应的DOM节点。③如果分词器解析出来的是EndTag标签,比如是EndTagdiv,HTML解析器会查看Tokn栈顶的元素是否是StarTagdiv,如果是,就将StartTagdiv从栈中弹出,表示该div元素解析完成。通过分词器产生的新Tokn就这样不停地压栈和出栈,整个解析过程就这样一直持续下去,直到分词器将所有字节流分词完成。HTML解析器开始工作时,会默认创建了一个根为documnt的空DOM结构

JavaScript是如何影响DOM生成的

在HTML中内嵌script标签,当解析到script标签时,渲染引擎判断这是一段JavaScript脚本,此时HTML解析器就会暂停DOM的解析,因为接下来的JavaScript可能要修改已经生成的DOM结构。在HTML中引入外部JavaScript脚本,需要先下载这个脚本,JavaScript文件的下载过程会阻塞DOM解析,而通常下载又是非常耗时的,会受到网络环境、JavaScript文件大小等因素的影响。不过Chrom解析器做了很多优化,其中一个主要优化是预解析操作。当渲染引擎收到字节流之后,会开启一个预解析线程,用来分析HTML文件中包含的JavaScript、CSS等相关文件,解析到相关文件之后,预解析线程会提前下载这些文件。JS线程会阻塞DOM,但也有一些策略来规避:比如使用CDN来加速JavaScript文件的加载,压缩JavaScript文件体积。如果JavaScript文件中没有操作DOM相关的代码,可以将该JavaScript脚本设置为异步加载,通过async或dfr来标记代码。使用async标志的脚本文件一旦加载完成,会立即执行;而使用了dfr标记的脚本文件,需要在DOMContntLoadd事件之前执行。

额外说明

渲染引擎还有一个安全检查模块叫XSSAuditor,是用来检测词法安全的。在分词器解析出来Tokn之后,它会检测这些模块是否安全,比如是否引用了外部脚本,是否符合CSP规范,是否存在跨站点请求等。如果出现不符合规范的内容,XSSAuditor会对该脚本或者下载任务进行拦截。

渲染流水线:CSS如何影响首次加载时的白屏时间

前面说过,当渲染引擎接受到HTML文件字节流时,会先开启一个预解析线程,若遇到js或css文件,就会提前下载这些数据。在构建完DOM之后,css文件还未下载完成之前,渲染流水线无事可做,因为下一步是合成布局树,而合成布局树需要CSSOM和DOM,所以这里要等待CSS加载结束并解析成CSSOM。和HTML一样,渲染引擎也无法直接理解CSS文件内容,所以需要将其解析成渲染引擎能直接理解的结构即CSSOM,作用有两个,**一是提供给JavaScript操作样式表的能力,二是为布局树的合成提供基础的样式信息。**这个CSSOM体现在DOM中就是documnt.stylShts。在解析DOM过程中,如果遇到JavaScript脚本,则需要先暂停DOM解析去执行JavaScript,因为JavaScript有可能会修改当前状态下的DOM。不过在执行js脚本前,如果页面包含了外部css文件的引用,或通过styl标签内置了css内容,那么渲染引擎还需要将这些内容转换为CSSOM,因为js有修改CSSOM的能力,所以在执行js之前,还需要依赖CSSOM。也就是说CSS在部分情况下也会阻塞DOM的生成。

影响页面展示的因素以及优化策略

渲染流水线影响到了首次页面展示的速度,而首次页面展示的速度又直接影响到了用户体验。

发起URL请求,到首次显示页面内容,在视觉上经历了三个阶段:

第一个阶段:等请求发出去之后,到提交数据阶段,这时页面展示出来的还是之前页面的内容。这里在《宏观下的浏览器》第部分解释了原因。第二个阶段:提交数据之后渲染进程会创建一个空白页面,我们通常把这段时间称为解析白屏,并等待CSS文件和JavaScript文件的加载完成,生成CSSOM和DOM,然后合成布局树,最后还要经过一系列的步骤准备首次渲染。第三个阶段:等首次渲染完成之后,就开始进入完整页面的生成阶段了,然后页面会一点点被绘制出来。

第二个阶段的主要问题是白屏时间,如果白屏时间过久,就会影响到用户体验。为了缩短白屏时间,我们来挨个分析这个阶段的主要任务,包括了解析HTML、下载CSS、下载JavaScript、生成CSSOM、执行JavaScript、生成布局树、绘制页面一系列操作。

通常情况下的瓶颈主要体现在下载CSS文件、下载JavaScript文件和执行JavaScript。

所以要想缩短白屏时长,可以有以下策略:

通过内联JavaScript、内联CSS来移除这两种类型的文件下载,这样获取到HTML文件之后就可以直接开始渲染流程了。但并不是所有的场合都适合内联,那么还可以尽量减少文件大小,比如通过wbpack等工具移除一些不必要的注释,并压缩JavaScript文件。还可以将一些不需要在解析HTML阶段使用的JavaScript标记上sync或者dfr。对于大的CSS文件,可以通过媒体查询属性,将其拆分为多个不同用途的CSS文件,这样只有在特定的场景下才会加载特定的CSS文件。

通过以上策略就能缩短白屏展示的时长了,不过在实际项目中,总是存在各种各样的情况,这些策略并不能随心所欲地去引用,所以还需要结合实际情况来调整最佳方案。

分层和合成机制:为什么CSS动画比JavaScript高效

显示器如何显示图像的

每个显示器都有固定的刷新频率,通常是60HZ,也就是每秒更新60张图片,更新的图片都来自于显卡中一个叫前缓冲区的地方,显示器所做的任务很简单,就是每秒固定读取60次前缓冲区中的图像,并将读取的图像显示到显示器上。显卡的职责就是合成新的图像,并将图像保存到后缓冲区中,一旦显卡把合成的图像写到后缓冲区,系统就会让后缓冲区和前缓冲区互换,这样就能保证显示器能读取到最新显卡合成的图像。通常情况下,显卡的更新频率和显示器的刷新频率是一致的。但有时候,在一些复杂的场景中,显卡处理一张图片的速度会变慢,这样就会造成视觉上的卡顿。

帧VS帧率

当你通过滚动条滚动页面,或者通过手势缩放页面时,屏幕上就会产生动画的效果。之所以你能感觉到有动画的效果,是因为在滚动或者缩放操作时,渲染引擎会通过渲染流水线生成新的图片,并发送到显卡的后缓冲区。大多数设备屏幕的更新频率是60次/秒,这也就意味着正常情况下要实现流畅的动画效果,渲染引擎需要每秒更新60张图片到显卡的后缓冲区。我们把渲染流水线生成的每一副图片称为一帧,把渲染流水线每秒更新了多少帧称为帧率,比如滚动过程中1秒更新了60帧,那么帧率就是60Hz(或者60FPS)。为了解决动画卡顿问题,就要解决每帧生成时间过久的问题,为此Chrom对浏览器渲染方式做了大量的工作,其中最卓有成效的策略就是引入了分层和合成机制。分层和合成机制代表了当今最先进的渲染技术。

如何生成一帧图像

任意一帧的生成方式,有重排、重绘和合成三种方式。

这三种方式的渲染路径是不同的,通常渲染路径越长,生成图像花费的时间就越多。比如重排,它需要重新根据CSSOM和DOM来计算布局树,这样生成一幅图片时,会让整个渲染流水线的每个阶段都执行一遍,如果布局复杂的话,就很难保证渲染的效率了。而重绘因为没有了重新布局的阶段,操作效率稍微高点,但是依然需要重新计算绘制信息,并触发绘制操作之后的一系列操作。相较于重排和重绘,合成操作的路径就显得非常短了,并不需要触发布局和绘制两个阶段,如果采用了GPU,那么合成的效率会非常高。

Chrom如何实现合成操作

三个词概括:分层、分块、合成

分层和合成

为什么引入分层和合成

通常页面的组成是非常复杂的,有的页面里要实现一些复杂的动画效果,比如点击菜单时弹出菜单的动画特效,滚动鼠标滚轮时页面滚动的动画效果,当然还有一些炫酷的D动画特效。如果没有采用分层机制,从布局树直接生成目标图片的话,那么每次页面有很小的变化时,都会触发重排或者重绘机制,这种“牵一发而动全身”的绘制策略会严重影响页面的渲染效率。

在这个过程中,将素材分解为多个图层的操作就称为分层,最后将这些图层合并到一起的操作就称为合成。所以,分层和合成通常是一起使用的。

Chrom怎么实现分层与合成

在渲染流水线中,分层体现在生成布局树后,渲染引擎会根据布局树的特点将其转换为层树(LayrT),层树是渲染流水线后续流程的基础结构。层树中的每个节点都对应着一个图层,下一步的绘制阶段就依赖于层树中的节点。绘制阶段其实并不是真正地绘出图片,而是将绘制指令组合成一个列表有了绘制列表之后,就需要进入光栅化阶段了,光栅化就是按照绘制列表中的指令生成图片。每一个图层都对应一张图片,合成线程有了这些图片之后,会将这些图片合成为“一张”图片,并最终将生成的图片发送到后缓冲区。这就是一个大致的分层、合成流程。需要重点


转载请注明:http://www.aierlanlan.com/tzrz/2856.html