浏览器渲染原理

浏览器渲染整体来讲分为如下几步javascript

  1. 浏览器经过HTTP或者HTTPS协议,先服务端请求页面
  2. 把请求回来的HTML解析成DOM树
  3. 把CSS解析成CSSOM树
  4. 把DOM树和CSSOM树组合在一块儿,生成渲染树(RENDER TREE)
  5. 经过渲染树计算出布局(layout)
  6. 渲染引擎会遍历Render树,绘制(painting)到界面上

网络部分放到TCP协议中,在这就很少说了css

渲染流程

构建DOM树、CSSOM树

DOM树和CSSOM树的构建流程很是像,就以DOM树为例html

浏览器从服务器获取的是16进制文件流,好比3C 62 6F 64 79 3E 48 65.....,浏览器要把16进制的Bytes转化成字符串,再遍历这个字符串解析成tokensvue

浏览器是怎么将字符串解析成tokens的。使用的方法是状态机java

状态机怎么执行的react

  • 接受到<字符。多是一个标签的开头,开启一个状态
    • 下一个字符是字母,就是标签名
    • 下一个字符是!,就是注释
    • ....
  • 接受到非<字符。多是一个文本节点,开启一个状态
    • ....

浏览器一步步将文件流转化为字符串再经过状态机转化为token,获得token后,按照W3C规则转换成DOM树。css3

简单总结下:后端

  1. 浏览器边接受文件流(进制编码内容)边编译为token
  2. 按照w3c规则进行字符解析,生成对应的Tokens,最后转换为浏览器内核能够识别渲染的DOM节点
  3. 按照节点最后解析为对应的 DOM TREE、CSSOM TREE

须要注意的事:浏览器

  • DOM树构建过程可能会由于css、js而阻塞
  • DOM树构建与CSSOM树构建能够同时进行
  • 不可见标签也会出如今dom树中
  • CSSOM树构建过程可能会由于js而阻塞

构建渲染树(Render Tree)

浏览器根据DOM树和CSSOM树生成带有标签和样式信息的渲染树(Render Tree)。渲染树与DOM树不是一一对应的关系,不显示的节点不会出如今渲染树上。缓存

布局(Layout)

根据渲染树提供的节点和样式,计算元素在视口中的确切的大小和位置。

绘制(paining)

将元素计算后的大小和位置渲染到页面上的过程。渲染树的绘制工做是浏览器调用UI后端组件完成的。

回流和重绘(reflow和repaint)

一些操做会引起元素位置或者大小的变化,这样浏览器须要从新进行Lauout计算(回流/重排),重排完成后,浏览器须要从新绘制(重绘)。

  • 第二次或屡次布局(Lauout)就是回流/重排(reflow)
  • 第二次或屡次绘制(paining)就是重绘(repaint)

若是是改变一些基础样式好比颜色,则不须要重排,只须要重绘便可。

  • 重绘:元素样式改变
    • 例如:color、visibility...
  • 回流:元素大小、位置发生变化
    • 例如:添加删除元素、视口大小改变...

重绘不必定会回流,可是回流必定会触发重绘

性能优化:减小DOM的回流

  1. 放弃传统操做dom的时代,基于vue/react开始数据影响视图模式(mvvm/mvc/virtual dom/dom diff....)
  2. 分离读写操做(如今的浏览器都有渲染队列的机制)
  3. 样式集中改变
  4. 缓存布局信息
  5. 元素批量修改
  6. 使用DocumentFragment将须要屡次修改的DOM元素缓存,最后一次性append到真实DOM中渲染
  7. 变化多的元素脱离文档流,造成新的Render Layer,下降回流成本
  8. css3硬件加速(GPU加速)(会占用大量内存)

资源加载

浏览器自上而下读取代码,读取到资源文件

css

使用css有三种方式:使用link、@import、内联样式,其中link和@import都是导入外部样式。它们之间的区别:

  • link:浏览器会派发一个新等线程(HTTP线程)去加载资源文件,与此同时GUI渲染线程会继续向下渲染代码
  • @import:GUI渲染线程会暂时中止渲染,去服务器加载资源文件,资源文件没有返回以前不会继续渲染(阻碍浏览器渲染)
  • style:GUI直接渲染

另外外部样式若是长时间没有加载完毕,浏览器为了用户体验,会使用浏览器会默认样式,确保首次渲染的速度。因此css通常写在headr中,让浏览器尽快发送请求去获取css样式。

javascript

JavaScript执行线程与GUI渲染线程不能同时执行,这就意味着执行js代码势必会阻塞页面渲染。为了避免阻塞页面的渲染,能够:

  • script标签放在页面的尾部,确保dom生成完再加载js
  • 尽量使用defer、async

关于<script><script defer><script async>的区别(配合图片食用更佳)

  • <script>:当即中止页面渲染去加载资源文件,当资源加载完毕后当即执行js代码,js代码执行完毕后继续渲染页面
  • <script defer>:开辟新的线程去加载资源文件,当资源加载完毕后等待页面渲染,页面渲染完毕后再执行js代码
  • <script async>:开辟新的线程去加载资源文件,当资源加载完毕后当即执行js代码,js代码执行完毕后继续渲染页面(特别注意:多个async js执行顺序是按照加载完毕的顺序,非js请求顺序)

\ 阻塞页面渲染(GUI线程) 当即加载js资源 js加载完毕后当即执行 按照script标签顺序执行脚本
script
defer
async

补充:window.onload 和 DOMContentLoaded 的区别

onload:是页面资源加载完毕,包括图片、视频资源

DOMContentLoaded:DOM渲染完成

性能优化

了解这么多,最终仍是要为了性能优化服务。除了已经提过的减小回流的优化外,还有:

  • 减小DOM树渲染的时间
    • HTML层级不要太深
    • 标签语义化(减小不标准语义化的特殊处理)
  • 减小CSS树渲染时间
    • 减小层级嵌套(选择器是从右向左解析的)(less、sass嵌套是个大坑,注意)
  • 减小资源加载时间
    • 利用浏览器并行加载资源次数、请求大小,不要太少也不要太多(6-7)
    • 通常会把css放在页面开始位置,提早请求
      • 使用link,不用@import
      • 若是css少,尽量采用内嵌式
    • ssr 减小数据首页数据的请求
    • 使用骨架屏、loding(感官上的提升,不会实际提升速度)
    • 避免阻塞的js加载
      • js放在页面底部
      • 尽量使用defer、async