思考
不知道大家是否思考过这几个问题:
当我们在浏览器中输入url后,到底发生了什么?
h5,css,js代码执行的顺序是什么?什么情况下会阻塞页面?又有什么办法可以提高页面响应速度呢?
如果你也一时半会无法完整回答,那么请跟着我的文章一起看看~
一,解析URL
https://blog.csdn.net/sleep_i_like/article/details/136065513?spm=1001.2014.3001.5502
协议:https
域名:blog.csdn.net
路径:sleep_i_like/article/details/136065513
端口:https默认端口是443,也就是说以上的url和
https://blog.csdn.net :443/sleep_i_like/article/details/136065513?spm=1001.2014.3001.5502
访问效果是一样的
参数:?spm=1001.2014.3001.5502
这里需要说明的是DNS解析是耗时的,我们可以通过dns-prefetch来将除站点域名以外的其它域名进行异步预解析:
<link rel="dns-prefetch" href="https://ant.design/components/overview-cn/">
没有使用dns-prefetch页面处理流程:
使用了dns-prefetch后的处理流程,因为提前异步解析了DNS所以在后续使用时候不会阻塞页面
二,渲染页面
首先要知道浏览器解析是从上往下一行一行地解析的。
基于http协议是不用担心数据包丢失的问题与乱序的,因为丢包和重传都是在tcp层解决的。http能保证数据按照顺序接收的!
接收到http响应头中的「content-type」类型时就开始准备渲染进程了,渲染进程是不需要等待所有资源加载完成才进入渲染,当接收到数据流之后,就会开启解析渲染进程,网络进程和渲染进程之间我们可以理解为一根管道,当网络进程接收到数据之后就会往管道里面输送,渲染进程就会在另外一端进行接收,响应体数据一旦接受到便开始做DOM解析了。
浏览器获取HTML并开始构建DOM(文档对象模型 - Document Object Model)。然后获取CSS并构建CSSOM(CSS对象模型 - CSS Object Model)。然后将DOM与CSSOM结合,创建渲染树(Render Tree)。然后找到所有内容都处于网页的哪个位置,也就是布局(Layout)这一步。最后,浏览器开始在屏幕上绘制像素。
合成线程拿到每个层、每个块的位图后,生成一个个「指引(quad)」信息。
指引会标识出每个位图应该画到屏幕的哪个位置,以及会考虑到旋转、缩放等变形。
变形发生在合成线程,与渲染主线程无关,这就是transform效率高的本质原因。
合成线程会把 quad 提交给 GPU 进程,由 GPU 进程产生系统调用,提交给 GPU 硬件,完成最终的屏幕成像。
1.构建DOM
浏览器从磁盘或网络读取HTML的原始字节,并根据(响应头中content-type)文件的指定编码(例如 UTF-8)将它们转换成字符。
符号化是词法分析的过程(Tokenizer),将输入解析为符号,html的符号包括开始标签、结束标签、属性名及属性值。
符号识别器识别出符号后,将其传递给树构建器,并读取下一个字符,以识别下一个符号,这样直到处理完所有输入。
那么如何保证节点之间的父子关系和兄弟关系呢?
HTML 解析器维护了一个 Token 栈结构,该 Token 栈主要用来计算节点之间的父子关系,在第一个阶段中生成的 Token 会被按照顺序压到这个栈中。具体的处理规则如下所示:
如果压入到栈中的是 StartTag Token,HTML 解析器会为该 Token 创建一个 DOM 节点,然后将该节点加入到 DOM 树中,它的父节点就是栈中相邻的那个元素生成的节点。
如果分词器解析出来是文本 Token,那么会生成一个文本节点,然后将该节点加入到 DOM 树中,文本 Token 是不需要压入到栈中,它的父节点就是当前栈顶 Token 所对应的 DOM 节点。
如果分词器解析出来的是 EndTag 标签,比如是 EndTag div,HTML 解析器会查看 Token 栈顶的元素是否是 StarTag div,如果是,就将 StartTag div 从栈中弹出,表示该 div 元素解析完成。
第三步(生成节点对象并构建DOM):事实上,构建DOM的过程中,不是等所有Token都转换完成后再去生成节点对象,而是一边生成Token一边消耗Token来生成节点对象。换句话说完整的一个开始和结束token化后就会被加载在dom中
带有结束标签标识的Token不会创建节点对象
节点对象包含了这个节点的所有属性。例如<img src=’'xx.png/>'标签最终生成出的节点对象中会保存图片地址等信息。
2.构建CSSOM
不完整的CSS是无法使用的,因为CSS的每个属性都可以改变CSSOM,所以会存在这样一个问题:假设前面几个字节的CSS将字体大小设置为16px,后面又将字体大小设置为14px,那么如果不把整个CSSOM构建完整,最终得到的CSSOM其实是不准确的。所以必须等CSSOM构建完毕才能进入到下一个阶段,哪怕DOM已经构建完,它也得等CSSOM,然后才能进入下一个阶段。
所以,CSS的加载速度与构建CSSOM的速度将直接影响首屏渲染速度
3.构建渲染树
浏览器使用 DOM 和 CSSOM 构建出 Render Tree。此时不像构建 DOM 一样把所有节点构建出来,浏览器只构建需要在屏幕上显示的部分,因此像< head>,< meta>这些标签就无需构建了。同时,对于 display: none的元素,也无需构建。
display: none告诉浏览器这个元素无需出现在 Render Tree 中,但是 visibility: hidden只是隐藏了这个元素,但是元素还占空间,会影响到后面的 Layout,因此仍然需要出现在 Render Tree 中。
当Render Tree构建完成后,已经得到了所有元素的自身信息,但是还不知道它们相对于 Viewport 的位置和大小,Layout 这过程需要计算的就是这两个信息。布局完成后,GPU根据「指引(quad)」信息 将渲染树转换成屏幕上的像素。
4.加载javascript
上面在生成cssom时候我们说过,不完整的CSS是无法使用的,因为css每个属性都可以改变CSSOM,那么js代码不光可以改变cssom也可以更改dom,所以JavaScript的加载、解析与执行,如果放在了< head >中是会阻塞DOM和CSSOM的构建。
当然这个还是需要分情况的:
情况1:如果js代码在h5和css后面,并且并没有修改dom和cssom,那么并不牵扯js阻塞问题
情况2:当js文件被放置在head标签内部时,浏览器会先加载js文件并执行它,然后才会继续解析HTML文档。因此,如果JavaScript文件过大或服务器响应时间过长,就会导致页面一直处于等待状态,进而影响DOM和CSSOM的构建。
情况3:如果dom已经构建,在随后的JavaScript代码执行时,有对DOM结构或者CSSOM进行了修改,那么浏览器需要重新计算布局(reflow)和重绘(repaint),这个过程会较为耗时,并且会阻塞DOM和CSSOM的构建。
首先弄明白2个script的属性defer和async:
async:表示应该立即开始下载脚本,但不能阻止其他的页面动作,比如下载资源或等待其他脚本加载。
使用async就相当于告诉浏览器立即下载脚本,此时和页面的渲染是异步的,但是当脚本加载完毕后会立即执行该脚本如果此时HTML还未解析完成,会造成阻塞。
<!DOCTYPE html>
<html>
<head>
<title>使用 async 修饰的 script 标签</title>
<script async src="script1.js"></script>
<script async src="script2.js"></script>
</head>
<body>
<h1>使用 async 修饰的 script 标签</h1>
</body>
</html>
由于脚本是异步加载的,它将在加载完成后立即执行,而不会按照它们在文档中的顺序执行。这意味着如果引入多个js脚本可能先下载完成的 script2.js 脚本执行。会优先于script1脚本执行;因此,如果脚本之间有依赖关系,需要谨慎管理加载顺序。
此外,因为 async 修饰的脚本会在它们被加载时立即执行,而不需要等待整个文档解析完成。
这可能会导致一些问题,例如脚本可能无法访问尚未解析的 DOM 元素。为了避免这些问题,通常建议将脚本放置在 元素的结尾,以确保在脚本执行时 DOM 已经完全解析。
defer 属性也用于异步加载脚本,但与 async 不同的是,defer加载完后不会立即执行, 会延迟到整个页面解析完成在执行, defer 会保持脚本的执行顺序与其在文档中的顺序一致。
与 async 不同,使用 defer 修饰的脚本会在整个文档解析完成后执行,但在 DOMContentLoaded 事件之前执行。这意味着脚本可以访问完整的 DOM 结构,而不必等待整个文档加载完成。
1.DOMContentLoaded事件
在 HTML 文档解析过程中,当浏览器完成构建 DOM 树,所有的初始 HTML 已经完全加载和解析,但外部资源(如图片、样式表和嵌入的框架)可能还没有完全下载时触发。也就是说,DOMContentLoaded 事件标志着页面的基本结构已经准备就绪,可以进行操作和修改 DOM 元素。
2.load事件 load 事件是在整个 HTML 文档以及所有的外部资源(如图片、样式表、嵌入的框架等)都已经完全加载和解析完成后触发的事件。
使用 load 事件可以确保页面的所有资源都已经被下载并解析完成,可以进行一些比较耗时或需要等待资源加载的操作。不过需要注意的是,load事件的触发时间比 DOMContentLoaded 事件要晚,所以在处理一些与页面元素相关的操作时,需要选择合适的事件来监听
其次我们弄明白浏览器的重绘(repaint)与重排(reflow)
对DOM的修改引发了DOM几何元素的变化,渲染树需要重新计算,会触发重排 而只会改变visibility、outline、背景色等属性导致样式的变化,使浏览器需要根据新的属性进行重绘。
相比而言,重排会产生比重绘更大的开销。所以,我们在实际生产中要严格注意减少重排的触发。
触发重排的操作:
1页面初始渲染:页面第一次渲染所有组件都要进行首次布局,这是开销最大的一次重排。
2.浏览器窗口尺寸改变
3.元素位置和尺寸发生改变的时候
4.新增和删除可见元素
5.元素内容发生改变(文字数量或图片大小等等)
6.元素字体大小变化。
7.激活CSS伪类(例如::hover)。
8.设置style属性
9.查询某些属性或调用某些方法。比如说:offsetTop、offsetLeft、 offsetWidth、offsetHeight、scrollTop、scrollLeft、scrollWidth、scrollHeight、clientTop、clientLeft、clientWidth、clientHeight
除此之外,当我们调用getComputedStyle方法,或者IE里的currentStyle时,也会触发重排,原理是一样的,都为求一个“即时性”和“准确性”。