缓存过期后请求:304(Not Modified)
一般浏览器会将缓存记录及缓存文件存在本地 Cache 文件夹中。Android 下 App 如果使用 Webview,缓存的文件记录及文件内容会存在当前 app 的 data 目录中。
分析:Cache-Control 和 Last-Modified 一般用在 Web 的静态资源文件上,如 JS、CSS 和一些图像文件。通过设置资源文件缓存属性,对提高资源文件加载速度,节省流量很有意义,特别是移动网络环境。但问题是:缓存有效时长该如何设置?如果设置太短,就起不到缓存的使用;如果设置的太长,在资源文件有更新时,浏览器如果有缓存,则不能及时取到最新的文件。
Last-Modified 需要向服务器发起查询请求,才能知道资源文件有没有更新。虽然服务器可能返回304告诉没有更新,但也还有一个请求的过程。对于移动网络,这个请求可能是比较耗时的。有一种说法叫“消灭304”,指的就是优化掉304的请求。
抓包发现,带 if-Modified-Since 字段的请求,如果服务器回包304,回包带有 Cache-Control:max-age 或 Expires 字段,文件的缓存有效时间会更新,就是文件的缓存会重新有效。304回包后如果再请求,则又直接使用缓存文件了,不再向服务器查询文件是否更新了,除非新的缓存时间再次过期。
另外,Cache-Control 与 Last-Modified 是浏览器内核的机制,一般都是标准的实现,不能更改或设置。以 XX 浏览器为例,Cache-Control 与 Last-Modified 缓存不能禁用。缓存容量是12MB,不分HOST,过期的缓存会最先被清除。如果都没过期,应该优先清最早的缓存或最快到期的或文件大小最大的;过期缓存也有可能还是有效的,清除缓存会导致资源文件的重新拉取。
还有,XX浏览器,在使用缓存文件时,是没有对缓存文件内容进行校验的,这样缓存文件内容被修改的可能。
分析发现,浏览器的缓存机制还不是非常完美的缓存机制。完美的缓存机制应该是这样的:
1. 缓存文件没更新,尽可能使用缓存,不用和服务器交互;
2. 缓存文件有更新时,第一时间能使用到新的文件;
3. 缓存的文件要保持完整性,不使用被修改过的缓存文件;
4. 缓存的容量大小要能设置或控制,缓存文件不能因为存储空间限制或过期被清除。
以XX浏览器为例,第1、2条不能同时满足,第3、4条都不能满足。
在实际应用中,为了解决 Cache-Control 缓存时长不好设置的问题,以及为了”消灭304“,Web前端采用的方式是:
1. 在要缓存的资源文件名中加上版本号或文件 MD5值字串,如 common.d5d02a02.js,common.v1.js,同时设置 Cache-Control:max-age=31536000,也就是一年。在一年时间内,资源文件如果本地有缓存,就会使用缓存;也就不会有304的回包。
2. 如果资源文件有修改,则更新文件内容,同时修改资源文件名,如 common.v2.js,html页面也会引用新的资源文件名。
通过这种方式,实现了:缓存文件没有更新,则使用缓存;缓存文件有更新,则第一时间使用最新文件的目的。即上面说的第1、2条。第3、4条由于浏览器内部机制,目前还无法满足。
2.2 Dom Storage 存储机制
DOM 存储是一套在 Web Applications 1.0 规范中首次引入的与存储相关的特性的总称,现在已经分离出来,单独发展成为独立的 W3C Web 存储规范。 DOM 存储被设计为用来提供一个更大存储量、更安全、更便捷的存储方法,从而可以代替掉将一些不需要让服务器知道的信息存储到 cookies 里的这种传统方法。
上面一段是对 Dom Storage 存储机制的官方表述。看起来,Dom Storage 机制类似 Cookies,但有一些优势。
Dom Storage 是通过存储字符串的 Key/Value 对来提供的,并提供 5MB (不同浏览器可能不同,分 HOST)的存储空间(Cookies 才 4KB)。另外 Dom Storage 存储的数据在本地,不像 Cookies,每次请求一次页面,Cookies 都会发送给服务器。
DOM Storage 分为 sessionStorage 和 localStorage。localStorage 对象和 sessionStorage 对象使用方法基本相同,它们的区别在于作用的范围不同。sessionStorage 用来存储与页面相关的数据,它在页面关闭后无法使用。而 localStorage 则持久存在,在页面关闭后也可以使用。
Dom Storage 提供了以下的存储接口:
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();
};
sessionStorage 是个全局对象,它维护着在页面会话(page session)期间有效的存储空间。只要浏览器开着,页面会话周期就会一直持续。当页面重新载入(reload)或者被恢复(restores)时,页面会话也是一直存在的。每在新标签或者新窗口中打开一个新页面,都会初始化一个新的会话。
<script type="text/javascript">
// 当页面刷新时,从sessionStorage恢复之前输入的内容
window.onload = function(){
if (window.sessionStorage) {
var name = window.sessionStorage.getItem("name");
if (name != "" || name != null){
document.getElementById("name").value = name;
}
}
};
// 将数据保存到sessionStorage对象中
function saveToStorage() {
if (window.sessionStorage) {
var name = document.getElementById("name").value;
window.sessionStorage.setItem("name", name);
window.location.href="session_storage.html";
}
}
</script>
<p>
You have viewed this page
<span id="count">an untold number of</span>
time(s).
</p>
将上面代码复制到 local_storage.html 的页面中,用浏览器打开,pageLoadCount 的值是1;关闭 PAGE 重新打开,pageLoadCount 的值是2。这是因为第一次的值已经保存了。
<p id="timePara"><button onclick="getDateTime()">Get Date and Time</button></p>
<p><img src="img_logo.gif" width="336" height="69"></p>
<p>Try opening <a href="tryhtml5_html_manifest.htm" target="_blank">this page</a>, then go offline, and reload the page. The script and the image should still work.</p>
</body>
</html>
上面 HTML 文档,引用外部一个 JS 文件和一个 GIF 图片文件,在其 HTML 头中通过 manifest 属性引用了一个 appcache 结尾的文件。
我们在 Google Chrome 浏览器中打开这个 HTML 链接,JS 功能正常,图片也显示正常。禁用网络,关闭浏览器重新打开这个链接,发现 JS 工作正常,图片也显示正常。当然也有可能是浏览缓存起的作用,我们可以在文件的浏览器缓存过期后,禁用网络再试,发现 HTML 页面也是正常的。
通过 Google Chrome 浏览器自带的工具,我们可以查看已经缓存的 AppCache(分 HOST)。
上面截图中的缓存,就是我们刚才打开 HTML 的页面 AppCache。从截图中看,HTML 页面及 HTML 引用的 JS、GIF 图像文件都被缓存了;另外 HTML 头中 manifest 属性引用的 appcache 文件也缓存了。
AppCache 的原理有两个关键点:manifest 属性和 manifest 文件。
HTML 在头中通过 manifest 属性引用 manifest 文件。manifest 文件,就是上面以 appcache 结尾的文件,是一个普通文件文件,列出了需要缓存的文件。
上面截图中的 manifest 文件,就 HTML 代码引用的 manifest 文件。文件比较简单,第一行是关键字,第二、三行就是要缓存的文件路径(相对路径)。这只是最简单的 manifest 文件,完整的还包括其他关键字与内容。引用 manifest 文件的 HTML 和 manifest 文件中列出的要缓存的文件最终都会被浏览器缓存。
完整的 manifest 文件,包括三个 Section,类型 Windows 中 ini 配置文件的 Section,不过不要中括号。
1. CACHE MANIFEST - Files listed under this header will be cached after they are downloaded for the first time
2. NETWORK - Files listed under this header require a connection to the server, and will never be cached
3. FALLBACK - Files listed under this header specifies fallback pages if a page is inaccessible
完整的 manifest 文件,如:
CACHE MANIFEST
# 2012-02-21 v1.0.0
/theme.css
/logo.gif
/main.js
IndexedDB 有个非常强大的功能,就是 index(索引)。它可对 Value 对象中任何属性生成索引,然后可以基于索引进行 Value 对象的快速查询。
要生成索引或支持索引查询数据,需求在首次生成存储对象时,调用接口生成属性的索引。可以同时对对象的多个不同属性创建索引。如下面代码就对name 和 email 两个属性都生成了索引。
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});
生成索引后,就可以基于索引进行数据的查询。
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>";
}
}
}
分析:IndexedDB 是一种灵活且功能强大的数据存储机制,它集合了 Dom Storage 和 Web SQL Database 的优点,用于存储大块或复杂结构的数据,提供更大的存储空间,使用起来也比较简单。可以作为 Web SQL Database 的替代。不太适合静态文件的缓存。
1. 以key-value 的方式存取对象,可以是任何类型值或对象,包括二进制。
2. 可以对对象任何属性生成索引,方便查询。
3. 较大的存储空间,默认推荐250MB(分 HOST),比 Dom Storage 的5MB 要大的多。
4. 通过数据库的事务(tranction)机制进行数据操作,保证数据一致性。
5. 异步的 API 调用,避免造成等待而影响体验。
Android 在4.4开始加入对 IndexedDB 的支持,只需打开允许 JS 执行的开关就好了。
WebView myWebView = (WebView) findViewById(R.id.webview);
WebSettings webSettings = myWebView.getSettings();
webSettings.setJavaScriptEnabled(true);
2.6 File System API
File System API 是 H5 新加入的存储机制。它为 Web App 提供了一个虚拟的文件系统,就像 Native App 访问本地文件系统一样。由于安全性的考虑,这个虚拟文件系统有一定的限制。Web App 在虚拟的文件系统中,可以进行文件(夹)的创建、读、写、删除、遍历等操作。
File System API 也是一种可选的缓存机制,和前面的 SQLDatabase、IndexedDB 和 AppCache 等一样。File System API 有自己的一些特定的优势:
1. 可以满足大块的二进制数据( large binary blobs)存储需求。
2. 可以通过预加载资源文件来提高性能。
3. 可以直接编辑文件。
浏览器给虚拟文件系统提供了两种类型的存储空间:临时的和持久性的。临时的存储空间是由浏览器自动分配的,但可能被浏览器回收;持久性的存储空间需要显示的申请,申请时浏览器会给用户一提示,需要用户进行确认。持久性的存储空间是 WebApp 自己管理,浏览器不会回收,也不会清除内容。持久性的存储空间大小是通过配额来管理的,首次申请时会一个初始的配额,配额用完需要再次申请。
虚拟的文件系统是运行在沙盒中。不同 WebApp 的虚拟文件系统是互相隔离的,虚拟文件系统与本地文件系统也是互相隔离的。
File System API 提供了一组文件与文件夹的操作接口,有同步和异步两个版本,可满足不同的使用场景。下面通过一个文件创建、读、写的例子,演示下简单的功能与用法。
<script type="text/javascript">
//请求临时文件的存储空间
if (window.requestFileSystem) {
window.requestFileSystem(window.TEMPORARY, 5*1024*1024, initFS, errorHandler);
}else{
alert('Sorry! Your browser doesn\'t support the FileSystem API');
}
// Create a new Blob and write it to log.txt.
var blob = new Blob(['Hello, World!'], {type: 'text/plain'});
fileWriter.write(blob);
}, errorHandler);
}
function errorHandler(err){
var msg = 'An error occured: ' + err;
console.log(msg);
};
</script>
将上面代码复制到 file_system_api.html 文件中,用 Google Chrome 浏览器打开(现在 File System API 只有 Chrome 43+、Opera 32+ 以及 Chrome for Android 46+ 这三个浏览器支持)。由于 Google Chrome 禁用了本地 HTML 文件中的 File System API功能,在启动 Chrome 时,要加上”—allow-file-access-from-files“命令行参数。
上面截图,左边是 HTML 运行的结果,右边是 Chrome 开发者工具中看到的 Web 的文件系统。基本上 H5的几种缓存机制的数据都能在这个开发者工具看到,非常方便。
分析:File System API 给 Web App 带来了文件系统的功能,Native 文件系统的功能在 Web App 中都有相应的实现。任何需要通过文件来管理数据,或通过文件系统进行数据管理的场景都比较适合。
到目前,Android 系统的 Webview 还不支持 File System API。
3 移动端 Web 加载性能(缓存)优化
分析完 H5提供的各种缓存机制,回到移动端(针对 Android,可能也适用于 iOS)的场景。现在 Android App 大多嵌入了 Webview 的组件,通过内嵌 Webview 来加载一些H5的运营活动页面或资讯页。这样可充分发挥Web前端的优势:快速开发、发布,灵活上下线。但 Webview 也有一些不可忽视的问题,比较突出的就是加载相对较慢,会相对消耗较多流量。
通过对一些 H5页面进行调试及抓包发现,每次加载一个 H5页面,都会有较多的请求。除了 HTML 主 URL 自身的请求外,HTML外部引用的 JS、CSS、字体文件、图片都是一个独立的 HTTP 请求,每一个请求都串行的(可能有连接复用)。这么多请求串起来,再加上浏览器解析、渲染的时间,Web 整体的加载时间变得较长;请求文件越多,消耗的流量也会越多。我们可综合使用上面说到几种缓存机制,来帮助我们优化 Web 的加载性能。
结论:综合各种缓存机制比较,对于静态文件,如 JS、CSS、字体、图片等,适合通过浏览器缓存机制来进行缓存,通过缓存文件可大幅提升 Web 的加载速度,且节省流量。但也有一些不足:缓存文件需要首次加载后才会产生;浏览器缓存的存储空间有限,缓存有被清除的可能;缓存的文件没有校验。
对于 Web 在本地或服务器获取的数据,可以通过 Dom Storage 和 IndexedDB 进行缓存。也在一定程度上减少和 Server 的交互,提高加载速度,同时节省流量。
当然 Web 的性能优化,还包括选择合适的图片大小,避免 JS 和 CSS 造成的阻塞等。这就需要 Web 前端的同事根据一些规范和一些调试工具进行优化了。