浏览器底层渲染机制

本文详细介绍了浏览器的渲染机制,包括DOM树构建、CSSOM树生成、渲染树合成及布局、绘制等步骤,并探讨了CRP关键渲染路径的性能优化策略。文章强调了CSS合并、异步加载、避免阻塞渲染、减少DOM操作等方面的重要性,同时提到了JS同步与异步编程、进程与线程的概念,以及如何通过延迟加载、读写分离等手段减少重排和重绘以提升前端性能。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

浏览器底层渲染机制

浏览器底层渲染机制:我们从服务器获取代码后,浏览器是如何把代码渲染为页面及相关效果的

CRP(关键渲染路径)性能优化法则:了解浏览器底层处理的具体步骤,针对每一个步骤进行优化

JS中的同步和异步编程

       +同步编程:上一件事情没有处理完,下一件事情无法处理

       +异步编程:上一件事情即便没有处理完,也无需等待,可以继续处理后续事情

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

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

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

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

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

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

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

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

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

      +...

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

浏览器底层渲染机制:

步骤1:生成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事件】

步骤2:生成CSSOM树【CSSOM TREE】

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

步骤3:合成渲染树【RENDER YREE】

把DOM树和CSSOM树合并在一起,生成渲染树

步骤4:layout布局 & 回流/重排

按照当前可视窗口大小,计算每一个节点在视图中的位置和大小

步骤5:分层

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

步骤6:painting绘制/重绘

按照计算好的规则,一层层的进行绘制

CRP 优化技巧:

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

        +CSS合并为一个

        +JS合并为一个

        +雪碧图

        +...

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

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顺序,依次渲染解析JS代码

---------------------------------------------------------------------------------------------------------------------------------

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

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

5.加快DOME 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 读写分离

         +新版本浏览器中存在渲染队列的机制:当前上下文代码执行过程中遇到修改样式的操作,并不会立即修改样式,而是把其挪到渲染队列中,代码继续向下执行...当代码执行完成后,会把渲染队列中所有修改样式的操作统一执行一次【只触发一次重排】;

         +但是在此过程中遇到获取元素样式的操作,则刷新渲染队列(也就是把目前队列中的操作执行一次),引发一次重排

把获取样式的操作和修改样式的操作分离开

样式批量设置

@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.innerText=i;

frg.appendChild(divBox); // 每一次创建元素,先放置在文档碎片中

}

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

@4 修改元素的样式尽可能使用“transform”【translate位移 scale缩放 rotate旋转】

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

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

+尽量把修改样式的元素,单独放在一个层面中(脱离文档流),这样即便重排,也只是对这一层的处理

+基于JS实现动画,尽量牺牲平滑度换取速度

...


评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值