关于浏览器的那些事儿
东拼西凑了一些浏览器内容(^-^)
主流浏览器对比与发展
- 推荐一个短视频介绍
浏览器/参数 | 厂商 | 内核 | JS引擎 | 其他 |
---|---|---|---|---|
Chrome |
Chromium、 Blink |
V8 | -webkit- |
|
Safari |
Apple | Webkit | JScore、 SquirrelFish(Nitro)(4.0+) |
-webkit- |
FireFox |
Mozilla | Gecko | SpiderMonkey(1.0-3.0)、 TraceMonkey(3.5-3.6)、 JaegerMonkey(4.0+) |
-moz- |
Opera |
OperaSoftware | Presto、 Webkit、 Blink |
Linear A(4.0-6.1)、 Linear B(7.0-9.2)、 Futhark(9.5-10.2)、 Carakan(10.5-) |
-o- |
IE |
Microsoft | Trident | JScript(IE9-)、 Chakra(IE9+) |
-ms- |
Edge |
Microsoft | EdgeHTML | Chakra | -ms- |
新Edge |
Microsoft | Chromium | V8 | -webkit- |
UC |
阿里巴巴 | U3 | U3集成? | 同红芯浏览器一样换汤不换药 |
360/QQ/搜狗/猎豹 、百度/2345/傲游/世界之窗 |
见下文 | Trident(兼容模式)+Webkit(高速模式)双内核 | / | 能用就行 |
- Chrome 以前是 Chromium 内核(Chrome 内核),在 Webkit 基础上修改,但代码可读性更高,比 Webkit 更好用。目前是使用重新升级换代后的 Blink 内核。谷歌还开发了自己的 JS 引擎 V8,使 JS 运行速度极大地提高,Node.js 也是以 V8 为底层架构封装。另外我们可以通过在地址栏输入
chrome://version/
来查看浏览器相关信息,通过chrome://dino
玩小游戏。 - Safari 的 Webkit 源自 KHTML,苹果在比较了 Gecko 和 KHTML 后,选择了后者来做引擎开发,是因为 KHTML 拥有清晰的源码结构和极快的渲染速度。苹果与谷歌冲突又研发使用 Webkit2 内核,谷歌则研发了 Chromium 内核,Webkit 也算是苹果为业界做出的最大贡献。
- FireFox 的 Gecko 内核俗称 Firefox 内核,代码完全公开,可开发程度高,全世界的程序员都可为其编写代码,增加其功能。还有一个 JS 引擎 Rhino,也是由Mozilla基金会管理,虽然最终被废弃,但其开放源代码,完全以Java编写。
- Opera 最早自己研发 Presto,后面用 Webkit,最后与谷歌一起发布使用 Blink,然后因用户体验下降逐渐衰落。
- IE 是微软和 Spyglass 合作开发,随 Windows 绑定抢占市场,并且只能在 Windows 使用也不开源。
- Edge 原名叫斯巴达,后改名 Edge,2015 年 3 月发布第一个预览版。微软计划在 Windows 中完全淘汰 Internet Explorer 后,为 Edge 添加 “IE 模式”,该模式允许用户在 Edge 内使用 IE 内核重新加载网页。
- 新 Edge 是微软妥协下的产物,2018 年 12 月宣布新 Edge 将基于 Chromium 内核开发,正式版于 2020 年 1 月发布。可以通过
edge://version/
来查看浏览器版本信息,通过edge://surf/
可以玩离线小游戏。 - UC 浏览器的 U3 内核本质是基于开源内核 Webkit 开发,也有说是基于 Gecko 内核与 Trident 内核开发的。
- 众多国产浏览器的厂商分别为360安全、Tencent、搜狗信息、豹好玩科技、Baidu、二三四五、网际傲游、凤凰工作室。这些浏览器适合需要经常访问那种古老系统的用户(兼容模式)
补充:还有一个 JS 引擎 - KJS,KDE 的 ECMAScript/JavaScript 引擎,最初由哈里·波顿开发,用于 KDE 项目的 Konqueror 网页浏览器中。
浏览器的原理
浏览器程序结构
- 用户界面(User Interface) - 包括地址栏、前进/后退按钮、书签菜单等。除了浏览器主窗口(显示页面),其他部分都属于用户界面。
- 浏览器引擎(Browser Engine) - 在用户界面和渲染引擎之间传送指令。
- 渲染引擎(Rendering Engine) - 显示(渲染)请求的内容。如果请求的内容是 HTML,它就负责解析 HTML 和 CSS 内容,并将解析后的内容显示在屏幕上。
- 网络(NetWorking) - 用于网络调用,比如 HTTP 请求。其接口与平台无关,并为所有平台提供底层实现。
- JavaScript 解释器(JavaScript Interpreter)。用于解析和执行 JavaScript 代码。
- 用户界面后端(UI Backend) - 用于绘制基本的窗口小部件,比如组合框和窗口。公开了与平台无关的通用接口,在底层使用操作系统的用户界面方法。
- 数据存储(Data Persistence) - 这是持久层。浏览器需要在硬盘上保存各种数据,例如 Cookie。新的 HTML 规范 (HTML5) 定义了“网络数据库”,这是一个完整(但是轻便)的浏览器内数据库。
浏览器程序结构发展
最早的浏览器上单进程结构,页面线程执行页面渲染,JS 线程执行 JS 代码等等,但是只要其中一个线程出问题,可能整个程序就崩溃了。
- 例如:浏览器的一个标签页卡死,那么整个浏览器都无法使用,导致应用程序极其不稳定。
- 单进程是可以共享数据的,所以并不安全。
- 由于各个线程负责的任务过多,使用起来也并不流畅。
为了解决这些问题,现代浏览器使用了多进程结构。可分为浏览器进程、网络进程、缓存进程、GPU 进程、渲染器进程、插件进程。
- 浏览器进程:控制用户界面,协调其他进程工作。
- 网络进程:发起接收网络请求
- 缓存进程:控制数据缓存
- GPU 进程:负责浏览器界面与页面的渲染
- 渲染器进程:控制 Tab 标签页的渲染,有可能会为每个标签页创建一个渲染进程(由浏览器启动模型决定)。独立每个页面一个进程可以起到进程隔离的作用,每个页面互不干扰。
- 插件进程:控制使用的插件,Flash等等,不是浏览器安装的插件。
渲染主流程
- 浏览器从网络层获取请求的文档内容,然后开始渲染流程。
- 解析并开始构建 Content Tree(Element -> DOM nodes),同时解析样式数据(外部 CSS 和 Style 元素)。
- 两者结合构建 Render Tree(渲染树包含带有视觉属性(如颜色和尺寸)的矩形们)。
- 在渲染树创建后进入 Layout 阶段,给渲染树的每个节点设置在屏幕上的位置信息。
- Paint 阶段,通过 UI backend 绘制 Render tree 到屏幕。
注意,渲染过程是渐进式的。浏览器会尽早展示文档内容,即不会在所有 HTML 文档解析完成后才会去构建 Render tree,而是部分内容被解析和展示,并继续解析和展示剩下的。
浏览器的页面渲染过程
参考文章
在浏览器地址栏输入 URL
浏览器会启动网络线程来请求 DNS 进行域名解析,最终返回一个 IP 地址。
一旦获取到服务器IP地址,浏览器就会通过TCP【三次握手】与服务器建立连接。这个机制的是用来让两端尝试进行通信。浏览器和服务器在发送数据之前,通过上层协议 HTTPS 可以协商网络 TCP 套接字连接的一些参数。TCP的【三次握手】技术经常被称为【SYN-SYN-ACK】,更确切的说是 【SYN, SYN-ACK, ACK】,因为通过 TCP 首先发送了三个消息进行协商,开始一个 TCP 会话在两台电脑之间。这意味着服务器之间还要来回发送三条消息,而我们的请求目前尚未发出。
上图我们可以这样理解,因为 TCP 是一个工作在传输层的可靠数据传输的服务,它能确保接收端接收的网络包是无损坏、无间隔、非冗余和按序的。
- 面向连接:一定是「一对一」才能连接,不能像 UDP 协议可以一个主机同时向多个主机发送消息,也就是一对多是无法做到的。
- 可靠的:无论的网络链路中出现了怎样的链路变化,TCP 都可以保证一个报文一定能够到达接收端。
- 字节流:消息是「没有边界」的,所以无论我们消息有多大都可以进行传输。并且消息是「有序的」,当「前一个」消息没有收到的时候,即使它先收到了后面的字节,那么也不能扔给应用层去处理,同时对「重复」的报文会自动丢弃。
- 参考:ibanmen
TCP 是面向连接的协议,所以使用 TCP 前必须先建立连接,而建立连接是通过三次握手来进行的,简单解释上图可以是:
- 我对你说:我要跟你说话。(此时服务端确认客户端的发送能力没有问题)
- 你跟我说:我知道你要跟我说话,我们开始说话吧。(此时客户端确认服务端的接收和发送能力都没有问题)
我跟你说:好的,我们开始说话吧。(此时服务端确认客户端的接收能力也没有问题)
TLS/SSL 协商:为了在【HTTPS】上建立安全连接,另一种握手是必须的。更确切的说是 TLS/SSL 协商,它决定了什么密码将会被用来加密通信,验证服务器,这就意味着在进行真实的数据传输之前建立安全连接。在发送真正的请求内容之前还需要三次往返服务器。虽然建立安全连接对增加了加载页面的等待时间,对于建立一个安全的连接来说,以增加等待时间为代价是值得的,因为在浏览器和 web 服务器之间传输的数据不可以被第三方解密。
经过上图 8 次往返,浏览器终于可以发出请求。一旦我们建立了到 web 服务器的连接,浏览器就代表用户发送一个初始的 HTTP GET请求,对于网站来说,这个请求通常是一个 HTML 文件。
一旦服务器收到请求,它将使用相关的响应头和 HTML 的内容进行回复。此时网络线程会通知 UI 线程执行后续操作。
UI 线程会创建一个渲染器进程来渲染页面,浏览器进程会通过 IPC 管道将数据传递给渲染器进程。
渲染器进程的主线程接收到数据(HTML),开始渲染解析。
通过标签词法解析,将内容解析为多个标记,然后构造 DOM 树,先创建 document 对象,再不断修改,向其中添加各种元素。
解析引入的 css、js、img 等等,图片与 css 不会阻塞 HTML 的解析,因为不影响 DOM 树的构造。但
标签会阻塞 HTML 的解析,转而执行其中的 js 代码,因为浏览器不清楚此 js 代码是否有改变 DOM 结构,所以先行执行。
构建 DOM 树后,主线程开始解析 css 并确定每个 DOM 节点的样式,即使我们没写样式,每个浏览器都有自己的样式表。
构建 LayoutTree,通过 DOM 树和样式生成 LayoutTree,即确定每个节点的位置。LayoutTree 的每个节点都记录了自己的坐标与边框尺寸等。
- DOM 树与 Layout 树并不是对应的,设置了
display:none;
的节点是不会在 Layout 树中。 有内容显示的伪元素节点不会出现在 DOM 树中,但会出现在 Layout 树中,因为 DOM 是通过 HTML 解析的,而伪元素是通过样式产生的。
- DOM 树与 Layout 树并不是对应的,设置了
渲染器进程的主线程遍历 LayoutTree 确定各个节点的绘制顺序,创建一个绘制记录表(Paint Record)。比如
z-index
值大的元素一般都是最后绘制等等。渲染器进程的主线程再遍历 LayoutTree 生成 Layer Tree。
渲染器进程的主线程将 Layer Tree 与绘制顺序表一起传给合成器线程。
- 合成器线程按规则进行分图层,并把图层生成更小的图块(tiles)再传递给栅格线程进行栅格化。
- 栅格化完成后返还给合成器线程 draw quads 图块信息(每图块的信息与位置等)。
- 合成器线程根据这些 draw quads 信息用 frame 合成器将合成一个合成器帧。
- 再通过 IPC 管道将此合成器帧传递给浏览器进程。
- 浏览器进程收到这帧的图像后传递给 GPU,GPU 渲染到页面上。
- 当你滚动页面时又会重新生成合成器帧,再次渲染到页面上。
- 当我们改变元素位置或尺寸属性时,会重新进行样式计算、布局、绘制等后面所有流程(重排-reflow)。
- 当我们只改变颜色这种属性时,不会引起重新触发布局,会触发样式计算与绘制(重绘-repaint)。
- 因为 JS 也是在主线程运行,所以尽量不要高频触发重绘重排,毕竟布局、绘制也是在占用主线程,高频触发重绘重排会导致页面掉帧。
- 所以尽量减少重绘重排,可以转而使用 CSS3 的 transform 动画来达到效果(会直接运行合成器线程),这些是不会占用主线程的,能够避免重绘重排与 js 执行抢夺主线程导致页面卡顿掉帧的问题。
- 在移动端使用 3D 转换可以优化性能。如果设备有 3D 加速引擎 GPU 可以提高性能,2D 转换是无法调用 GPU,2D 是靠的 CPU。
也可以利用
requestAnimationFrame()
API,利用浏览器的空闲时间来优化,React Fiber 就是使用此 API,他会将主线程的任务分散到每一帧的间隔,从而不影响动画的流程。
收到全部内容之后可以选择断开(根据 Connection 请求头,若为 keep-alive 则保持。)与服务器之间的 TCP 连接。
- 我对你说:好了,我不想跟你说话了,再见。(客户端发送关闭连接请求给服务端)
- 你对我说:好的,那我要跟你再见啦。(服务端确认客户端的请求)
- 你对我说:现在我要跟你再见了,你知道了吗。(服务端请求关闭连接)
我对你说:我知道了,再见!(客户端确认请求)
浏览器页面的优化
性能优化
- 减少 DNS 查找(使用 cdn 等)
- js 延迟加载或异步加载
- 尽量使用 link,减少使用 @import,import 是最后挂载的。
- 减少 HTTP 请求(CSS Sprite、合并 css、合并 js 等等)
- 将 html/css/js/img 等文件压缩
- 开启 gzip 模块
- 善于开启并利用缓存,从缓存中读取图片与 html/css/js 等。
添加 Expires 头缓存
页面的初次访问者会进行很多 HTTP 请求,但是通过使用一个长久的 Expires 头,可以使这些组件被缓存,下次访问的时候,就可以减少不必要的 HTPP 请求,从而提高加载速度。
- Web 服务器通过 Expires 头告诉客户端可以使用一个组件的当前副本,直到指定的时间为止。
- 例如:
Expires: Fri, 18 Mar 2016 07:41:53 GMT
- Expires 缺点: 它要求服务器和客户端时钟严格同步,过期日期需要经常检查。
- HTTP1.1 中引入 Cache-Control 来克服 Expires 头的限制,使用 max-age 指定组件被缓存多久。
Cache-Control: max-age=12345600
- 若同时制定 Cache-Control 和 Expires,则 max-age 将覆盖 Expires 头。
HTML 代码优化
- 使用语义化的标签,代码清晰简洁。
- 使用 W3C 标准书写闭合小写的标签。
- 避免使用空请求,包括空的 href 链接、空 src 链接。空链接本身无法请求成功,因此会把一个 HTTP 请求拖到超时,而且空链接会阻塞页面中其他资源的下载进程,会拖慢页面加载速度。
- 根据项目大小,选择主要使用 class 还是 id。id 选择器优先级最高,访问速度最快。但是在 html 中每声明一个 id,就会在 js 底层声明一个全局变量,而全局变量的增多,将会拖慢 js 中变量遍历的效率,推荐项目小可以用 id,项目大少用 id。
- 预先设定图片与 table 大小,避免缩放。在页面加载过程中,图片最后加载,若不对图片预设大小,当图片加载完成后,将会引起大量的重排,将会浪费浏览器资源及拖慢页面加载速度。
- 尽量减少 DOM 元素的数量与层级。解析 HTML 时,标签的数量越多,标签的层级越深,浏览器解析构建 DOM 树的时间就越长,应尽可能的减少 DOM 元素的数量和层级。
- 尽量避免使用 table 标签。浏览器对 table 标签的解析是全部生成后再一次性绘制的,因此会造成表格位置较长时间的空白,推荐使用 ul 及 li 标签绘制表格。
- 使用异步加载 iframe 标签。浏览器加载 iframe 标签时,会阻塞父页面渲染树的构建及 HTTP 请求,因此尽量使用异步加载 iframe。
CSS 代码优化
禁止使用样式表达式,它的解析速度较慢,而且运算次数远比我们想象的要大,随意动动鼠标就能轻松达到上万次运算,会对页面性能造成影响。
- 例如:
#myDiv{width: expression(document.body.offsetWidth-110+"px");}
。
- 例如:
- 优化关键选择器,去掉无效的父级选择器,尽量少在选择器末尾使用通配符。大多数人都认为,浏览器对 CSS 选择器的解析式从左往右进行的,但是其实是从右到左执行的。
- 减少无效代码,注意公用样式。
JS 代码优化
- 多个 js 变量声明合并。
- 不使用 eval 函数,不安全,性能消耗严重。
- 使用事件代理绑定事件,如将事件绑定到 body 上进行代理,利用冒泡原理将事件加到父级上,能够给动态增加的元素进行数据绑定。
- 避免频繁的操作 DOM 节点,使用 innerHTML 代替,从而减少重绘和重排。
- 减少全局变量,尽量使用局部变量。js 中全局变量运算速率远低于局部变量,速度差异达到上百倍,且全局变量越多,全局变量的查找速率便越慢。
- 减少 js 对 css 样式的修改从而减少重绘和重排。
- 减少 ajax 请求,常用数据存本地。
- LazyLoad Images
兼容性优化
- HTML5 新的语义标签在低版本的老 IE 浏览器中存在兼容性问题,可以引入第三方解析库。
@keyframes
transform
transtion
animation
border-radius
box-shadow
flex
...
- 避免使用不兼容的 js 代码(时间处理问题、屏幕宽高问题、event 事件问题、DOM 节点问题、事件传播问题、阻止默认事件问题、鼠标滚轮滚动事件问题等等)。
- 鼠标指针
cursor: hand;
只有 IE 浏览器识别,其他浏览器不识别。统一使用cursor: pointer;
- 超链接访问过后 hover 样式就不出现的问题,需要注意伪类顺序
link-visited-hover-active
。 - css hack
background-color: yellow0; // 0 ie8
+background-color: pink; // + ie7
_background-color: orange; // _ ie6
- 使用一个功能之前判断浏览器是否支持,比如使用 ajax 要判断是否支持 XMLHttpRequest,IE6 之前不支持。还有使用本地存储、通知弹窗等 HTML5 新特性时更须注意。