zl程序教程

您现在的位置是:首页 >  前端

当前栏目

【浏览器渲染原理】

2023-06-13 09:17:24 时间

1 浏览器框架结构

  1. 用户界面:就是浏览器自带的呈现给用户的一些标签界面,包含一些标签收藏夹等用于用户提升浏览器体验的功能UI
  2. 浏览器引擎:用于在用户界面和渲染引擎之间,传递数据。浏览器引擎下还有个数据持久层子模块,帮助浏览器存储各种数据,cookie、storage…
  3. 渲染引擎:负责渲染用户通过HTTP请求获取的内容。渲染引擎下边还有很多子模块:
    • 网络模块:负责网络请求
    • JS解析器:解析和执行js代码

渲染引擎也称内核是浏览器的核心。

2. 进程与线程关系

浏览器是一个运行在操作系统上的应用程序,每个应用程序必须至少启动一个进程来执行其功能,每个程序往往又会执行很多任务,那么进程就会创建很多线程来执行这些小的任务。

当我们在启动某个应用程序的时,就会创建一个进程来执行任务代码,同时会为该进程分配内存空间,该应用程序的状态都保存在该内存空间里。当应用关闭时,该内存空间就会被回收。

程序可以启动更多的进程来执行任务,由于每个进程分配的内存空间是独立的,如果两个进程之间需要传递某些数据,则需要进程通信管道IPC来传递。很多应用程序都是多进程的结构,这样是为了避免某一个进程卡死,影响整个应用程序,因为进程之间相互独立,一个进程卡死不会影响用户使用另一个进程。

进程可以将任务分成更多细小的任务,然后通过创建多个线程并行执行不同的任务,同一个进程之间的线程是可以直接通信共享数据的.

3. 早期浏览器结构

目前的浏览器都是多进程的结构,但是早期的浏览器都是单进程的结构。 但这一个进程也有多个线程:

  • 页面线程:负责页面渲染和展示
  • JS线程:执行js代码
  • 还有其他各种线程

但是单进程结构有几个问题: ① 不稳定:其中一个线程卡死,会导致整个进程出问题。比如你打开多个标签页,其中一个标签页卡死,可能会导致整个浏览器无法运行。 ② 不安全:浏览器之间是可以共享数据的,那么js线程就可以访问浏览器里的所有数据 ③ 不流畅:一个进程需要负责太多事情,导致运行效率不佳

4. 现代浏览器结构

为了解决早期浏览器的问题,现代浏览器采用了多进程架构,根据进程功能不同来分解浏览器:

  • 浏览器进程:控制除标签页面以外的用户界面,包括地址栏、书签、前进、后退等,以及负责与浏览器的其他进程协调工作
  • 网络进程:发起接收网络请求
  • GPU进程:负责整个了浏览器界面的渲染
  • 插件进程:负责控制网站使用的所有插件,这里的插件不是指安装的拓展
  • 渲染器进程:控制显示tab标签内的所有内容,浏览器默认情况下可能会为每一个标签页都创建一个进程,因为这和用户启动浏览器时选择的进程模型有关,一共有4种进程模型:
    • 默认进程:为每一个标签页创建一个进程
    • 同一站点使用同一进程,
    • 一个tab里的所有站点使用同一个进程
    • 浏览器引擎和渲染引擎共用一个进程

5 浏览器渲染原理

  1. 当我们在地址栏输入地址时,浏览器的UI线程会捕捉我们的输入内容, a. 若访问的是网址,则UI线程会启动一个网络线程来处理请求DNS和域名解析,接着开始连接服务器获取数据; b. 若输入的不是网址,而是关键词,会启动默认配置的搜索引擎来查询。
  2. 网络线程从服务器获取到数据后的操作: a. 当网络线程获取到数据后,会通过SafeBrowsing先检查站点是否恶意站点,如果是,则会展示一个警告页面,告诉你这个站点有安全问题,浏览器会阻止你的访问,当然你也可以强行访问; b. 当访问的数据准备完毕并且安全校验通过时,网络进程会通知UI进程; c. UI进程会创建一个渲染进程来渲染页面,浏览器进程通过IPC管道将数据传递给渲染器进程,正式进入渲染流程; d. 渲染器进程接收到的数据,也就是HTML,渲染器进程的核心任务就是把HTML、CSS、JS、静态资源等,资源渲染成用户可以交互的Web页面:
    1. 构造DOM树:渲染器进程的主进程将html进行解析,通过词法分析,构造DOM数据结构:创建document对象,然后以对document对象为根节点的DOM树不断修改,向其中添加各种元素;
    2. 下载静态资源:css、图片等静态资源通常都是通过网络下载或从缓存中直接加载,不会阻塞html的解析,不会影响DOM结构的生产;
    3. js阻塞:但是在解析过程中遇到script标签时,就会停止html解析,转而去加载解析并执行js(因为浏览器并不知道当前的js操作会不会改变当前的html结构,如果js代码里用例document.write()来修改html,那么之前的解析就都没有意义了,所以我们一定要把script标签放在合适的位置,或者使用async或deffer属性来异步加载执行js);
    4. 计算UI:在html解析完成后,就会得到一个DOM树,但此时还不知道DOM树的每一个节点的样式,主线程需要解析css并确定每个节点的就算样式,即使你没有提供自定义的css样式,浏览器也有默认的样式表;
    5. layout布局:在知道节点的样式后,我们需要计算节点在页面的坐标位置以及占用空间。主线程通过遍历DOM和计算好的样式来生产Layout Tree,Layout Tree上的每个节点都记录了节点坐标和尺寸大小,但是DOM Tree中设置了display: none属性的节点并不会出现在Layout Tree上,而添加了 ::before的伪元素,其内容会出现在Layout Tree上不会出现在DOM Tree上。因为DOM是通过html解析获得的并不关心样式,而Layout tree是通过DOM和计算好的样式来生产的,Layout Tree就是最后展示给用户的UI界面;
    6. 绘制:比如z-index属性影响节点绘制的层级关系,若按照dom的层级结构来渲染页面,则会导致错误的渲染,所以为了保证节点绘制的层级,主线程遍历Layout Tree创建了一个绘制记录表(Paint Record),该表记录了绘制的顺序;
    7. 栅格化:将Layout Tree转化成像素点显示在屏幕上(过程复杂,不研究)。就是用户看到的效果了。

6. 渲染问题

  1. 当我们改变一个元素的位置尺寸时,会重新进行样式计算、布局、绘制、栅格化等流程(重排)。
  2. 当我们改变某个元素的颜色属性时,不会触发重新布局,但是会触发样式计算和绘制(重绘)。

解决方案: js、重排、重构都是在主线程上执行的,如果浏览器不断的重排、重绘,浏览器会在每一帧(浏览器滚动是以帧为单位的动画)上都进行计算布局、绘制的操作。若在上一帧的js还没有执行完,就滚动到了下一帧,会导致下一帧动画没有及时渲染,就会造成页面卡顿。

  1. requestAnimationFrame() 会在每一帧执行完之前,结束js的执行,这样下一帧就能及时绘制。react最新渲染引擎React Fiber就使用了这个方法做了很多优化。
  2. transform。css中transform属性实现动画不会导致页面重绘、重排问题。它直接在合成器线程和栅格化线程中运行,这就意为着它无需和js线程抢夺主线程。更重要的是,通过transform实现的动画不需要经过绘制、布局、样式计算等操作,节省了很多时间。

SafeBrowsing(谷歌内部的站点安全系统):通过检查该站点的数据来检测该站点是否安全,比如通过查看站点IP是否在谷歌的黑名单之内。