本篇博客仅以个人学习记录使用
回顾
在介绍浏览器组成结构之前,我们先来回顾一个老生常谈的问题:
从我们打开浏览器输入一个网址到页面展示内容的这段时间内,浏览器和服务端发生了什么事情?
1.在接收到用户输入的网址后,浏览器会开启一个线程来处理这个请求,对用户输入的URL地址进行分析判断
如果是HTTP协议就按照HTTP方式来处理。
2.调用浏览器引擎中的对应方法,比如WebView中的loadUrl方法,分析并加载这个URL地址。
3.通过DNS解析获取该网站地址对应的IP地址,查询完成后连同浏览器的Cookie、userAgent等信息向网站目标IP发出GET请求。
4.进行HTTP协议会话,浏览器客户端向Web服务器发送报文。
5.进入网站后台上的Web服务器处理请求,如Apache、Tomcat、Node.js等服务器。
6.进入部署好的后端应用程序,找到对应的请求处理逻辑,这期间可能会读取服务器缓存或查询数据库等。
7.服务器处理请求并返回响应报文,此时如果浏览器访问过该页面,缓存上有对应资源,会与服务器最后修改记录对比,
一致则返回304,否则返回200和对应的内容。
8.如果返回的状态码是200,浏览器开始下载HTML文档,如果是304则从本地缓存读取文件内容。
9.浏览器根据下载接收到的HTML文件解析结构建立DOM文档树,并根据HTML中的标记请求下载指定的MIME类型文件(如CSS,JS脚本等),同时设置缓存等内容。
10.页面开始解析渲染DOM,CSS根据规则解析并结合DOM文档树进行网页内容布局和绘制渲染,Javascript根据DOM API操作DOM,
并读取浏览器缓存,执行事件绑定等,页面整个展示过程完成。
整个过程中使用到了较多的浏览器功能,如用户地址栏输入框、网络请求、浏览器文档解析、渲染引擎、Javascript执行引擎、客户端存储等。
下面,我们具体来看一下浏览器的主要结构:
浏览器主要结构
通常我们认为浏览器主要由七个部分组成:用户界面、网络、JavaScript解释器和持久化数据存储。
用户界面包括浏览器中可见的地址输入框、浏览器前进返回按钮、打开书签、打开历史记录等用户可操作的功能选项。
浏览器引擎可以在用户界面和渲染引擎之间传送指令或在客户端本地缓存中读写数据等,浏览器引擎是浏览器中各个部分之间相互通信的核心。
浏览器渲染引擎的功能是解析DOM文档和CSS规则并将内容排版到浏览器中显示,也有人撑之为排版引擎,我们常说的浏览器内核主要指的就是渲染引擎。
网络功能模块则是浏览器开启网络线程发送请求或下载资源文件的模块,例如DOM树解析过程中请求及静态资源首先是通过浏览器中的网络模块发起的,
UI后端则用于绘制基本的浏览器窗口内控件,比如组合选择框、按钮、输入框等。Javascript解释器则是浏览器解释和执行Javascript脚本的部分,
例如V8引擎。浏览器数据持久化存储则涉及cookie、localStorage等一些客户端存储技术,可以通过浏览器引擎提供的API进行调用。
浏览器渲染引擎简介
渲染引擎的主要工作原理
渲染引擎在浏览器中主要用于解析HTML文档和CSS文档,然后将CSS规则应用到HTML标签元素上,并将HTML渲染到浏览器窗口中以显示具体的DOM内容。
浏览器通过网络模块下载HTML文件后进行页面解析渲染,流程主要包括,解析HTML构建DOM树、构建渲染树、渲染树布局、绘制渲染树。
机械HTML构建DOM树时渲染引擎会先将HTML元素标签解析成由多个DOM元素对象节点组成的且具有节点父子关系的DOM树结构,然后根据DOM树结构的每个
节点顺序提取计算使用的CSS规则并重新计算DOM树结构的样式数据,生成一个带样式描述的DOM渲染树对象。DOM渲染树生成结束后,进入渲染树的布局
阶段,即根据每个渲染树节点在页面中的大小和位置,将节点固定到页面的对应位置上,这个阶段主要是元素的布局属性(如position、float、margin
等属性)生效,即在浏览器中绘制页面上元素节点的位置。接下来就是绘制阶段,HTMLElement将渲染树节点的背景、颜色、文本等样式信息应用到每个节点上,
这个阶段主要是元素的内部显示样式(例如color、background、text-shadow等属性)生效、最终完成整个DOM在页面上的绘制显示。
1 | 这里我们要注意的是渲染树的布局阶段和绘制阶段。 |
渲染引擎对DOM渲染树的解析和输出是逐行进行的,所以渲染树前面的内容可以先渲染展示,这样就保证了较好的用户体验。另外也尽量不要在HTML显示
内容中插入script脚本等标签,script标签内容的解释执行常常会阻塞页面结构的渲染。
通常情况下,不同浏览器内核的解析渲染过程也略有不同,我们以Chrome,Safari浏览器的webkit内核和Firefox浏览器的Gecko内核为例,
来看看渲染引擎工作流程的这四个步骤具体是怎样完成的。
Webkit内核和Gecko内核
这两种渲染引擎工作流程的主要区别在于解析HTML或CSS文档生成渲染树的过程;Webkit内核中的HTML和CSS解析可以认为是并行的;而
Gecko则是先解析HTML,生成内容Sink(Conent Sink可以认为是构建DOM结构树的工厂方法)后再开始解析CSS。这两种渲染引擎工作过程中
使用的描述术语也不一样;Webkit内核解析后的渲染对象被称为渲染树(Render Tree),而Gecko内核解析后的渲染树对象则称为Frame树
(Frame Tree)。但是他们主要的流程是相似的,都经过HTML DOM解析、CSS样式解析、渲染树生成和渲染树绘制显示阶段。一般渲染引擎的解析过程
中都包含了HTML解析和CSS解析阶段,这也是渲染引擎解析流程中最重要的两个部分,接下来就看看HTML文档解析和CSS规则解析具体是怎样进行的。
HTML文档解析
HTML文档解析过程是将HTML文本字符串逐行解析生成具有父子关系的DOM节点树对象的过程,通过解析HTML文档就生成了由多个不同DOM元素对象组成的
DOM树。渲染引擎在HTML解析阶段会对HTML文本的标签进行分析,同时创建DOM对象,最终生成DOM对象树。需要注意的是,<header>、<nav>、<section>、<footer>这些布局类标签的DOM类型均为HTMLElement,此外HTML中不同标签的每个元素对应的原始类型也可以是不一样
的。
1 | 根元素: |
渲染引擎通过解析HTML文本形成了对象化的DOM树,但要将DOM树渲染到浏览器窗口中形成有样式的内容,仍需要结合CSS规则生成一个带有节点CSS样式
描述的DOM树。
1 | DOM元素标签和DOM元素对象虽然都是用来描述DOM结构的, |
CSS解析
CSS解析和HTML解析类似,首先也要通过词法解析生成CSS分析树,而且也使用特定的CSS文本语法来实现,不同的是,HTML是使用类似XML
结构的语法解析方式来完成分析的。
通过分析CSS文档,会生成CSS规则树,CSSRule会保持每个不同元素和对应样式的映射关系。在渲染树逐行生成的阶段,DOM树中的节点会在CSS分析树
中根据元素、类、id选择器来提取与之对应元素的CSS规则,进行CSS规则的层叠和权重计算,得到最终生效的样式CSSRule并添加到DOM渲染树上,
也就是说当每个DOM节点提取CSS样式完成时,用于页面布局和绘制的DOM渲染树便形成了。
浏览器数据持久化存储技术
浏览器缓存是浏览器端用于在本地保存数据并进行快速读取以避免重复资源请求的传输机制的统称。有效的缓存可以避免重复的网络资源请求
并让浏览器快速的响应用户操作,提高页面内容的加载速度。浏览器端缓存的实现机制种类较多,一般可以分为九种:
HTTP文件缓存、LocalStorage、SessionStorage、indexDB、Web SQL、Cookie、CacheStorage、Application Cache、以及Flash缓存
HTTP文件缓存
HTTP文件缓存是基于HTTP协议的浏览器端文件级缓存机制。在文件重复请求的情况下,浏览器可以根据HTTP响应的协议头信息判断是从服务器端请求
文件还是从本地读取文件,Chrome控制台下的Frames就可以查看浏览器的HTTP文件资源缓存列表内容。
HTTP文件缓存判断机制流程:
1.浏览器会先查询Cache-Control(这里用Expires判断也是可以的,但是Expires一般设置的是绝对过期时间,Cache-Control设置的是相对过期时间)
来判断内容是否过期,如果未过期,则直接读取浏览器端缓存文件,不发送HTTP请求,否则进入下一步。
2.在浏览器端判断上次文件返回头中是否含有Etag信息,有则连同If-None-Match一起向服务器发送请求,服务端判断Etag未修改则
返回状态304,修改则返回200,否则进入下一步。
3.如果在步骤2中没有Etag信息,则在浏览器端判断上次文件返回头中是否含有Last-Modified信息,有则连同If-Modified-Since一起向服务器发送
请求,服务器端判断Last-Modified是否生效,失效则返回200,未失效则返回304.
4.如果Etag和Last-Modified都不存在,直接向服务器请求内容。
HTTP缓存可以在文件缓存生效的情况下让浏览器从本地读取文件,不仅加快了页面资源加载,同时节省网络流量,所以在Web站点配置中要尽可能利用缓
存来优化请求过程。在HTML中,我们可以添加<meta>中的Expires或Cache-Control来设置,需要注意的是,一般这里Cache-Control设置max-age
的时间单位是秒,如果同时设置了Expires和Cache-Control,则只有Cache-Control的设置生效。
1 | <meta http-equiv="Expires" content="Mon, 20 Jul 2019 23:00:00 GMT" /> |
当然服务器端也需要进行对应设置,Node.js服务器可以使用中间件来这样设置静态资源文件的缓存时间,例如我们可以结合Koa Web框架和Koa-static
中间件如下设置实现。
1 | const static = require('koa-static'); |
localStorage
localStorage是HTML5的一种本地缓存方案,目前主要用于浏览器端保存体积较大的数据(如AJAX返回结果等),需要了解的是,localStorage
在不同浏览器中有长度限制且各不相同。值得注意的是,这里的大小限制指的是单个域名下localStorage的大小,所以localStorage中不适合
存放过多的数据,如果数据存放超过最大限制可能会读取报错,因此在使用之后最好移除不再使用的数据。此外localStorage只支持简单数据类型的读取
,为了方便localStorage读取对象等格式的内容,通常需要进行一层安全封装再引入使用。
1 | 尽管单个域名下localStorage的大小是有限制的,但是可以用iframe的方式使用多个域名来突破单个页面下localStorage存储数据的最大限制。 |
sessionStorage
sessionStorage和localStorage的功能类似,但是sessionStorage在浏览器关闭时会自动清空。sessionStorage的Api和localStorage完全相同。
由于不能进行客户端的持久化数据存储,实际项目中sessionStorage的使用场景相对较少
Cookie
cookie指网站为了辨别用户身份或Session跟踪而储存在用户浏览器端的数据。Cookie信息一般会通过HTTP请求发送到服务器端。一条cookie记录
主要由键、值、域、过期时间和大小组成,一般用于保存用户的网站认证信息。浏览器中cookie的最大长度和单个域名支持的cookie个数由
浏览器的不同来决定。和localStorage类似,不同域名之间的cookie信息也是独立的,如果需要设置共享,则可以在被共享域名的服务器端设置
cookie的path和domain来实现,例如koa下:
1 | this.cookies.set('username','ouven', { |
浏览器端也可以通过document.cookie来获取cookie,并通过js去处理解析。但是需要注意的是,cookie分为两种;Session Cookie和
持久型Cookie。Session Cookie一般不设置过期时间,表示该cookie的声明周期为浏览器会话期间,只要关闭浏览器窗口,cookie就会消失,
而且Session Cookie一般不保存在硬盘上而是保存在内存里,持久型cookie一般会设置过期时间,而且浏览器会将持久型cookie的信息保存到硬盘上,
关闭后再次打开浏览器,这些cookie依然有效,直到超过设定的过期时间或被清空才失效。Cookie设置中有个Httponly参数,前端浏览器使用
document.cookie是读取不到HTTPOnly类型cookie的,被设置为HTTPOnly的cookie记录只能通过HTTP请求头发送到服务器端进行读写操作,这样
就避免了服务器端的cookie记录被前端js篡改,保证了服务端验证cookie的安全性。
js在浏览器端可以通过document.cookie来读取非HTTPOnly类型的Cookie记录,document.cookie的内容通常是用等号和分号分割的键值对形式的字符串。
在前端浏览器中,如果要对document.cookie进行修改就比较麻烦了,不过我们同样可以进行统一的封装来达到方便读取和操作cookie的目的
WebSQL(非HTML5规范,了解即可)
WebSQL是浏览器端用于存储较大量数据的缓存机制,不过这只有较新版本的Chrome浏览器支持该机制,并以一个独立浏览器端数据存储规范的形式出现。
WebSQL主要有以下几个特点:
1.WebSQL数据库API实际上不是HTML5规范的组成部分,目前只是一种特定的浏览器特性,而且WebSQL在HTML5之前就已经存在,是单独的规范。
2.WebSQL将数据以数据库二维表的形式存储在客户端,可以根据需要使用js去读取
3.WebSQL与其他存储方式的区别:localStorage和cookie以键值对的形式存在,WebSQL为了更便于检索,允许SQL语句的查询。
4.WebSQL可以让浏览器实现小型数据库存储功能,而且使用的数据库是集成在浏览器里面的
WebSQL API主要包括三个核心方法:openDatabase()、transaction()和executeSql()。
openDatabase()方法可以打开已存在的数据库,并默认创建不存在的数据库。openDatabase()中的五个参数分别为数据库名、版本号、描述、
数据库大小、创建回调,即使创建回调为null也可以创建数据库,transaction()方法允许我们根据情况控制事务提交或回滚,executeSql()则
用于执行真是的SQL查询语句。如果需要进行读操作,而且是读取已存在的记录,我们也可以使用一个回调函数来处理查询的结果。由于兼容性的问题,
加上使用场景比较有限,目前WebSQL的应用并不是很广泛,作为浏览器缓存技术的一种方式仍应当了解。
IndexDB(数据易泄漏,,,不安全,了解即可)
IndexDB也是一个可在客户端存储大量结构化数据并且能在这些数据上使用索引进行高性能检索的一套API。由于WebSQL不是HTML5规范,所以一般推荐
使用IndexDB来进行大量数据的存储。其基本实现和WebSQL类似,只是使用的API规范不一样,WebSQL使用类似NoSql数据库的设计实现,读取效率更高。
浏览器对IndexDB的大小限制通常约为50MB,这样就可以将大量的应用数据保存到本地,在本地满足需要搜索的场景。和WebSQL类似,目前使用IndexDB
的实际应用场景也不是很多,而且将大量数据保存到本地也会造成数据泄露,所以了解即可。
Application Cache(弃用,,,已被ServiceWorkers替代)
Application Cache 是一种允许浏览器通过manifest配置文件在本地有选择性的存储js、css、图片等静态资源的文件级缓存机制。
当页面不是首次打开时,通过一个特定的manifest文件配置描述来选择读取本地Application Cache里面的文件。所以使用Application Cache
来实现浏览器应用具有以下三个优势:
1.离线浏览。通过manifest配置描述来读取本地文件,用户可在离线时浏览完整的页面内容。
2.快速加载。由于缓存资源为本地资源,因此页面加载速度较快
3.服务器负载小,只有在文件资源更新时,浏览器才会从服务器端下载,这样就减小了服务器资源请求的压力
Application Cache 在页面第二次被访问时开始生效。页面打开时优先从Application Cache中访问资源,读取资源加载后同时会去请求
检查服务端的manifest文件是否已更新,如果没有更新,则整个访问过程结束;否则浏览器会去检查manifest列表,将更新的内容重新拉取到
Application Cache中,这样页面第三次访问时就可以加载到更新后的内容了,所以前端页面开发完成后更新的内容将在用户再一次访问时才会生效,
而不是马上就能生效。通常,一个基本的Application Cache离线页面应用至少应该包括HTML页面的manifest配置引用与被引用的manifest文件
另外需要注意的是,在更新缓存时,我们也可以通过window.applicationCache对象来访问浏览器的Application Cache,并可以查看对象的status
属性来获取cache对象的当前状态。
applicationCache对象也可以主动更新cache内容,不需要等到下一次更新。例如调用applicationCache.update()方法,这样浏览器可以去主动
尝试更新用户的Cache(在manifest文件已经改变的情况下)。最后,当applicationCache.status处于UPDATEREADY状态时,
调用applicationCache.swapCache()方法,旧的cache内容就会被置换成新的。
尽管Application Cache的实现很方便,但是仍然需要注意以下几个问题。
1.Application Cache已经开始被标准弃用,渐渐将会由ServiceWorkers来代替,所以现在不建议使用,了解即可
cacheStorage
cacheStorage是在ServiceWorker规范中定义的,可用于保存每个ServiceWorker声明的Cache对象,有open()、match()、has()、delete()、
keys()、五个核心API方法。可以对Cache对象的不同匹配内容进行不同的响应,CacheStorage在浏览器端为window下的全局内置对象caches,
那么我们就可以直接以caches.open()的方式直接使用。
要了解cacheStorage,我们必须深入了解一下ServiceWorker,ServiceWorker与WebWorker一样是在浏览器后台作为一个独立的线程运行的js脚本,
可以为浏览器提供并行的计算和数据处理能力,并通过message/postMessage方法在页面之间进行通信,但是不能与前端界面进行交互。我们知道
Native App(一般指移动客户端的原生应用)可以做到消息推送、离线使用、自动更新等,同样地,如果使用ServiceWorker也可以让Web应用具有类似
功能。
ServiceWorker有他的生命周期,我们在使用ServiceWorker时通常需要先注册ServiceWorker的脚本文件,然后在其脚本中运行caches的缓存控制
方法,caches是浏览器提供的用于存储文件缓存管理的对象,也是浏览器提供的CacheStorage全局对象。
flash缓存(基本已经凉凉了)
主要基于flash,然而flash已经不再推荐使用了
总结
关于浏览器缓存实现的技术和方式较多,尤其是在较新的浏览器上,但目前可以在实际项目中配置应用的有HTTP缓存、localStorage、Cookie
和ServiceWorker。其他的缓存方式我们仅作为知识和历史发展过程了解即可