浏览器基本原理
浏览器输入URL到页面展示过程
查看
-
输入URL并解析
- 检查输入内容是否为关键字(自动补全或跳转搜索引擎或进入浏览器设置页面)
- 对URL进行合法性检查和格式化
-
域名解析: 域名解析器(DNS: domain name system)将域名解析为IP地址, 浏览器通过IP地址访问服务器。
-
建立TCP连接(三次握手), 确保客户端与服务端双向通信能力。
-
发起HTTP/HTTPS请求。
-
服务器处理请求并返回响应。
-
浏览器解析与渲染。
-
加载动态资源与交互。
-
连接终止: TCP四次挥手。
浏览器渲染页面的的流程
查看
从服务器获取到资源后
-
创建DOM树: 解析HTML标签, 创建DOM树。
-
创建CSSOM树: 解析CSS样式, 创建CSSOM树。
- 当解析过程中发现存在link标签引用CSS文件时, 浏览器会向服务器请求CSS文件。
- 对外部CSS资源的下载解析会交给其他进程处理, 并不会阻塞DOM树的解析。
-
生成渲染树: 将DOM树和CSSOM树合并, 生成渲染树(rander tree)。
- 在此过程中, 渲染树的并不会存在每个节点的尺寸和位置信息。
- 对于渲染树, 可能存在等待CSSOM树解析完毕倒是渲染树生成的不及时的情况。针对这一点, 浏览器可能会对其进行一些优化操作。
-
布局(layout): 计算渲染树中每个节点的几何信息, 如宽高、位置等。
-
绘制(paint): 生成绘制指令列表,描述元素的绘制顺序和方式。
- 绘制是逻辑操作,不直接操作像素。
-
分块:既将页面或合成层划分成多个小块,进行独立光栅化和更新。
- 分块不是浏览器必须的,而是一种优化手段。
- 通过分块可以实现仅更新视口可见区域或既将进入视口的块。
- 由于可能存在多个合成层,所以可以将多个合成层划分的小块交予其他线程处理。
- 由于非可见区域的块可以延迟处理或降级质量,节省了内存。
-
光栅化:将渲染树中的矢量图形转为屏幕上的像素矩阵。
- 光栅化这一块是浏览器必须的。
- 光栅化可以调用GPU加速页面的绘制。
-
合成:将光栅化期间的像素矩阵交由合成线程进行合并。
-
显示: 画到屏幕上。
回流(reflow)和重绘(repaint)
查看
回流(reflow)
-
第一次确定节点的大小和位置, 称为布局。
-
之后对节点的大小和位置的修改, 称为回流(重排)。
回流(reflow)的触发方式
-
DOM结构发生改变(添加或移除的节点)。
-
改变布局(修改元素的宽度、高度、位置、边框、margin、padding、字体大小等)
-
修改了窗口尺寸(resize)
-
查询元素的尺寸和位置信息(offsetWidth、offsetHeight、offsetLeft、offsetTopget、getComputedStyle等)
重绘(repaint)
-
第一次渲染称之为绘制(paint)。
-
之后重新渲染, 称为重绘。
重绘(repaint)的触发方式
-
修改了元素的背景色、边框颜色、字体颜色等。
回流一定会导致重绘
回流是一件很耗性能的事情, 所以尽量避免。
建议:
-
修改样式时尽量一次性修改。
- 比如通过cssText或添加class修改。
-
尽量避免频繁的操作DOM。
- 比如通过DocumentFragment批量操作DOM或在父元素中将要操作的DOM操作完成,再一次性的操作。
-
尽量避免通过getComputedStyle获取尺寸位置等信息。
-
对某些元素使用position的absolute或fixed。
- 其同样会造成回流, 但开销相对较小, 不会对其他元素产生影响。
script标签的defer和async属性
查看
defer
-
defer属性会告诉浏览器不用等待脚本下载, 而是继续解析HTML, 构建DOM Tree。
- 脚本由浏览器来进行下载, 但不会阻塞DOM Tree的构建过程。
- 如果脚本提前下载好, 他会等待DOM Tree的构建完成, 在DOMContentLoaded事件之前执行。
- 所以DOMContentLoaded总是会等待defer中的代码先执行完成。
-
对于多个defer的脚本, 浏览器会按照脚本的先后顺序执行。
-
由于设置defer后, 并不会阻塞DOM树的构建, 并且会交与浏览器对其下载, 所以推荐将设置defer的脚本放在head中, 减少其因下载损耗的事件, 一定程度上可以提高页面的性能。
注意: defer仅适用于外部脚本, 对于script默认内容会被忽略。
async
-
async是一个让脚本完全独立的:
- 浏览器不会因async脚本而阻塞。
- async脚本不能保证顺序, 它是独立下载, 独立运行, 不会等待其他脚本。
- async不能保证在DOMContentLoaded之前或之后执行。
defer和async的使用场景
-
defer通常用于需要文档解析后操作DOM的JS代码, 并且对多个JS文件有顺序要求.
-
async通常用于独立的脚本, 对其他脚本, 甚至没有DOM依赖的。