浏览器的底层机制、CRP优化技巧、如何解决DOM消耗性能、同步异步、进程和线程

本文详细解析了浏览器的渲染过程,包括DOM树、CSSOM树的生成,渲染树合成,布局、分层与绘制。并探讨了CRP(Critical Rendering Path)优化策略,如合并CSS和JS,避免@import,懒加载图片,以及优化DOM操作以减少回流和重绘。此外,还介绍了JS的同步与异步编程和浏览器的多线程概念。
摘要由CSDN通过智能技术生成


浏览器的底层机制:

步骤一:生成DOM树「DOM TREE」

当我们从服务器获取HTML代码后,浏览器会分配“GUI渲染线程”自上而下解析代码

  • 遇到:分配一个新的“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优化技巧:

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

    • CSS合并为一个
    • JS合并为一个
    • 雪碧图
    • 。。。
  • 尽可能不要使用@import导入式,因为他会阻碍GUI的渲染;如果CSS样式代码不是很多,使用style内嵌式更好(尤其是移动端开发);但是如果代码很多,还是使用link外链式(但是最好把link放在<head>中);

  • 图片懒加载一定要处理:不要再第一次渲染页面的时候,让图片资源的请求,去占用有限的HTTP线程以及宽带资源,优先本着CSS/JS资源获取;当页面渲染完成后,再去根据图片是否出现在视口中,来加载真实的图片;

  • 关于<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顺序,依此渲染JS代码!
    async的特点是:只要js代码获取到,就会立即执行,不管书写的先后顺序,适用于JS之间不存在依赖的时候“谁先请求回来先执行谁”;
    defer的特点是:必须等待GUI以及所有设置defer的JS代码都获取到,在按照之前书写的顺序,依此渲染和解析,即实现了资源的异步获取,也可以保证JS代码之间的依赖关系!
    
  • 加快DOM TREE的构建

    • 减少HTML的层级嵌套
    • 使用符合W3C规范的语义化标签
    • 。。。
  • 加快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.clientWith);
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.createElement('div');
    divBox.innerHTML=i;
    frg.appendChild(divBox); // 每一次创建元素,先放置在文档碎片中
}
document.body.appendChild(feg); //最后统一把文档碎片中所有内容放在body末尾,引发一次重排

4、修改元素的样式尽可能使用“transform”「translate位移、scale缩放、rotate旋转。。。」
  • 这个属性开启了硬件加速,不会引发重排(回流)
5、如果真的引发重排,也把性能消耗降到最低
  • 尽量把修改样式的元素,单独放在一个层面中(脱离文档流),这样即便重排,也只是对这一层的处理
  • 基于JS实现动画,尽量牺牲平滑度换取速度

。。。

JS中的同步和异步编程:

  • 同步编程:上一步事情没有处理完,下一件事情无法处理
  • 异步编程: 上一步事情没有处理完,也无需等待,可以继续处理后面的事情

进程和线程:

一个进程中可能包含多个线程

  • 进程:一般代表一个程序(或者浏览器打开一个页面就开辟一个进程)
  • 线程:程序中具体干事的人

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

  • GUI渲染线程:用来渲染和解析HTML/CSS的,以及绘制页面
  • JS引擎线程:用来渲染和解析JS的
  • HTTP网络线程:用来从服务器获取相关资源文件的「同源下,最多同时开辟5~7个HTTp网络线程」
  • 定时器监听线程:监听定时器是否到时间的(计时的)
  • 事件监听线程:监听事件是否触发的
  • 。。。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值