浏览器的页面渲染机制

本文深入探讨浏览器的工作原理,包括浏览器进程、内核、页面加载流程、Event Loop机制以及页面渲染的关键步骤,如DOM树和CSSOM树的构建、渲染树的生成、布局和绘制过程。了解这些知识有助于优化页面性能和理解JavaScript执行上下文。

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

一、前言

1、浏览器常用进程

  • Browser进程:浏览器的主进程(负责协调、主控),只有一个。作用有
    负责浏览器界面显示,与用户交互。如前进,后退等
    负责各个页面的管理,创建和销毁其他进程
    网络资源的管理,下载等
  • 第三方插件进程:每种类型的插件对应一个进程,仅当使用该插件时才创建
  • GPU进程:最多一个,用于3D绘制等
  • 浏览器渲染进程(浏览器内核),Renderer进程,内部是多线程的,也就是我们每个标签页所拥有的进程,互不影响,负责页面渲染,脚本执行,事件处理等

2、浏览器内核

在这里插入图片描述
这就是我们的核心渲染进程Renderer,内部有多个线程,参考上图

  • ① 图形用户界面GUI渲染线程

负责渲染浏览器界面,包括解析HTML、CSS、构建DOM树、Render树、布局与绘制等
当界面需要重绘(Repaint)或由于某种操作引发回流(reflow)时,该线程就会执行

  • ② JS引擎线程

JS内核,也称JS引擎,负责处理执行javascript脚本
等待任务队列的任务的到来,然后加以处理,浏览器无论什么时候都只有一个JS引擎在运行JS程序

  • ③ 事件触发线程

听起来像JS的执行,但是其实归属于浏览器,而不是JS引擎,用来控制时间循环(可以理解,JS引擎自己都忙不过来,需要浏览器另开线程协助)
当JS引擎执行代码块如setTimeout时(也可来自浏览器内核的其他线程,如鼠标点击、AJAX异步请求等),会将对应任务添加到事件线程中
当对应的事件符合触发条件被触发时,该线程会把事件添加到待处理队列的队尾,等待JS引擎的处理
注意:由于JS的单线程关系,所以这些待处理队列中的事件都得排队等待JS引擎处理(当JS引擎空闲时才会去执行)

  • ④ 定时触发器线程

setInterval与setTimeout所在线程
定时计时器并不是由JS引擎计时的,因为如果JS引擎是单线程的,如果JS引擎处于堵塞状态,那会影响到计时的准确
当计时完成被触发,事件会被添加到事件队列,等待JS引擎空闲了执行
注意:W3C的HTML标准中规定,setTimeout中低与4ms的时间间隔算为4ms

  • ⑤ 异步HTTP请求线程

在XMLHttpRequest在连接后新启动的一个线程
线程如果检测到请求的状态变更,如果设置有回调函数,该线程会把回调函数添加到事件队列,同理,等待JS引擎空闲了执行

二、页面加载流程

这里简单介绍一下基本网页加载请求流程,后面会出一章DNS解析的详细讲解

要点如下:

  • 浏览器根据 DNS 服务器得到域名的 IP 地址
  • 向这个 IP 的机器发送 HTTP 请求
  • 服务器收到、处理并返回 HTTP 请求
  • 浏览器得到返回内容

三、浏览器的Event Loop机制

Event Loop

  • 背景:JavaScript是一门单线程的脚本语言,由于这门语言的设计初衷是简单的可以运行在服务器的脚本语言,如果设计为多线程,会涉及线程间的资源共享等处理,会使变得复杂,由于是单线程,如果遇到大量请求I/O或网络请求等操作时,会阻塞,程序假死

  • 解决:所以Event Loop就是解决这个问题的方法,在程序中设置两个线程,一个负责js的程序本身的主线程,另一个则是负责主线程与其他线程(I/O、网络请求等)的通信,即“Event Loop”线程,当主线程遇到I/O、网络请求等操作时,会让“Event Loop”线程去通知对应的操作,而主线程继续向下执行,当I/O操作完成时,Event Loop线程再把结果返回主线程。主线程就调用事先设定的回调函数,完成整个任务

  • 优势:这就是为什么node.js平台可以用很少的资源,应付大流量访问的原因。(淘宝等电商平台在618或双十一等流量高峰也能正常处理nodejs做出了很大的贡献)

  • Event Loop”事件循环中,有两个队列,宏队列macrotask queue(setTimeout、setInterval、requestAnimationFrame、I/O、UI rendering)和微队列microtask queue(process.nextTick、Promise、MutationObserver)
    [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-s3zDd9l3-1622521586513)(https://segmentfault.com/img/remote/1460000016278118/view)]

  • 这张图将浏览器的Event Loop完整的描述了出来,我来讲执行一个JavaScript代码的具体流程:

  1. 执行全局Script同步代码,这些同步代码有一些是同步语句,有一些是异步语句(比如setTimeout等);
  2. 全局Script代码执行完毕后,调用栈Stack会清空;
  3. 从微队列microtask queue中取出位于队首的回调任务,放入调用栈Stack中执行,执行完后microtask queue长度减1;
  4. 继续取出位于队首的任务,放入调用栈Stack中执行,以此类推,直到直到把microtask queue中的所有任务都执行完毕。注意,如果在执行microtask的过程中,又产生了microtask,那么会加入到队列的末尾,也会在这个周期被调用执行
  5. microtask queue中的所有任务都执行完毕,此时microtask queue为空队列,调用栈Stack也为空;
  6. 取出宏队列macrotask queue中位于队首的任务,放入Stack中执行;
  7. 执行完毕后,调用栈Stack为空;
  8. 重复第3-7个步骤;
  9. 重复第3-7个步骤;

可以看到,这就是浏览器的事件循环Event Loop

这里归纳3个重点:

  1. 宏队列macrotask一次只从队列中取一个任务执行,执行完后就去执行微任务队列中的任务;
  2. 微任务队列中所有的任务都会被依次取出来执行,知道microtask queue为空;
  3. 图中没有画UI rendering的节点,因为这个是由浏览器自行判断决定的,但是只要执行UI rendering,它的节点是在执行完所有的microtask之后,下一个macrotask之前,紧跟着执行UI render。

四、浏览器页面渲染流程(重点)

0、概述

在这里插入图片描述

  • 浏览器会解析三个东西
    • 一是HTML/SVG/XHTML,HTML字符串描述了一个页面的结构,浏览器会把HTML结构字符串解析转换DOM树形结构。
    • 二是CSS,解析CSS会产生CSS规则树,它和DOM结构比较像
    • 三是Javascript脚本,等到Javascript 脚本文件加载后, 通过 DOM API 和 CSSOM API 来操作 DOM Tree 和 CSS Rule Tree。
  • 解析完成后,浏览器引擎会通过生成的DOM Tree 和 CSS Rule Tree 来构造 Rendering Tree。
    • Rendering Tree 渲染树并不等同于DOM树,渲染树只会包括需要显示的节点和这些节点的样式信息。
    • CSS 的 Rule Tree主要是为了完成匹配并把CSS Rule附加上Rendering Tree上的每个Element(也就是每个Frame)。
    • 然后,计算每个Frame 的位置,这又叫layoutreflow过程。
  • 最后通过调用操作系统Native GUI的API绘制。

1、生成DOM树

在这里插入图片描述

在这里插入图片描述

  • 浏览从磁盘或者网络读取HTML原始字节流,,根据文件指定的编码规则(utf-8)转为字符串格式;
  • 浏览器将转换的字符串再转换为token令牌标识,用来表示节点与节点间的关系
    • Token中会标识出当前Token是“开始标签”或是“结束标签”亦或是“文本”等信息
      在这里插入图片描述
  • 根据token生成节点对象,并开始构建DOM树
    事实上,构建DOM的过程中,不是等所有Token都转换完成后再去生成节点对象,而是一边生成Token一边消耗Token来生成节点对象。换句话说,每个Token被生成后,会立刻消耗这个Token创建出节点对象。注意:带有结束标签标识的Token不会创建节点对象。

2、生成CSSOM树

DOM树只是页面的骨架,浏览器还不知道如何展示,因此还需要构建CSSOM
在这里插入图片描述

  • 在这一过程中,浏览器会确定下每一个节点的样式到底是什么,并且这一过程其实是很消耗资源的。因为样式你可以自行设置给某个节点,也可以通过继承获得。在这一过程中,浏览器得递归 CSSOM 树,然后确定具体的元素到底是什么样式。
    注意:CSS匹配HTML元素是一个相当复杂和有性能问题的事情。所以,DOM树要小,CSS尽量用id和class,千万不要过渡层叠下去。

3、构建渲染树

在这里插入图片描述

  • 此过程不是简单的两棵树合并,生成的Render Tree 只会包含需要显示的节点和节点样式信息,如果某个节点是 display: none 的,那么就不会在渲染树中显示。
  • 渲染过程中,如果遇到<script>就停止渲染,执行 JS代码。因为浏览器有GUI渲染线程JS引擎线程,为了防止渲染出现不可预期的结果,这两个线程是互斥的关系。
    JavaScript的加载、解析与执行会阻塞DOM的构建,也就是说,在构建DOM时,HTML解析器若遇到了JavaScript,那么它会暂停构建DOM,将控制权移交给JavaScript引擎,等JavaScript引擎运行完毕,浏览器再从中断的地方恢复DOM构建。
    也就是说,如果你想首屏渲染的越快,就越不应该在首屏就加载 JS 文件,这也是都建议将 script 标签放在 body 标签底部的原因。当然在当下,并不是说 script 标签必须放在底部,因为你可以给 script 标签添加 defer 或者 async 属性
  • JS文件不只是阻塞DOM的构建,它会导致CSSOM也阻塞DOM的构建。
    这是因为JavaScript不只是可以改DOM,它还可以更改样式,也就是它可以更改CSSOM。因为不完整的CSSOM是无法使用的,如果JavaScript想访问CSSOM并更改它,那么在执行JavaScript时,必须要能拿到完整的CSSOM。所以就导致了一个现象,如果浏览器尚未完成CSSOM的下载和构建,而我们却想在此时运行脚本,那么浏览器将延迟脚本执行和DOM构建,直至其完成CSSOM的下载和构建。也就是说,在这种情况下,浏览器会先下载和构建CSSOM,然后再执行JavaScript,最后在继续构建DOM。
    在这里插入图片描述

4、布局(Layout)与绘制(painting)

  • 当浏览器生成渲染树以后,就会根据渲染树来进行布局(也可以叫做回流)。这一阶段浏览器要做的事情是要弄清楚各个节点在页面中的确切位置和大小。通常这一行为也被称为“自动重排”。
    布局流程的输出是一个“盒模型”,它会精确地捕获每个元素在视口内的确切位置和尺寸,所有相对测量值都将转换为屏幕上的绝对像素。
    布局完成后,浏览器会立即发出“Paint Setup”和“Paint”事件,将渲染树转换成屏幕上的像素。
  • 这里就引入了两个概念:reflow(回流)repaint(重绘)
    reflow(回流/重排):当浏览器发现某个部分发生了点变化影响了布局,需要倒回去重新渲染,内行称这个回退的过程叫 reflow。reflow 会从 这个 root frame 开始递归往下,依次计算所有的结点几何尺寸和位置。reflow 几乎是无法避免的。现在界面上流行的一些效果,比如树状目录的折叠、展开(实质上是元素的显 示与隐藏)等,都将引起浏览器的 reflow。鼠标滑过、点击……只要这些行为引起了页面上某些元素的占位面积、定位方式、边距等属性的变化,都会引起它内部、周围甚至整个页面的重新渲 染。通常我们都无法预估浏览器到底会 reflow 哪一部分的代码,它们都彼此相互影响着。
    repaint(重绘):改变某个元素的背景色、文字颜色、边框颜色等等不影响它周围或内部布局的属性时,屏幕的一部分要重画,但是元素的几何尺寸没有变。
    在这里插入图片描述
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值