主流浏览器对比与发展
- 推荐一个短视频介绍
浏览器/参数 | 厂商 | 内核 | 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 前必须先建立连接,而建立连接是通过三次握手来进行的,简单解释上图可以是:
- 我对你说:我要跟你说话。(此时服务端确认客户端的发送能力没有问题)
- 你跟我说:我知道你要跟我说话,我们开始说话吧。(此时客户端确认服务端的接收和发送能力都没有问题)
- 我跟你说:好的,我们开始说话吧。(此时服务端确认客户端的接收能力也没有问题)
- 上图我们可以这样理解,因为 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 树的构造。但
<script>
标签会阻塞 HTML 的解析,转而执行其中的 js 代码,因为浏览器不清楚此 js 代码是否有改变 DOM 结构,所以先行执行。
- 解析引入的 css、js、img 等等,图片与 css 不会阻塞 HTML 的解析,因为不影响 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 头。
- 页面的初次访问者会进行很多 HTTP 请求,但是通过使用一个长久的 Expires 头,可以使这些组件被缓存,下次访问的时候,就可以减少不必要的 HTPP 请求,从而提高加载速度。
- 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 浏览器中存在兼容性问题,可以引入第三方解析库。
1 | <script src="https://cdn.bootcss.com/html5shiv/3.7.3/html5shiv.min.js"></script> |
1 | @keyframes |
- 避免使用不兼容的 js 代码(时间处理问题、屏幕宽高问题、event 事件问题、DOM 节点问题、事件传播问题、阻止默认事件问题、鼠标滚轮滚动事件问题等等)。
- 鼠标指针
cursor: hand;
只有 IE 浏览器识别,其他浏览器不识别。统一使用cursor: pointer;
- 超链接访问过后 hover 样式就不出现的问题,需要注意伪类顺序
link-visited-hover-active
。 - css hack
1 | background-color: yellow0; // 0 ie8 |
- 使用一个功能之前判断浏览器是否支持,比如使用 ajax 要判断是否支持 XMLHttpRequest,IE6 之前不支持。还有使用本地存储、通知弹窗等 HTML5 新特性时更须注意。
参考来源
- 参考 HTML 引擎
- 参考 JavaScript 引擎
- 参考浏览器原理