浏览器的底层渲染机制

139 阅读8分钟

浏览器的底层渲染机制

浏览器渲染机制:当我们从服务器获取代码后,浏览器是如何把代码,渲染为页面及相关效果的 CRP(关键渲染路径)性能优化法则:了解浏览器底层处理的具体步骤,针对每一个步骤进行优化 JS中的同步和异步编程: 同步编程:上一件事情没有处理完,下一件事情无法处理 异步编程:上一件事情即便没有处理完,也无需等待,可以继续处理后面的事情 进程和线程:一个进程中可能包括多个线程

  • 进程:一般代表一个程序(或者浏览器打开一个页面就开辟一个线程)

  • 线程:程序中具体干事的人

浏览器是多线程的,当基于浏览器打开一个页面(开辟一个进程),会有不同的线程同时去做多件事情 JS是单线程的,只能一件事一件事的干

  • GUI渲染线程:用来渲染和解析HTML/CSS的 以及绘制页面

  • JS引擎线程:用来渲染和解析JS的

  • HTTP网络线程:用来从服务器获取相关资源文件的[同源下,最多同时开辟5~7个HTTP线程]

  • 定时器监听线程:监听定时器是否到时间(计时的)

  • 事件监听线程:监听事件是否触发的

浏览器底层渲染机制:

步骤一:生成DOM树 [DOM TREE] 当我们从服务器获取HTML代码后,浏览器会分配"GUI渲染线程"自上而下解析代码

  • 遇到<link> 会分配一个新的'HTTP线程'去获取对应的CSS资源,GUI继续向下渲染[异步]

  • 遇到<style> 无需获取资源 但是GUI也不会去立即渲染css代码,防止渲染顺序错乱;会等待DOM结构渲染完成

  • 访问的link等资源也获取到了,按照之前书写的顺序,依次渲染样式!
  • 遇到@import 也需要去服务器获取资源(记录HTTP线程),但是这个操作会把"GUI线程"挂起,无法继续向下渲染
  • 直到CSS资源获取到之后,GUI才会继续向下渲染[同步:阻碍GUI渲染]
  • 遇到<img> 和link是一样的,也是异步的,也会分配新的HTTP线程去获取图片资源,GUI继续向下渲染

  • 遇到<script> 因为JS中要涉及DOM的操作,所以遇到<script>,默认会阻碍GUI的渲染;

  • 先分配HTTP线程去获取JS资源,
  • 资源获取后再分配JS引擎把JS代码先渲染了,
  • 都渲染完了,GUI在继续向下渲染;

自上而下处理完成后,目前只是把页面中的DOM结构(节点),构建出对应的层级关系!而这就是DOM树![触发DOMContentLoaded事件]

===================

步骤二:生成CSSOM树 [CSSOM TREE] DOM树生成后,等待CSS资源都获取到,此时按照CSS书写的顺序,依次渲染和解析CSS代码(GUI渲染线程) 生成CSSOM树:计算出每个节点具备的样式[含某些样式是继承过来的等]

步骤三:合成渲染树 [RENDER TREE] 把DOM树和CSSOM树合并在一起,生成渲染树

步骤四:layout布局 & 回流/重排 按照当前可视窗口的大小,计算每一个节点在视图中的位置和大小

步骤五:分层 计算每一层(每一个文档流)中各个节点的具体绘制规则

步骤六:Painting绘制 & 重绘 按照计算好的规则,一层层的进行绘制

===================

CRP优化技巧:

1.我们最好把所有的css合并压缩为一个,只请求一次就把所有样式获取到即可,分多次请求,因为HTTP的并发限制和可能出现的网络拥堵问题等问题,导致并不如请求一次快!!

  • CSS合并为一个
  • JS合并为一个
  • 雪碧图
  • ...

2.尽可能不要使用@import导入式,因为他会阻碍GUI的渲染;如果CSS样式代码不是很多,使用style内嵌式更好(尤其是移动端开发);但是如果代码很多,还是使用link外链式[但是最好把link放在中];

3.图片懒加载一定要处理,在第一次渲染页面的时候,不要让图片资源的请求去占用有限的HTTP线程以及宽带资源,

优先本着CSS/JS中资源获取:当页面渲染完成后,再去根据图片是否出现在视口中,来加载真实的图片;

4.关于script标签的优化

  • 最好把script放在body的末尾,等待DOM结构加载完成,再去获取和解析JS[此时就可以获取页面中的DOM元素了]

  • 也可以基于监听事件去处理

  • window.onload:等待页面中所有资源(包含DOM结构/CSS/JS等资源)都加载完触发
  • window.addEventListener('DOMContentLoaded',function(){}):只需要等待DOM结构加载完成就会触发 ,所以触发的时机比window.onload会早很多
  • 也可以给script设置async或者defer 异步属性
  • async [获取异步,渲染同步]:遇到<“script” async>,分配新的HTTP去获取资源,GUI会继续渲染;当资源获取之后, 立即结束GUI渲染,让JS引擎线程去渲染解析JS;JS代码渲染完,再去执行GUI渲染!

  • defer [获取异步,渲染异步]:遇到<“script” defer>,分配HTTP去获取资源,此时GUI继续渲染;当DOM结构渲染完成, 而且设置defer的JS资源也都获取到了,按照之前编写的顺序,依次渲染解析JS代码!

  • async的特点是:只要JS代码获取到,就会立即执行,不管书写的先后顺序,适用于JS之间不存在依赖的时候"谁先请求回来先执行谁";

  • defer的特点是:必须等待GUI以及所有设置defer的JS代码都获取到,再按照之前书写的顺序,依次渲染和解析,即实现资源的异步获取,也可以保证JS代码之间的依赖关系!

5.加快DOM TREE的构建

  • 减少HTML的层级嵌套
  • 使用符合W3C规范的语义化标签
  • ... 6.加快CSSOM TREE的构建
  • 选择器层级嵌套不要过深[选择器的渲染顺序:从右到左]
  • 减少CSS表达式的使用
操作DOM比较消耗性能:大部分性能都消耗在了"DOM的重排(回流 Reflow)和重绘(Repaint)"页面第一次渲染,必然会出现一次layout(回流)和Painting(重绘);第一次渲染完成后;
  • 重排(回流):如果浏览器的视口大小发生改变 或者 页面中元素的位置、大小发生改变
  • 再或者就是DOM结构发生变化(删除、新增元素或挪动位置)...浏览器都需要重新计算节点在视口中(本层)的最新位置[也就是重新layout]
  • 完成后再分层和重新绘制! ----> 此操作非常消耗性能,所以我们应该尽可能减少重排(回流)的次数
  • 重绘:视口\元素的位置大小都不变,只是修改了一些基础样式(例如:背景颜色、文字颜色)
此时无需重新layout,只需重新Painting即可! -----> 重绘操作是必不可免的,只要想让页面第一次渲染完后还可以再改变
  • 必然会需要重绘;而且触发一次回流,必然会引发重绘
如果基于JS操作DOM ,那么前端性能优化"必做"的事情:减少DOM的重排和回流

@1 基于Vue/React/Angular等框架进行开发,我们是基于"数据驱动视图渲染",规避了直接操作DOM, 我们只需要操作数据,框架内部帮助我们操作DOM[他们做了很多减少DOM重排的操作]

@2 读写分离

  • 新版本浏览器中存在"渲染队列机制":当前上下文代码执行中,遇到修改元素样式操作,并不会立即去修改样式
而是把其挪至渲染队列中,代码继续向下执行...当代码执行完成后,此时会把渲染队列中,所有修改样式的操作 统一执行一次[只触发一次重排];
  • 但是在此过程中,遇到了获取元素样式的操作,则"刷新渲染队列"(也就是把目前队列中的操作执行一次),引发一次重排!!
把修改样式操作和获取样式的操作分离开
box.style.width = '100px'
box.style.height = '100px'
console.log(box.clientHeight)

@3 批量新增元素

  • 基于模板字符串实现批量新增 let str= ''; for(let i= 1;i<=10;i++){ str+=<div>${i}</div> } document.body.innerHTML += str
    会导致BODY原始结构中绑定的事件全部消失,所以此操作适用于:原始容器中没有任何内容 我们把新的内容插入进去

+文档碎片 let frg = document.createDocumentFragment(); //创建文档碎片:装DOM元素的容器 for(let i= 1;i<=10;i++){ let divBox = document.createElment('div'); divBox.innerHTML = i; frg = appendChild(divBox);//每创建一次元素,先放到文档碎片中

} document.body.appendChild(frg);//最后统一把文档碎片中所有内容放在body末尾,引发一次重排

@4 修改样式尽可能使用 "transform" [translate位移、scale缩放、roate旋转...]

  • 这个属性开启了硬件加速,不会引发重排(回流)

@5 如果真的引发重排,也把性能消耗降到最低

  • 尽量把修改样式的元素,单独放在一个层面中(脱离文档流),这样即使重排,也只是对这一层的处理
  • 基于JS实现动画,尽量牺牲平滑度换取速度