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