浏览器的底层渲染机制
进程:一个程序,浏览器打开一个页面就开启一个进程
线程:程序中具体“干活的”,浏览器具备很多线程,这样就可以同时做很多事 一个进程包含 1 到多个线程(进程大)
- “同步编程”: 每一个同时只能做一件事,这件事处理完成才能处理下一件事情 -->单线程,同时只能处理一件事情
- 异步编程” :多线程,同时可以处理多件事情
浏览器具备的线程:
-
+ GUI渲染线程:自上而下渲染页面的:含html,css,img -
+ JS引擎线程:渲染和解析JS代码 -
+ HTTP网络请求线程:从服务器获取内容(最多同时可以开辟5~7个) -
+ 定时器监听线程:监听定时器是否到达时间 -
+ 事件监听线程:监听事件是否触发 -
+ ...
当我们从服务器获取HTML代码后,浏览器需要按照代码规则,绘制出对应的页面
-
@1 创建“DOM TREE”;GUI渲染线程,自上而下一行行去渲染解析代码;当渲染到底部的时候,浏览器已经规划出:当前页面的结构和层级嵌套关系(节点和节点之间的关系),这就是DOM树 -
@2 创建“CSSOM TREE”;在DOM TREE 生成之后,浏览器把从服务器获取的样式代码渲染,生成“CSSOM TREE”;在GUI渲染过程中,可能会遇到link、style、img、script等标签,遇到不同标签会有不同处理方案 -
@3 把DOM TREE 和 CSSOM TREE合并在一起,形成 RENDER TREE -
@4 Layout布局:根据视口大小,计算出每一个节点,在视口中的具体位置及大小等 -
@5 分层:规划出对应的层级,以及节点该在哪个层级上 -
@6 painting绘制:按照所有解析的规则,一层层的绘制
回流(Reflow)或重排:
页面第一次渲染完之后,后期基于某些操作,修改了某个节点在视口中的位置或大小,这样浏览器需要重新计算当前视口(当前层级中)所有节点的布局位置,也就是把 Layout操作重新来一遍,计算完层级,重新painting,我们把这个操作叫 “回流(Reflow)或重排”。回流非常消耗性能,我们常说的操作DOM消耗性能,指的就是这件事。
引发回流:
+改变节点位置、大小
+新增或引出节点
+视口大小改变
+内容改变引发节点的大小改变...
而且回流一定引发重绘,页面第一次渲染就会有一次Layout和painting
某些DOM操作(例如改变文字颜色,背景)并不会对节点的位置产生影响,此时仅需 “重绘(Repaint)”
在页面运行时的性能优化
@1 读写分离:把设置元素样式代码和获取元素样式代码分离编写,不要穿插混合一起
渲染队列机制:当代浏览器的机制,当前上下文代码执行过程当中,遇到修改元素样式代码,浏览器并没有立即处理,而是先存放在渲染队列中,当遇到获取元素样式操作(或当前上下文代码执行完毕),才会刷新渲染队列(即把队列中对DOM操作统一处理),只引发一次回流
@2 统一修改样式
+ 把需要修改的样式统一写到css样式表中,基于修改元素样式类名达到修改样式的需求,只会引发一次回流
box.classList.add('XXX');
+ 基于cssText处理
box.style.cssText='width:100px;height:200px';
@3 新增DOM元素采用批量新增
+ 文档碎片:一个临时存储DOM节点容器(可以设置一个变量承接)
+ 模板字符串拼接
@4 修改元素的样式尽可能使用 transform 变形属性/opacity ,它的改变不会引发DOM回流,浏览器内部对其做了硬件加速,也可以理解为这就是“规定”
@5 修改样式的元素尽可能在单独的文档流中,虽然不能减少DOM回流,但是可以处理的更快,渲染绘制的时候只针对当前文档流
@6 JS处理动画的规则:
+ 能用css3动画解决的,坚决不用JS动画
+ 适当情况下,牺牲平滑度来换取速度
不要直接操作DOM,使用vue和React框架,我们只操作数据,操作DOM交给框架去做
** 前端性能优化核心操作:减少操作DOM产生的回流、重排、**
加快页面第一次渲染,减少白屏等待时间
关于样式做法:
+ 遇到内嵌式的<style>: 无需去服务器获取样式代码,但也不会立即渲染,要等到DOM树生成完,外链式获取的样式代码都拿到了,再按照编写的先后顺序依次渲染解析样式代码,以此保证css优先级正确
+ 遇到外链式的<link>: 单独开辟一个新的HTTP线程去服务器获取样式代码,而GUI渲染线程会继续向下渲染,属于‘异步操作’;也就是CSS样式代码渲染,一般发生在DOM树生成之后
+ 遇到导入式@import: 也会开辟一个HTTP线程,从服务器获取样式代码,只不过它会阻碍GUI渲染,也就是,样式代码没有请求回来之前,GUI暂停渲染,‘同步操作’、
+ 遇到<img>:
+ 开辟一个新的HTTP线程请求图片,GUI继续渲染,“异步编程”
+ 当获取到图片资源后,浏览器首先进行编码,然后按照编码进行渲染绘制
+ 遇到<script>
+ 开辟新的HTTP线程去获取代码,同时阻碍GUI渲染, “同步编程”
优化方案
@1 当样式代码较少的情况下,直接使用内嵌式放在HTML页面即可,没必要从服务器获取,这样可以在DOM TREE生成后立即渲染样式,生成CSSOM TREE,加快页面渲染速度(移动端经典优化方案)
@2 样式代码较多,基于<link>外链式
+ 最好把CSS都写入到一个样式表中,只请求一次即可,减少HTTP请求次数
+ 把<link>放在HEAD中,这样保证样式资源的提前获取,当DOM TREE生成后可能样式代码已经回来了
@3 非必要情况,不用@import,它会阻碍GUI渲染
遇到img优化方案
@1 虽然图片不会阻碍GUI渲染,但是每次请求都会占用一个HTTP线程,而浏览器可同时开启的线程是有限的,会导致其他类型资源获取延后,所以需要做图片的“懒加载”
@2 如果想加快图片的渲染,我们可以跳过 获取资源&编码 这两步,直接让浏览器 绘制
+ 如果图片较大,生成的BASE64码会很多,这样会增加CSS文件的体积,而且代码维护麻烦,所以不能滥用
+ 小图片可以BASE64,大图只有在各种解决办法都试过之后,发现还是达不到自己的要求,此时尝试使用BASE64,会发现此时图片渲染速度会明显提高(后期可以通过webpack可以自动打包BASE64)
遇到script优化方案
@1 把<script>放在页面底部
@2 把<script>获取资源改为“异步编程”,在标签上加 async/defer 属性
+ <script async>:开辟新的HTTP,去获取JS代码;获取过程中,GUI继续渲染,获取到了之后立即暂停GUI,先把js继续执行,再执行GUI; “半异步执行”
+ <script defer>:和link相似,开辟新的HTTP,‘异步’去获取JS代码,GUI继续渲染,哪怕js获取到,也会先把GUI渲染完成,再渲染解析JS; “异步执行”
如果没有和JS之间的相互依赖,使用async即可;但如果需要按照编写的先后顺序去执行,就使用defer
@3 和CSS文件一样,都合并到一个文件中,减少HTTP请求次数