超细致带你过一遍 从输入url到浏览器显示页面到底发生了什么

252 阅读10分钟

基础知识

在讲解之前我们简单过一下一些基础的概念

浏览器的多进程架构

进程:是程序的一次执行过程,是一个动态概念,是程序在执行过程中分配和管理资源的基本单位

线程:是CPU调度和分派的基本单位,它可与同属一个进程的其他的线程共享进程所拥有的全部资源。

一个好的程序常常被划分为几个相互独立又彼此配合的模块,浏览器也是如此,以 Chrome 为例,它由多个进程组成,每个进程都有自己核心的职责,它们相互配合完成浏览器的整体功能,每个进程中又包含多个线程,一个进程内的多个线程也会协同工作,配合完成所在进程的职责。

在Chrome浏览器当中包含以下进程

  • 浏览器进程 (Browser Process):负责浏览器的TAB的前进、后退、地址栏、书签栏的工作和处理浏览器的一些不可见的底层操作,比如网络请求和文件访问。
  • 渲染进程 (Renderer Process):负责一个Tab内的显示相关的工作,也称渲染引擎。
  • 插件进程 (Plugin Process):负责控制网页使用到的插件
  • GPU进程 (GPU Process):负责处理整个应用程序的GPU任务

优点

  1. 某一渲染进程出问题不会影响其他进程
  2. 更为安全,在系统层面上限定了不同进程的权限

缺点

由于不同进程间的内存不共享,不同进程的内存常常需要包含相同的内容。

导航过程

导航过程涉及到的是浏览器进程,执行任务主要包含以下线程

  • UI thread : 控制浏览器上的按钮及输入框;
  • network thread: 处理网络请求,从网上获取数据;
  • storage thread: 控制文件等的访问

开始导航

  1. 首先UI线程判断输入的内容是 url 还是 query

  2. 当确定之后,UI线程通知 network thread 发送请求,此时tab上面的 spinner 就开始旋转,代表导航开始。

  3. network thread 收到信息后先进行 dns 查询,这里便涉及到 dns 缓存,获取到对应的ip地址之后 则会建立 连接,如果收到的内容是重定向,则 network thread 会通知 UI线程发起新的请求,重复上面步骤,如果域名或者请求内容匹配到已知的恶意站点,network thread 会展示一个警告页

    当有 Service Worker 被注册时,其作用域会被保存,当有导航时,network thread 会在注册过的 Service Worker 的作用域中检查相关域名,如果存在对应的 Service worker,UI thread 会找到一个 renderer process 来处理相关代码,Service Worker 可能会从 cache 中加载数据,从而终止对网络的请 求,也可能从网上请求新的数据。

读取响应

    当请求响应返回的时候,network thread 会依据 Content-Type 及 MIME Type sniffing 判断响应内容的格式

     如果媒体类型是一个HTML文件,则将响应数据交给渲染进程(renderer process)来进行下一步的工作,如果是 zip 文件或者其它文件,会把相关数据传输给下载管理器。

查找渲染进程

    在所有的工作完成之后,network thread 确信浏览器可以导航到请求网页,network thread 会通知 UI thread 数据已经准备好,UI thread 会查找到一个 renderer process 进行网页的渲染。

    因为网络请求往往需要时间,所以浏览器对这一过程做了优化,在开始导航的时候这一步就开始了,值得一提的是,Chrome提供了四种进程模式(Process Models),不同的进程模式会对渲染进程做不同的处理。

Process-per-site-instance (default) - 同一个 site-instance 使用一个进程
Process-per-site - 同一个 site 使用一个进程
Process-per-tab - 每个 tab 使用一个进程
Single process - 所有 tab 共用一个进程

确认导航

    到了这一步,数据和渲染进程都准备好了,Browser Process 会向 Renderer Process 发送IPC消息来确认导航,此时,浏览器进程将准备好的数据发送给渲染进程,渲染进程接收到数据之后,又发送IPC消息给浏览器进程,告诉浏览器进程导航已经提交了,页面开始加载。

    这个时候导航栏会更新,安全指示符更新(地址前面的小锁),访问历史列表(history tab)更新,即可以通过前进后退来切换该页面。

渲染过程

     渲染进程几乎负责 Tab 内的所有事情,渲染进程最重要的任务就是将 HTML CSS Javascript 代码转换成可以和用户交互的界面。主要包含以下线程:

1. GUI 渲染线程

    GUI渲染线程负责渲染浏览器界面HTML元素,当界面需要重绘(Repaint)或由于某种操作引发回流(reflow)时,该线程就会执行。在Javascript引擎运行脚本期间,GUI渲染线程都是处于挂起状态的,也就是说被冻结了.

2. JavaScript引擎线程

     JS为处理页面中用户的交互,以及操作DOM树、CSS样式树来给用户呈现一份动态而丰富的交互体验和服务器逻辑的交互处理。GUI渲染线程与JS引擎线程互斥的

3. 定时触发器线程

    浏览器定时计数器并不是由JS引擎计数的, 因为JS引擎是单线程的, 如果处于阻塞线程状态就会影响记计时的准确, 因此通过单独线程来计时并触发定时是更为合理的方案。

4. 事件触发线程

    当一个事件被触发时该线程会把事件添加到待处理队列的队尾,等待JS引擎的处理。这些事件可以是当前执行的代码块如定时任务、也可来自浏览器内核的其他线程如鼠标点击、AJAX异步请求等,但由于JS的单线程关系所有这些事件都得排队等待JS引擎处理。

5. 异步http请求线程

     在XMLHttpRequest在连接后是通过浏览器新开一个线程请求,将检测到状态变更时,如果设置有回调函数,异步线程就产生状态变更事件放到JS引擎的处理队列中等待处理。

渲染流程

    用户请求的HTML文本(text/html)通过浏览器的网络层到达渲染引擎后,渲染工作开始,有四个主要步骤:

  1. 解析HTML生成DOM树 - 渲染引擎首先解析HTML文档,生成DOM树

    这个过程是一个深度遍历的过程,当前节点的所有子节点都构建好后才会去构建当前节点的下一个兄弟节点。 DOM树的根节点就是document对象。

2**. 构建Render树** - 接下来不管是内联式,外联式还是嵌入式引入的CSS样式会被解析生成CSSOM树,根据DOM树与CSSOM树生成另外一棵用于渲染的树-渲染树(Render tree),

生成DOM树的同时会生成样式结构体CSSOM(CSS Object Model)Tree,再根据CSSOM和DOM树构造渲染树
Render Tree,渲染树包含带有颜色,尺寸等显示属性的矩形,这些矩形的顺序与显示顺序基本一致。

 3****. 布局Render树** - 然后对渲染树的每个节点进行布局处理,确定其在屏幕上的显示位置
** 4**. 绘制Render树** - 最后遍历渲染树并用UI后端层将每一个节点绘制出来

渲染细节

1、创建Document对象,开始解析web页面。解析HTML元素和他们的文本内容后添加Element对象和Text节点
到文档中。这个阶段document.readyState = 'loading'2、遇到link外部css,通知浏览器进程中的网络线程进行加载,并继续解析文档。(css构建的CSSOM是阻塞渲染
的资源,所以应精简css或者提供媒体查询应用)。

3、遇到script标签,如果没有 defer 或 async,浏览器会立即加载并执行指定的脚本,这个过程会阻塞html的
解析,如果包含 defer 或 async,则异步加载不堵塞,标记了 async 的js加载完之后会立即执行,defer 则
会在文档解析完之后执行。

4、遇到img等,先正常解析dom结构,然后浏览器异步加载src

5、当文档解析完成后,document.readyState = 'interactive'。defer脚本按顺序执行。

6document对象触发DOMContentLoaded事件

7、当所有async的脚本加载完成并执行后、img等加载完成后,document.readyState = 'complete'window对象触发load事件。

渲染过程衍生出的优化点

css方面的优化

    渲染树的构成必须要 DOM 树和 CSSOM 树的,所以尽快的构建 CSSOM 树是一个重要的优化手段。

  1. 文件位置,如果 css 文件放在尾部,那么整个过程就是一个串行的过程先解析了 dom,再去解析 css。所以 css 我们一般都是放在头部,这样 DOM 树和 CSSOM 树的构建是同步进行的。
  2. 降低样式选择器的复杂度,尽量保持class的简短,或者使用Web Components框架
  3. **使用flexbox替代老的布局模型,**同样的元素数量布局,Flexbox布局耗时要比传统的浮动更加迅速。

js方面的优化

   js 的运行会阻止 DOM 树的渲染,操作dom也会耗费很多资源。

  1. **文件位置,**和css不同的是一旦我们的 js 放在了头部,而且也没有异步加载这些操作的话,js 就会阻塞dom的渲染,那么页面就会一直出现白屏界面,所以一般我们会把 js 文件放在尾部。还有一个原因,就是为了有时候 js 代码会有操作 dom 节点的情况,保证dom已经渲染,但是要防止文件过大不然加载大量耗时造成页面不可交互。
  2. 减少重排重绘,重排和重绘会耗费大量资源,js 尽量减少对样式和dom的操作,如果必须要用 js 操作样式,能合并尽量合并不要分多次操作。加载图片的时候,提前写好宽高。
  3. 动画实现使用requestAnimationFrame
  4. 长耗时的计算放到web woker当中

缓存优化

合理的运用浏览器缓存机制,将一些可能频繁使用到的资源进行缓存

请求优化

  1. **减少请求数量,**将小图片打包成base64。利用雪碧图融合多个小图片;css文件合并。
  2. **减少请求时间,**将js,css,html等文件能压缩的尽量压缩,减少文件大小,加快下载速度;利用webpack打包根据路由进行懒加载,不要初始就加载全部,那样文件会很大;建立合理的CDN更快速的获取文件;

如果这篇文章对你有所帮助,还望点个:❤