Closed zhongxia245 closed 5 years ago
时间:2017-03-27 18:02:47
该文章为 《HTML5缓存机制浅析:移动端Web加载性能优化》 的读书笔记,整理一下自己的收获。
HTML5是新一代的HTML标准,加入很多新的特性。离线存储(也可称为缓存机制)是其中一个非常重要的特性。HTML5引入的离线存储,这意味着Web应用可进行缓存,并可在没有因特网连接时进行访问。
勾选的,表示熟悉
Web SQL DataBase 官方的标准文档不在推荐使用,将来也不再维护,(2015年12月); 各大桌面浏览器和移动端浏览器都有很好的实现这个接口,兼容性问题不大,底层基本都是 sqlite(正因为这样,作为一个web标准是不可接受的) 经测试,IOS上容量最大支持50M,不过用系统自带的safari,超过5MB,会主动提醒用户是否要增加数据库大小,不友好(不过微信里面不会) 5MB 如果用来存一些日志,还是够的(当然需要记得清理旧的日志) websql 目前是主要的解决方案(腾讯用来做日志记录,方便排查用户的错误反馈)
Web SQL DataBase 官方的标准文档不在推荐使用,将来也不再维护,(2015年12月);
等下查下最新的情况。
这里只是官网推荐的比较,具体使用还需要看目前业界上的情况。 比如websql目前还是主流的,indexdb是未来用来替换的,具体需要多久,还需要等等。
先对各个缓存机制有一个大体的了解,然后才能去考虑如何优化。目前对 浏览器缓存和DOM Storage比较熟悉,其他的大体了解,但是没有项目中用过。
浏览器缓存机制是指通过HTTP协议头里的 Cache-Control / Expires 和 Last-Modified / ETag 等字段控制文件缓存的机制。
和 DOM Storage, AppCache 等缓存机制,本质一样。 一个在HTTP协议层实现(浏览器缓存),一个在应用层实现。
采用 Cache-Control 和 Expires 来控制缓存。 Chrome开发者工具NetWork, 查看为 form disk cache
form disk cache
Cache-Control (HTTP1.1标准中新增的字段)
Cache-Control:max-age=600
Expires (HTTP1.0标准中的字段)
Expires: Thu, 10 Nov 2015 08:45:11 GMT
HTTP 的状态为 304 表示 协商缓存
Last-Modified 和 If-Modified-Since
Last-Modified是标识文件在服务器上的最新更新时间。下次请求时,如果文件缓存过期,浏览器通过If-Modified-Since字段带上这个时间,发送给服务器,由服务器比较时间戳来判断文件是否有修改。如果没有修改,服务器返回304告诉浏览器继续使用缓存;如果有修改,则返回200,同时返回最新的文件。
ETag 和 If-None-Match
Etag也是和Last-Modified一样,对文件进行标识的字段。不同的是,Etag的取值是一个对文件进行标识的特征字串。在向服务器查询文件是否有更新时,浏览器通过If-None-Match字段把特征字串发送给服务器,由服务器和文件最新特征字串进行匹配,来判断文件是否有更新。没有更新回包304,有更新回包200。
Etag和Last-Modified可根据需求使用一个或两个同时使用。两个同时使用时,只要满足基中一个条件,就认为文件没有更新。
手动刷新页面(F5),浏览器会直接认为缓存已经过期(可能缓存还没有过期),在请求中加上字段:Cache-Control:max-age=0,发包向服务器查询是否有文件是否有更新。
可能存在304协商缓存
强制刷新页面(Ctrl+F5),浏览器会直接忽略本地的缓存(有缓存也会认为本地没有缓存),在请求中加上字段:Cache-Control:no-cache(或 Pragma:no-cache),发包向服务重新拉取文件。
不存在强缓存和协商缓存
一个简单的键值对存储系统,接口简单实用,兼容性也很好。有大小限制,提供5M(不同浏览器可能不同,区分host, cookie 只有 4k)
interface Storage { readonly attribute unsigned long length; [IndexGetter] DOMString key(in unsigned long index); [NameGetter] DOMString getItem(in DOMString key); [NameSetter] void setItem(in DOMString key, in DOMString data); [NameDeleter] void removeItem(in DOMString key); void clear(); };
LocalStorage 和 SessionStorage 的区别, SessionStorage 在页面选项卡关闭后,就不存在了, 但是 LocalStorage会还存在。但是注意:页面刷新的时候,SessionStorage还是存在的。
比如在 页面刷新后,表单填写的内容还存在,这个时候使用 SessionStorage最有用了。
分析:Dom Storage给Web提供了一种更录活的数据存储方式,存储空间更大(相对Cookies),用法也比较简单,方便存储服务器或本地的一些临时数据。
目前用来存储结构化数据还是比较主流的一个选择。 参考 《腾讯开发工程师:前端异常监控到底怎么做》
IOS下,最大支持50M , 系统自带浏览器Safari,默认超过5M的时候,会弹出提示让用户增加大小,不太友好。
微信浏览器下,不会提示。
<script> if(window.openDatabase){ //打开数据库,如果没有则创建 var db = openDatabase('mydb', '1.0', 'Test DB', 2 * 1024); //通过事务,创建一个表,并添加两条记录 db.transaction(function (tx) { tx.executeSql('CREATE TABLE IF NOT EXISTS LOGS (id unique, log)'); tx.executeSql('INSERT INTO LOGS (id, log) VALUES (1, "foobar")'); tx.executeSql('INSERT INTO LOGS (id, log) VALUES (2, "logmsg")'); }); //查询表中所有记录,并展示出来 db.transaction(function (tx) { tx.executeSql('SELECT * FROM LOGS', [], function (tx, results) { var len = results.rows.length, i; msg = "<p>Found rows: " + len + "</p>"; for(i=0; i<len; i++){ msg += "<p>" + results.rows.item(i).log + "</p>"; } document.querySelector('#status').innerHTML = msg; }, null); }); } </script> <div id="status" name="status">Status Message</div><br>
似乎是为了支持 Web App 离线使用二开发的缓存机制。 缓存 和 浏览器缓存机制类似。
AppCache的原理有两个关键点:manifest属性和 manifest文件。
CACHE MANIFEST # 2012-02-21 v1.0.0 /theme.css /logo.gif /main.js NETWORK: login.asp FALLBACK: /html/ /offline.html <br>
分析:AppCache看起来是一种比较好的缓存方法,除了缓存静态资源文件外,也适合构建Web离线 App。在实际使用中有些需要注意的地方,有一些可以说是”坑“。
要更新缓存的文件,需要更新包含它的manifest文件,那怕只加一个空格。常用的方法,是修改manifest文件注释中的版本号。如:# 2012-02-21 v1.0.0;
被缓存的文件,浏览器是先使用,再通过检查manifest文件是否有更新来更新缓存文件。这样缓存文件可能用的不是最新的版本。
在更新缓存过程中,如果有一个文件更新失败,则整个更新会失败。
manifest和引用它的HTML要在相同Host。
manifest文件中的文件列表,如果是相对路径,则是相对manifest文件的相对路径。
manifest也有可能更新出错,导致缓存文件更新失败。
没有缓存的资源在已经缓存的HTML中不能加载,即使有网络。例如:http://appcache-demo.s3-website-us-east-1.amazonaws.com/without-network/。
manifest文件本身不能被缓存,且manifest文件的更新使用的是浏览器缓存机制。所以manifest
文件的Cache-Control缓存时间不能设置太长。 另外,根据官方文档,AppCache已经不推荐使用了,标准也不会再支持。现在主流的浏览器都是还支持AppCache的,以后就不太确定了。
IndexedDB也是一种数据库的存储机制,但不同于已经不再支持的Web SQL Database。IndexedDB不是传统的关系数据库,可归为NoSQL数据库。IndexedDB又类似于Dom Storage的key-value的存储方式,但功能更强大,且存储空间更大。
<script type="text/javascript"> var db; window.indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB; //浏览器是否支持IndexedDB if (window.indexedDB) { //打开数据库,如果没有,则创建 var openRequest = window.indexedDB.open("people_db", 1); //DB版本设置或升级时回调 openRequest.onupgradeneeded = function(e) { console.log("Upgrading..."); var thisDB = e.target.result; if(!thisDB.objectStoreNames.contains("people")) { console.log("Create Object Store: people."); //创建存储对象,类似于关系数据库的表 thisDB.createObjectStore("people", { autoIncrement:true }); //创建存储对象, 还创建索引 //var objectStore = thisDB.createObjectStore("people",{ autoIncrement:true }); // //first arg is name of index, second is the path (col); //objectStore.createIndex("name","name", {unique:false}); //objectStore.createIndex("email","email", {unique:true}); } } //DB成功打开回调 openRequest.onsuccess = function(e) { console.log("Success!"); //保存全局的数据库对象,后面会用到 db = e.target.result; //绑定按钮点击事件 document.querySelector("#addButton").addEventListener("click", addPerson, false); document.querySelector("#getButton").addEventListener("click", getPerson, false); document.querySelector("#getAllButton").addEventListener("click", getPeople, false); document.querySelector("#getByName").addEventListener("click", getPeopleByNameIndex1, false); } //DB打开失败回调 openRequest.onerror = function(e) { console.log("Error"); console.dir(e); } }else{ alert('Sorry! Your browser doesn\'t support the IndexedDB.'); } //添加一条记录 function addPerson(e) { var name = document.querySelector("#name").value; var email = document.querySelector("#email").value; console.log("About to add "+name+"/"+email); var transaction = db.transaction(["people"],"readwrite"); var store = transaction.objectStore("people"); //Define a person var person = { name:name, email:email, created:new Date() } //Perform the add var request = store.add(person); //var request = store.put(person, 2); request.onerror = function(e) { console.log("Error",e.target.error.name); //some type of error handler } request.onsuccess = function(e) { console.log("Woot! Did it."); } } //通过KEY查询记录 function getPerson(e) { var key = document.querySelector("#key").value; if(key === "" || isNaN(key)) return; var transaction = db.transaction(["people"],"readonly"); var store = transaction.objectStore("people"); var request = store.get(Number(key)); request.onsuccess = function(e) { var result = e.target.result; console.dir(result); if(result) { var s = "<p><h2>Key "+key+"</h2></p>"; for(var field in result) { s+= field+"="+result[field]+"<br/>"; } document.querySelector("#status").innerHTML = s; } else { document.querySelector("#status").innerHTML = "<h2>No match!</h2>"; } } } //获取所有记录 function getPeople(e) { var s = ""; db.transaction(["people"], "readonly").objectStore("people").openCursor().onsuccess = function(e) { var cursor = e.target.result; if(cursor) { s += "<p><h2>Key "+cursor.key+"</h2></p>"; for(var field in cursor.value) { s+= field+"="+cursor.value[field]+"<br/>"; } s+="</p>"; cursor.continue(); } document.querySelector("#status2").innerHTML = s; } } //通过索引查询记录 function getPeopleByNameIndex(e) { var name = document.querySelector("#name1").value; var transaction = db.transaction(["people"],"readonly"); var store = transaction.objectStore("people"); var index = store.index("name"); //name is some value var request = index.get(name); request.onsuccess = function(e) { var result = e.target.result; if(result) { var s = "<p><h2>Name "+name+"</h2><p>"; for(var field in result) { s+= field+"="+result[field]+"<br/>"; } s+="</p>"; } else { document.querySelector("#status3").innerHTML = "<h2>No match!</h2>"; } } } //通过索引查询记录 function getPeopleByNameIndex1(e) { var s = ""; var name = document.querySelector("#name1").value; var transaction = db.transaction(["people"],"readonly"); var store = transaction.objectStore("people"); var index = store.index("name"); //name is some value index.openCursor().onsuccess = function(e) { var cursor = e.target.result; if(cursor) { s += "<p><h2>Key "+cursor.key+"</h2></p>"; for(var field in cursor.value) { s+= field+"="+cursor.value[field]+"<br/>"; } s+="</p>"; cursor.continue(); } document.querySelector("#status3").innerHTML = s; } } </script> <p>添加数据<br/> <input type="text" id="name" placeholder="Name"><br/> <input type="email" id="email" placeholder="Email"><br/> <button id="addButton">Add Data</button> </p> <p>根据Key查询数据<br/> <input type="text" id="key" placeholder="Key"><br/> <button id="getButton">Get Data</button> </p> <div id="status" name="status"></div> <p>获取所有数据<br/> <button id="getAllButton">Get EveryOne</button> </p> <div id="status2" name="status2"></div> <p>根据索引:Name查询数据<br/> <input type="text" id="name1" placeholder="Name"><br/> <button id="getByName">Get ByName</button> </p> <div id="status3" name="status3"></div><br>
分析:IndexedDB是一种灵活且功能强大的数据存储机制,它集合了Dom Storage和Web SQL Database的优点,用于存储大块或复杂结构的数据,提供更大的存储空间,使用起来也比较简单。可以作为Web SQL Database的替代。不太适合静态文件的缓存。
以key-value 的方式存取对象,可以是任何类型值或对象,包括二进制。
可以对对象任何属性生成索引,方便查询。
较大的存储空间,默认推荐250MB(分Host),比Dom Storage的5MB要大得多。
通过数据库的事务(tranction)机制进行数据操作,保证数据一致性。
异步的 API 调用,避免造成等待而影响体验。
临时型
由浏览器自动分配的,但可能被浏览器收回
持久性
需要显示的申请,申请时浏览器会给用户提示,让用户确定。浏览器不会收回(提供用户则不太友好),大小不够用需要再次申请。
File System API给Web App带来了文件系统的功能,Native文件系统的功能在Web App中都有相应的实现。任何需要通过文件来管理数据,或通过文件系统进行数据管理的场景都比较适合。
该文章为 《HTML5缓存机制浅析:移动端Web加载性能优化》 的读书笔记,整理一下自己的收获。
一、HTML5缓存机制介绍
1.1 HTML5应用程序缓存带来的优势
1.2 现有的HTML5缓存机制
等下查下最新的情况。
1.3 目标
二、分析各个缓存机制
2.1 浏览器缓存机制
浏览器缓存机制是指通过HTTP协议头里的 Cache-Control / Expires 和 Last-Modified / ETag 等字段控制文件缓存的机制。
和 DOM Storage, AppCache 等缓存机制,本质一样。 一个在HTTP协议层实现(浏览器缓存),一个在应用层实现。
2.1.1 强缓存
采用 Cache-Control 和 Expires 来控制缓存。 Chrome开发者工具NetWork, 查看为
form disk cache
Cache-Control (HTTP1.1标准中新增的字段)
Cache-Control:max-age=600
表示文件本地缓存有效时间600s,接下来600s内请求这个资源,浏览器不发出HTTP请求,直接从本地缓存拿Expires (HTTP1.0标准中的字段)
Expires: Thu, 10 Nov 2015 08:45:11 GMT
表示在这个时间前,缓存有效2.1.2 协商缓存
HTTP 的状态为 304 表示 协商缓存
Last-Modified 和 If-Modified-Since
ETag 和 If-None-Match
Etag和Last-Modified可根据需求使用一个或两个同时使用。两个同时使用时,只要满足基中一个条件,就认为文件没有更新。
2.1.3 F5 和 Ctrl+F5 的特殊情况
手动刷新页面(F5),浏览器会直接认为缓存已经过期(可能缓存还没有过期),在请求中加上字段:Cache-Control:max-age=0,发包向服务器查询是否有文件是否有更新。
强制刷新页面(Ctrl+F5),浏览器会直接忽略本地的缓存(有缓存也会认为本地没有缓存),在请求中加上字段:Cache-Control:no-cache(或 Pragma:no-cache),发包向服务重新拉取文件。
三、LocalStorage, SessionStorage
一个简单的键值对存储系统,接口简单实用,兼容性也很好。有大小限制,提供5M(不同浏览器可能不同,区分host, cookie 只有 4k)
3.1 LocalStorage和 SessionStorage的用法
3.2 LocalStorage 和 SessionStorage 的区别
LocalStorage 和 SessionStorage 的区别, SessionStorage 在页面选项卡关闭后,就不存在了, 但是 LocalStorage会还存在。但是注意:页面刷新的时候,SessionStorage还是存在的。
比如在 页面刷新后,表单填写的内容还存在,这个时候使用 SessionStorage最有用了。
3.3 总结
分析:Dom Storage给Web提供了一种更录活的数据存储方式,存储空间更大(相对Cookies),用法也比较简单,方便存储服务器或本地的一些临时数据。
四、Web SQL Database存储机制
IOS下,最大支持50M , 系统自带浏览器Safari,默认超过5M的时候,会弹出提示让用户增加大小,不太友好。
微信浏览器下,不会提示。
4.1 简单例子
4.2 分析
五、Application Cache 机制
似乎是为了支持 Web App 离线使用二开发的缓存机制。 缓存 和 浏览器缓存机制类似。
AppCache的原理有两个关键点:manifest属性和 manifest文件。
5.2 分析
分析:AppCache看起来是一种比较好的缓存方法,除了缓存静态资源文件外,也适合构建Web离线 App。在实际使用中有些需要注意的地方,有一些可以说是”坑“。
要更新缓存的文件,需要更新包含它的manifest文件,那怕只加一个空格。常用的方法,是修改manifest文件注释中的版本号。如:# 2012-02-21 v1.0.0;
被缓存的文件,浏览器是先使用,再通过检查manifest文件是否有更新来更新缓存文件。这样缓存文件可能用的不是最新的版本。
在更新缓存过程中,如果有一个文件更新失败,则整个更新会失败。
manifest和引用它的HTML要在相同Host。
manifest文件中的文件列表,如果是相对路径,则是相对manifest文件的相对路径。
manifest也有可能更新出错,导致缓存文件更新失败。
没有缓存的资源在已经缓存的HTML中不能加载,即使有网络。例如:http://appcache-demo.s3-website-us-east-1.amazonaws.com/without-network/。
manifest文件本身不能被缓存,且manifest文件的更新使用的是浏览器缓存机制。所以manifest
文件的Cache-Control缓存时间不能设置太长。 另外,根据官方文档,AppCache已经不推荐使用了,标准也不会再支持。现在主流的浏览器都是还支持AppCache的,以后就不太确定了。
六、 Indexed Database
IndexedDB也是一种数据库的存储机制,但不同于已经不再支持的Web SQL Database。IndexedDB不是传统的关系数据库,可归为NoSQL数据库。IndexedDB又类似于Dom Storage的key-value的存储方式,但功能更强大,且存储空间更大。
6.1 基本使用
6.2 分析
分析:IndexedDB是一种灵活且功能强大的数据存储机制,它集合了Dom Storage和Web SQL Database的优点,用于存储大块或复杂结构的数据,提供更大的存储空间,使用起来也比较简单。可以作为Web SQL Database的替代。不太适合静态文件的缓存。
以key-value 的方式存取对象,可以是任何类型值或对象,包括二进制。
可以对对象任何属性生成索引,方便查询。
较大的存储空间,默认推荐250MB(分Host),比Dom Storage的5MB要大得多。
通过数据库的事务(tranction)机制进行数据操作,保证数据一致性。
异步的 API 调用,避免造成等待而影响体验。
七、File System API
7.1 File System 的优势
7.2 两种文件空间
临时型
持久性
7.3 分析
File System API给Web App带来了文件系统的功能,Native文件系统的功能在Web App中都有相应的实现。任何需要通过文件来管理数据,或通过文件系统进行数据管理的场景都比较适合。
八、参考文章