浏览器底层运行机制

131 阅读7分钟

浏览器底层渲染机制:

当我们从服务器获取代码后,浏览器如何把代码渲染位页面及相关效果的

  • CRP(关键渲染路径)性能优化法则:

    了解浏览器底层处理的具体步骤,针对每一个步骤进行优化

  • 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在继续向下渲染

  • 步骤二:生成CSSOM树(CSSOM TREE)

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

    • 把DOM树和CSSOM树合并在一起,生成渲染树
  • 步骤四:Layout布局 & 回流/重排

    • 按照当前可视窗口大小,计算每个节点在可视窗口中的位置和大小
  • 步骤五:分层

    • 计算每一层(每个文档流)中各个节点的具体绘画制规则
  • 步骤六:Painting绘制 & 重绘

    • 按照计算好的规制一层层绘制

CRP优化技巧:

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

 - CSS合并位一个
 - JS合并位一个
 - 雪碧图

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

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

  • 关于SCRIPT的优化

    • 最好把script放在body的末尾,等待DOM结构加载完成,再去获取和解析JS,此时就可以获取页面中的DOM 元素了
    • 利用事件监听去处理
    • window.addEventListener(DOMContenLoaded.function(){})
      • DOM结构加载完成就会触发,所以触发的时候会比window.onload早很多
    • window.onload=function(){}
      • 等待页面中的所有资源加载完成
    • 也可以给script设置async或者defer异步属性
      • async:异步获取,同步渲染
        • 遇到script async ,分配新的HTTP线程去获取资源,GUI会继续渲染,获取之后立刻结束GUI渲染,让JS引擎去渲染解析JS;JS代码渲染完,再去执行GUI渲染
        • 特点是只要JS代码获取到后就会立刻执行,不管书写的先后顺序,适用于JS之间,不存在依赖
    • defer:获取异步,渲染异步
      • 遇到script defer,分配新的HTTP线程去获取资源,GUI继续渲染,DOM结构树渲染完成后,defer的JS资源获取之后,按照之前编写的JS顺序 依次渲染解析JS代码
      • 特点是必须等待GUI以及所有设置的JS代码都获取到了,在按照之前的顺序,依次渲染和解析,即实现了资源的异步获取,也保证了JS代码之间的依赖关系
  • 加快DOM TREE的构建

    • 减少HTML的层级嵌套
    • 使用复合WC3的规范语义化标签

加快CSSOM TREE大的构建

  • 选择器层级嵌套不要过深(前缀不要过长)(选择器的渲染顺序从左到右)
  • 减少CSS表达式的使用

操作DOM比较消耗性能

  • 大部分的性能都消耗在DOM的重拍(回流RReflow)和重绘(Repaint)
  • 页面第一次渲染,必然会出现依次Layout(回流)和painting(重绘),第一次渲染完成后:
    • 重排(回流)
      • 如果浏览器的视口大小发生改变,或者页面中的元素的位置大小发生改变
      • DOM结构发生变化(删除,新增或者挪动位置)
      • 浏览器都需要重新计算节点在视口中的最新位置也就是重新Layout,完成之后在重新分层和重新绘制
      • 此操作非常消耗性能,所以我们应该尽量减少重排的次数
    • 重绘:视口/元素的位置大小都不变,只是修改了一些基础样式(例如:背景颜色,文字颜色,透明度)
      • 此时无需重排,只需要重绘
      • 重绘操作时必不可少的,只要想让页面第一次渲染完还可以在改变,必然需要重绘;而且触发一次回流也必然经历重绘
  • 如果基于JS操作DOM,那么前端性能优化必做的一件事情:减少DOM的重排
    • 基于vue/react/angular等框架进行开发,我们是基于数据驱动视图渲染,规避了直接操纵DOM,我们只需要操作数据,框架内部帮助我们操作DOM(他们做了很多减少操纵DOM的操作)
    • 读写分离(获取和设置不写在一起)
      • 新版本浏览器中存在“渲染队列机制”:当前上下文代码执行过程中,遇到修改元素样式的操作,并不会立刻去修改样式,而是把其挪到渲染队列当中,代码继续向下执行;当代码执行完成后,此时会把渲染队列当中所有有关于修改样式的操作,同意执行一次,触发一次重排
      • 但是在此过程当中遇到获取元素样式的操作(四个),则书信渲染队列;也就是把目前队列当中的操作执行一次,引发一次重排
    • 减少重排
      • 我们应该把获取样式的操作和修改样式的操作分开,获取样式放在后面

      • 样式批量设置

        • box.style.cssText=width:100px;height:100px
        • box.className=active--->.active{width:100px;height:222px}
      • 批量新增元素

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