浏览器遇到各种资源的加载机制
客户端从服务器获取到需要渲染页面的源代码后,会开辟一个“GUI渲染线程”,自上而下解析代码,最后绘制出对应的页面。
自上而下渲染解析代码的过程是【同步】的,但是有些操作也是异步的:
- 1.关于CSS资源的加载
- 遇到的是【style】"内嵌样式”,则“同步”交给GUI渲染线程解析
- 遇到的是【link】“外链样式”, 则会异步开辟一个新的HTTP网络请求线程,不等资源信息请求回来,而是GUI渲染线程继续往下渲染;等GUI渲染线程同步操作都处理完后,再把基于HTTP网络请求回来的资源文件进行解析渲染(注意:同一个源下,根据不同浏览器,最多只允许同时开辟4~7个HTTP线程“HTTP的并发数”)
- 遇到@import“导入式样式”,则是“同步”开辟一个新的HTTP网络请求线程去请求资源,但是在资源文件没有请求回来之前,GUI渲染线程会被“阻塞”,不允许其继续向下渲染
- 2.遇到【script】资源的请求
- 默认都是同步的,必须基于HTTP网络请求线程,把资源请求回来之后,并且交给“JS渲染线程”渲染解析完成后,GUI渲染线程才能继续向下渲染,所以【script】默认也是“阻塞”GUI渲染的。
- async属性:遇到【script async】首先也是开辟一个HTTP网络请求线程去请求加载资源文件,但是与此同时GUI渲染线程继续向下渲染[把默认的同步改为异步了],但是一旦当资源请求回来之后,会中断GUI的渲染,先把请求回来的JS进行渲染解析
- defer属性:遇到【script defer】和async类似,都是新开辟HTTP网络请求线程去请求加载资源文件,与此同时GUI渲染线程还会继续渲染【异步】,但是不一样的是:defer和link类似,是在GUI同步的代码渲染完成后,才会渲染解析请求回来的JS代码
- 3.遇到【img】或者音频资源
- 遇到这些资源,也会发送新的HTTP网络线程,请求加载对应的资源文件,不会阻塞GUI的渲染【异步】;当GUI渲染完成后,才会把请求回来的资源进行渲染解析;
- Webkitl浏览器预测解析:chrome的预测加载扫描器html-preload-scanner通过扫描节点中的“src”、“link”等属性,找到外部连接资源后进行预加载,避免了资源加载的等待时间,同样实现了提前加载以及加载和执行的分离。也就是说在GUI渲染线程渲染之前,浏览器预先找到link等资源预先去请求加载
页面渲染步骤
- DOM TREE(DOM树):自上而下渲染完页面,整理好整个页面的DOM结构关系
- CSSOM TREE(样式树):当把所有样式资源请求加载回来后,按照引入的CSS顺序,一次渲染样式代码,生成样式树
- RENDER TREE(渲染树):把生成的DOM树和CSSOM树合并在一起,生成渲染树(设置display:none的元素是不进行处理的)
- Layout布局/回流/重排:根据生成的渲染树,计算它们在设备视口(viewport)内确切的位置和大小
- 分层处理:按照层级定位分层处理,每一个层级都会详细规划出具体的绘制步骤
- Painting:按照每一个层级计算出来的绘制步骤,开始绘制页面
前端性能优化
CRP:关键渲染路径
- 生成DOM TREE
- 减少DOM的层级嵌套
- 不要使用“非标准”标签
- 生成CSSOM TREE
- 尽可能不使用@import,会阻塞GUI渲染
- 如果css代码比较少,尽可能使用style内嵌样式(尤其是移动端开发)
- 如果使用link,尽可能把所有样式资源合并成一个CSS且压缩,减少HTTP请求数量,以及渲染CSSOM树的时候也不需要再计算依赖关系
- CSS选择器链短一些,因为CSS选择器渲染是从右到左的
- 把link等导入CSS的操作放在HEAD中,目的是:一加载页面就开始请求资源,同时GUI去生成DOM树,CSS等资源预先加载
- 对于其它资源优化
- 对于script尽可能放在页面底部(防止阻塞GUI渲染);对于部分script需要使用async或defer
- async是不管JS的依赖关系的,哪一个资源先获取到,就先把这个资源代码渲染执行
- defer则和link一样,是等待所有script defer都请求回来后,按照导入顺序/依赖关系依次渲染执行
- 对于img资源
- 懒加载:第一次加载页面时不要加载图片,哪怕它是异步的,也占据了HTTP并发的数量,导致其它资源延后加载
- 图片的BASE64:不用请求加载图片,BASE64码基本上代表就是图片,而且页面渲染图片的时候速度也会更快。但慎用(会使代码变很长很长)
- Layout/Painting:重要的优化手段:减少DOM的回流和重绘
- 第一次加载页面必然会有一次回流和重绘
- 触发回流操作后,也必然会触发重绘,如果只是单纯的重绘则不会触发回流;性能优化点重点都在回流上;
- 样式分离读写,把修改样式和获取样式代码分开
- 不要自己直接操作DOM,例如VUE/REACT
- 批量添加DOM元素时尽量不要使用for循环和createElement去一个个添加,可以使用字符串拼接或者使用文档碎片(documentFragment,临时容器)一次性添加,这样只会引发一次回流
- 把动画等频繁操作样式的操作,运用到position:fixed/absolute上(脱离文档流,单独一层);利用分层机制,如果只改变一个层面上的位置大小信息,浏览器回流和重绘的速度回快很多
- 修改元素的transform/opacity(filters)不会引发DOM的回流(浏览器硬件加速,缺点耗内存)
当代浏览器关于回流的渲染队列机制:在当前上下文操作中,遇到一行修改样式的代码,并没有立即通知浏览器渲染,而是把其放在渲染队列中,接下来看是否还有修改样式的代码,如果有继续放在队列中等待,一直到再也没有修改样式的代码后者“遇到一行获取样式的操作”,则会刷新浏览器的渲染队列(也就是把现在队列中修改样式的操作,统一告诉浏览器渲染,这样只会引发一次回流)