浏览器的主要功能是将用户选择的 web 资源呈现出来,它需要从服务器请求资源,并将其显示在浏览器窗口中,资源的格式通常是 HTML,也包括 PDF、image 及其他格式。
浏览器的线程
浏览器是多线程的,它们在内核制控下相互配合以保持同步。一个浏览器至少实现三个常驻线程:JavaScript 引擎线程,GUI 渲染线程,浏览器事件触发线程。
-
GUI 渲染线程:负责渲染浏览器界面 HTML 元素,当界面需要重绘(Repaint)或由于某种操作引发回流(reflow)时,该线程就会执行。在 Javascript 引擎运行脚本期间,GUI 渲染线程都是处于挂起状态的,也就是说被”冻结”了。
-
JavaScript 引擎线程:主要负责处理 Javascript 脚本程序
-
定时器触发线程:浏览器定时计数器并不是由 JavaScript 引擎计数的, JavaScript 引擎是单线程的, 如果处于阻塞线程状态就会影响记计时的准确, 因此浏览器通过单独线程来计时并触发定时。
-
事件触发线程:当一个事件被触发时该线程会把事件添加到待处理队列的队尾,等待 JS 引擎的处理。这些事件包括当前执行的代码块如定时任务、浏览器内核的其他线程如鼠标点击、AJAX 异步请求等。由于 JS 的单线程关系所有这些事件都得排队等待 JS 引擎处理。定时块任何和 ajax 请求等这些异步任务,事件触发线程只是在到达定时时间或者是 ajax 请求成功后,把回调函数放到事件队列当中。
-
异步 HTTP 请求线程:在 XMLHttpRequest 在连接后是通过浏览器新开一个线程请求, 将检测到状态变更时,如果设置有回调函数,异步线程就产生状态变更事件放到 JavaScript 引擎的处理队列中等待处理。在发起了一个异步请求时,http 请求线程则负责去请求服务器,有了响应以后,事件触发线程再把回到函数放到事件队列当中。
浏览器渲染流程
1.解析 html,构建 dom 树;解析 CSS 会产生 CSS 规则树
浏览器解析 HTML 文档的源码,然后构造出一个 DOM 树,DOM 树的构建过程是一个深度遍历的过程,当前节点的所有子节点都构建好以后才会去构建当前节点的下一个兄弟节点。
浏览器对 CSS 文件内容进行解析,一般来说,浏览器会先查找内联样式,然后是 CSS 文件中定义的样式,最后再是浏览器默认的样式,构建 CSS Rule Tree。
2.构建(construct) render 树
根据 DOM 树和 CSSOM 树来构造 Rendering Tree。注意:Rendering Tree 渲染树并不等同于 DOM 树,因为一些像 Header 或 display:none 的东西就没必要放在渲染树中了。
3.布局(layout) render 树
有了 Render Tree,浏览器已经能知道网页中有哪些节点、各个节点的 CSS 定义以及他们的从属关系,从而去计算出每个节点在屏幕中的位置。
4.绘制(painting) render 树
按照算出来的规则,通过显卡,把内容画到屏幕上。
5.回流(reflow)
当浏览器发现某个部分发生了点变化影响了布局,需要倒回去重新渲染,内行称这个回退的过程叫 reflow。reflow 会从 这个 root frame 开始递归往下,依次计算所有的结点几何尺寸和位置。
6.重绘(repaint)
改变某个元素的背景色、文字颜色、边框颜色等等不影响它周围或内部布局的属性时,屏幕的一部分要重画,但是元素的几何尺寸没有变。
浏览器对 CSS 和 JS 的解析规则
CSS:
- CSS 放在 head 中会阻塞页面的渲染(页面的渲染会等到 css 加载完成)
- CSS 阻塞 JS 的执行 (因为 GUI 线程和 JS 线程是互斥的,因为有可能 JS 会操作 CSS)
- CSS 不阻塞外部脚本的加载(不阻塞 JS 的加载,但阻塞 JS 的执行,因为浏览器都会有预先扫描器)
JS:
- 直接引入的 JS 会阻塞页面的渲染(GUI 线程和 JS 线程互斥)
- 异步加载的 JS (script 标签中添加 defer 属性)不阻塞页面的解析
- 异步加载的 JS (script 标签中添加 async 属性)下载过程不阻塞页面的解析,下载完成后立即执行,执行过程会阻塞页面的解析
- JS 不阻塞资源的加载
- JS 顺序执行,阻塞后续 JS 逻辑的执行
HTML 页面加载和解析流程
- 用户输入网址(假设是个 html 页面,并且是第一次访问),浏览器向服务器发出请求,服务器返回 html 文件;
- 浏览器开始载入 html 代码,发现< head >标签内有一个< link >标签引用外部 CSS 文件;
- 浏览器发出 CSS 文件的请求,服务器返回这个 CSS 文件;(同时 GUI 渲染线程继续执行,互不影响)
- 浏览器继续载入 html 中< body >部分的代码,并且 CSS 文件已经拿到手了,开始渲染页面;
- 浏览器在代码中发现一个< img >标签引用了一张图片,向服务器发出请求。此时浏览器不会等到图片下载完,而是继续渲染后面的代码;
- 服务器返回图片文件,由于图片占用了一定面积,影响了后面段落的排布,因此浏览器需要回过头来重新渲染这部分代码;
- 浏览器发现了一个包含一行 Javascript 代码的< script >标签,直接运行;
- Javascript 脚本操作某个元素,它命令浏览器隐藏掉代码中的某个标签 (style.display=”none”)。浏览器会重新渲染这部分代码;
- 遇到 </html >,流程结束;
- 当用户操作,页面产生交互,浏览器发现某个部分发生了点变化影响了布局,浏览器回流(reflow);改变某个元素的背景色、文字颜色、边框颜色等等不影响它周围或内部布局的属性时,浏览器重绘(repaint)。
HTML 页面加载优化
-
页面减肥。页面的肥瘦是影响加载速度最重要的因素删除不必要的空格、注释。将 inline 的 script 和 css 移到外部文件,可以使用 HTML Tidy 来给 HTML 减肥,还可以使用一些压缩工具来给 JavaScript 减肥;
-
减少文件数量。减少页面上引用的文件数量可以减少 HTTP 连接数。许多 JavaScript、CSS 文件可以合并最好合并;
-
减少域名查询。DNS 查询和解析域名也是消耗时间的,所以要减少对外部 JavaScript、CSS、图片等资源的引用,不同域名的使用越少越好;
-
缓存重用数据;
-
优化页面元素加载顺序。首先加载页面最初显示的内容和与之相关的 JavaScript 和 CSS,然后加载 DHTML 相关的东西,像什么不是最初显示相关的图片、flash、视频等很肥的资源就最后加载;
-
减少 inline JavaScript 的数量。浏览器 parser 会假设 inline JavaScript 会改变页面结构,所以使用 inline JavaScript 开销较大,不要使用 document.write()这种输出内容的方法,使用现代 W3C DOM 方法来为现代浏览器处理页面内容;
-
使用现代 CSS 和合法的标签。使用现代 CSS 来减少标签和图像,例如使用现代 CSS+文字完全可以替代一些只有文字的图片,使用合法的标签避免浏览器解析 HTML 时做“error correction”等操作,还可以被 HTML Tidy 来给 HTML 减肥;
-
不要使用嵌套 tables;
-
指定图像和 tables 的大小。如果浏览器可以立即决定图像或 tables 的大小,那么它就可以马上显示页面而不要重新做一些布局安排的工作,这不仅加快了页面的显示,也预防了页面完成加载后布局的一些不当的改变。