Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

【20170329】HTML5缓存机制浅析 #86

Closed
2 of 9 tasks
zhongxia245 opened this issue Mar 29, 2017 · 0 comments
Closed
2 of 9 tasks

【20170329】HTML5缓存机制浅析 #86

zhongxia245 opened this issue Mar 29, 2017 · 0 comments

Comments

@zhongxia245
Copy link
Owner

zhongxia245 commented Mar 29, 2017

时间:2017-03-27 18:02:47

该文章为 《HTML5缓存机制浅析:移动端Web加载性能优化》 的读书笔记,整理一下自己的收获。

一、HTML5缓存机制介绍

HTML5是新一代的HTML标准,加入很多新的特性。离线存储(也可称为缓存机制)是其中一个非常重要的特性。HTML5引入的离线存储,这意味着Web应用可进行缓存,并可在没有因特网连接时进行访问。

1.1 HTML5应用程序缓存带来的优势

  1. 离线浏览
  2. 提高加载速度(从缓存中加载)
  3. 减少服务器负载(浏览器将只从服务器下载更新的资源,强缓存的则不发起HTTP请求,协商缓存会发起HTTP,和服务器验证文件是否修改过了)

1.2 现有的HTML5缓存机制

勾选的,表示熟悉

  • 浏览器缓存机制(强缓存,协商缓存)
  • DOM Storgage 存储机制
  • Web SQL DataBase 存储机制 【关系型数据库】
  • Application Cache (App Cache)机制
  • Indexed DataBase (IndexedDB) 【NoSQL】
  • File System API

Web SQL DataBase 官方的标准文档不在推荐使用,将来也不再维护,(2015年12月);

  • 各大桌面浏览器和移动端浏览器都有很好的实现这个接口,兼容性问题不大,底层基本都是 sqlite(正因为这样,作为一个web标准是不可接受的)

    • 经测试,IOS上容量最大支持50M,不过用系统自带的safari,超过5MB,会主动提醒用户是否要增加数据库大小,不友好(不过微信里面不会)
    • 5MB 如果用来存一些日志,还是够的(当然需要记得清理旧的日志)
    • websql 目前是主要的解决方案(腾讯用来做日志记录,方便排查用户的错误反馈)
  • Indexed DataBase 是下一代的客服端结构化数据持久存储反感,目前各大浏览器中也有很好的支持,是未来用来替换 websql 的方案。

等下查下最新的情况。

这里只是官网推荐的比较,具体使用还需要看目前业界上的情况。
比如websql目前还是主流的,indexdb是未来用来替换的,具体需要多久,还需要等等。

1.3 目标

  • 分析各种缓存机制的原理,用法,特点
  • Android移动端Web性能加载优化
  • 如何利用缓存机制提高Web加载性能

二、分析各个缓存机制

先对各个缓存机制有一个大体的了解,然后才能去考虑如何优化。目前对 浏览器缓存和DOM Storage比较熟悉,其他的大体了解,但是没有项目中用过。

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标准中新增的字段)

    1. 控制文件本地缓存的有效时间.
    2. Cache-Control:max-age=600 表示文件本地缓存有效时间600s,接下来600s内请求这个资源,浏览器不发出HTTP请求,直接从本地缓存拿
    3. 是一个相对的时间
  • Expires (HTTP1.0标准中的字段)

    1. Expires: Thu, 10 Nov 2015 08:45:11 GMT 表示在这个时间前,缓存有效
    2. 是一个绝对时间,由于服务器的时间和客服端的时间可能不一致造成缓存问题,因此引入了HTTP1.1的Cache-Control
    3. 优先级小于 Cache-Control

2.1.2 协商缓存

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可根据需求使用一个或两个同时使用。两个同时使用时,只要满足基中一个条件,就认为文件没有更新。

2.1.3 F5 和 Ctrl+F5 的特殊情况

手动刷新页面(F5),浏览器会直接认为缓存已经过期(可能缓存还没有过期),在请求中加上字段:Cache-Control:max-age=0,发包向服务器查询是否有文件是否有更新。

可能存在304协商缓存

强制刷新页面(Ctrl+F5),浏览器会直接忽略本地的缓存(有缓存也会认为本地没有缓存),在请求中加上字段:Cache-Control:no-cache(或 Pragma:no-cache),发包向服务重新拉取文件。

不存在强缓存和协商缓存

三、LocalStorage, SessionStorage

一个简单的键值对存储系统,接口简单实用,兼容性也很好。有大小限制,提供5M(不同浏览器可能不同,区分host, cookie 只有 4k)

3.1 LocalStorage和 SessionStorage的用法

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();
};

3.2 LocalStorage 和 SessionStorage 的区别

LocalStorage 和 SessionStorage 的区别, SessionStorage 在页面选项卡关闭后,就不存在了, 但是 LocalStorage会还存在。但是注意:页面刷新的时候,SessionStorage还是存在的。

比如在 页面刷新后,表单填写的内容还存在,这个时候使用 SessionStorage最有用了。

3.3 总结

分析:Dom Storage给Web提供了一种更录活的数据存储方式,存储空间更大(相对Cookies),用法也比较简单,方便存储服务器或本地的一些临时数据。

  • 适合存储简单数据
  • 如果存结构化数据,可以借住JSON的功能
  • 不适合存储复杂,存储空间要求比较大的数据,还有不适合存静态文件

四、Web SQL Database存储机制

目前用来存储结构化数据还是比较主流的一个选择。 参考 《腾讯开发工程师:前端异常监控到底怎么做》

IOS下,最大支持50M , 系统自带浏览器Safari,默认超过5M的时候,会弹出提示让用户增加大小,不太友好。

微信浏览器下,不会提示。

4.1 简单例子

<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>

4.2 分析

  • 适合存储结构复杂的数据
  • 使用起来相对麻烦点,需要了解SQL语句
  • 不是和做静态文件的存储

五、Application Cache 机制

似乎是为了支持 Web App 离线使用二开发的缓存机制。 缓存 和 浏览器缓存机制类似。

  • 按文件单位进行缓存
  • 通过manifest来控制文件的缓存
  • 大小5M的限制

AppCache的原理有两个关键点:manifest属性和 manifest文件。

  • 缓存文件有更新需要更新 manifest文件 [正常有更新文件就修改mainfest里面的一个版本号]
CACHE MANIFEST
# 2012-02-21 v1.0.0
/theme.css
/logo.gif
/main.js
NETWORK:
login.asp
FALLBACK:
/html/ /offline.html <br>

5.2 分析

分析:AppCache看起来是一种比较好的缓存方法,除了缓存静态资源文件外,也适合构建Web离线 App。在实际使用中有些需要注意的地方,有一些可以说是”坑“。

  1. 要更新缓存的文件,需要更新包含它的manifest文件,那怕只加一个空格。常用的方法,是修改manifest文件注释中的版本号。如:# 2012-02-21 v1.0.0;

  2. 被缓存的文件,浏览器是先使用,再通过检查manifest文件是否有更新来更新缓存文件。这样缓存文件可能用的不是最新的版本。

  3. 在更新缓存过程中,如果有一个文件更新失败,则整个更新会失败。

  4. manifest和引用它的HTML要在相同Host。

  5. manifest文件中的文件列表,如果是相对路径,则是相对manifest文件的相对路径。

  6. manifest也有可能更新出错,导致缓存文件更新失败。

  7. 没有缓存的资源在已经缓存的HTML中不能加载,即使有网络。例如:http://appcache-demo.s3-website-us-east-1.amazonaws.com/without-network/。

  8. manifest文件本身不能被缓存,且manifest文件的更新使用的是浏览器缓存机制。所以manifest

  9. 文件的Cache-Control缓存时间不能设置太长。
    另外,根据官方文档,AppCache已经不推荐使用了,标准也不会再支持。现在主流的浏览器都是还支持AppCache的,以后就不太确定了。

六、 Indexed Database

IndexedDB也是一种数据库的存储机制,但不同于已经不再支持的Web SQL Database。IndexedDB不是传统的关系数据库,可归为NoSQL数据库。IndexedDB又类似于Dom Storage的key-value的存储方式,但功能更强大,且存储空间更大。

  • NoSQL数据库
  • 类似DOM Storage 键值对存储方式

6.1 基本使用

<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>

6.2 分析

分析:IndexedDB是一种灵活且功能强大的数据存储机制,它集合了Dom Storage和Web SQL Database的优点,用于存储大块或复杂结构的数据,提供更大的存储空间,使用起来也比较简单。可以作为Web SQL Database的替代。不太适合静态文件的缓存。

  1. 以key-value 的方式存取对象,可以是任何类型值或对象,包括二进制。

  2. 可以对对象任何属性生成索引,方便查询。

  3. 较大的存储空间,默认推荐250MB(分Host),比Dom Storage的5MB要大得多。

  4. 通过数据库的事务(tranction)机制进行数据操作,保证数据一致性。

  5. 异步的 API 调用,避免造成等待而影响体验。

七、File System API

  • HTML5新加入的存储机制
  • 为 webApp提供了虚拟的文件系统,类似Native App访问本地系统一样

7.1 File System 的优势

  • 满足大块的二进制文件存储
  • 通过预加载资源文件提供性能
  • 可以直接编辑文件

7.2 两种文件空间

  • 临时型

由浏览器自动分配的,但可能被浏览器收回

  • 持久性

需要显示的申请,申请时浏览器会给用户提示,让用户确定。浏览器不会收回(提供用户则不太友好),大小不够用需要再次申请。

7.3 分析

File System API给Web App带来了文件系统的功能,Native文件系统的功能在Web App中都有相应的实现。任何需要通过文件来管理数据,或通过文件系统进行数据管理的场景都比较适合。

八、参考文章

  1. 《HTML5缓存机制浅析:移动端Web加载性能优化》
  2. 《腾讯开发工程师:前端异常监控到底怎么做》
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant