J'Blog

浏览器端缓存方法

浏览器端缓存方法

浏览器缓存(Browser Caching)是浏览器端保存数据用于快速读取或避免重复资源请求的优化机制,有效的缓存使用可以避免重复的网络请求和浏览器快速地读取本地数据,整体上加速网页展示给用户。打开浏览器地调试模式->application左侧。

http缓存

什么是http缓存

http缓存指的是: 当客户端向服务器请求资源时,会先抵达浏览器缓存,如果浏览器有“要请求资源”的副本,就可以直接从浏览器缓存中提取而不是从原始服务器中提取这个资源。

常见的http缓存只能缓存get请求响应的资源,对于其他类型的响应则无能为力

http缓存都是从第二次请求开始的。第一次请求资源时,服务器返回资源,并在respone header头中回传资源的缓存参数;第二次请求时,浏览器判断这些请求参数,命中强缓存就直接200,否则就把请求参数加到request header头中传给服务器,看是否命中协商缓存,命中则返回304,否则服务器会返回新的资源。

http缓存的分类:

根据是否需要重新向服务器发起请求来分类,可分为(强制缓存,协商缓存) 根据是否可以被单个或者多个用户使用来分类,可分为(私有缓存,共享缓存) 强制缓存如果生效,不需要再和服务器发生交互,而协商缓存不管是否生效,都需要与服务端发生交互

1.1、强制缓存

强制缓存在缓存数据未失效的情况下(即Cache-Control的max-age没有过期或者Expires的缓存时间没有过期),那么就会直接使用浏览器的缓存数据,不会再向服务器发送任何请求。强制缓存生效时,http状态码为200。这种方式页面的加载速度是最快的,性能也是很好的,但是在这期间,如果服务器端的资源修改了,页面上是拿不到的,因为它不会再向服务器发请求了。

1.2、协商缓存

当第一次请求时服务器返回的响应头中没有Cache-Control和Expires或者Cache-Control和Expires过期还或者它的属性设置为no-cache时(即不走强缓存),那么浏览器第二次请求时就会与服务器进行协商,与服务器端对比判断资源是否进行了修改更新。如果服务器端的资源没有修改,那么就会返回304状态码,告诉浏览器可以使用缓存中的数据,这样就减少了服务器的数据传输压力。如果数据有更新就会返回200状态码,服务器就会返回更新后的资源并且将缓存信息一起返回。跟协商缓存相关的header头属性有(ETag/If-Not-Match 、Last-Modified/If-Modified-Since)请求头和响应头需要成对出现

为什么要使用HTTP缓存 ?

1. 减少了冗余的数据传输,节省了网费。

2. 缓解了服务器的压力, 大大提高了网站的性能

3. 加快了客户端加载网页的速度

如何使用HTTP缓存 ?

一般需要缓存的资源有html页面和其他静态资源:

1、html页面缓存的设置主要是在<head>标签中嵌入<meta>标签,这种方式只对页面有效,对页面上的资源无效

//1.1、html页面禁用缓存的设置如下:
<meta http-equiv="pragma" content="no-cache">
// 仅有IE浏览器才识别的标签,不一定会在请求字段加上Pragma,但的确会让当前页面每次都发新请求
<meta http-equiv="cache-control" content="no-cache">
// 其他主流浏览器识别的标签
<meta http-equiv="expires" content="0">
// 仅有IE浏览器才识别的标签,该方式仅仅作为知会IE缓存时间的标记,你并不能在请求或响应报文中找到Expires字段

//1.2、html设置缓存如下:
<meta http-equiv="Cache-Control" content="max-age=7200" />
// 其他主流浏览器识别的标签
<meta http-equiv="Expires" content="Mon, 20 Aug 2018 23:00:00 GMT" />
// 仅有IE浏览器才识别的标签

2、静态资源的缓存一般是在web服务器上配置的,常用的web服务器有:nginx、apache。

websql

websql这种方式只有较新的chrome浏览器支持,并以一个独立规范形式出现,主要有以下特点

- Web Sql 数据库API 实际上不是HTML5规范的组成部分;

- 在HTML5之前就已经存在了,是单独的规范;

- 它是将数据以数据库的形式存储在客户端,根据需求去读取;

- 跟Storage的区别是: Storage和Cookie都是以键值对的形式存在的;

- Web Sql 更方便于检索,允许sql语句查询;

- 让浏览器实现小型数据库存储功能;

- 这个数据库是集成在浏览器里面的,目前主流浏览器基本都已支持;

websql API主要包含三个核心方法:

- openDatabase : 这个方法使用现有数据库或创建新数据库创建数据库对象。

- transaction : 这个方法允许我们根据情况控制事务提交或回滚。

- executeSql : 这个方法用于执行真实的SQL查询。

openDatabase方法可以打开已经存在的数据库,不存在则创建

var db = openDatabase('mydatabase', '2.0', my db', 2 * 1024);

openDatabasek中五个参数分别为:数据库名、版本号、描述、数据库大小、创建回调。创建回调没有也可以创建数据库

database.transaction() 函数用来查询,executeSql()用于执行sql语句

例如在mydatabase数据库中创建表t1:

  var db = openDatabase(' mydatabase ', '1.0', 'Test DB', 2 * 1024 * 1024);  
   db.transaction(function (tx) {    
      tx.executeSql('CREATE TABLE IF NOT EXISTS t1 (id unique, log)');  
   });

插入操作

var db = openDatabase('mydatabase', '2.0', my db', 2 * 1024);

   db.transaction(function (tx) { 
      tx.executeSql('CREATE TABLE IF NOT EXISTS t1 (id unique, log)');  
      tx.executeSql('INSERT INTO t1 (id, log) VALUES (1, "foobar")');  
      tx.executeSql('INSERT INTO t1 (id, log) VALUES (2, "logmsg")');  
   });

在插入新记录时,我们还可以传递动态值,如:

   var db = openDatabase(' mydatabase ', '2.0', 'my db', 2 * 1024);  
   db.transaction(function (tx) {    
     tx.executeSql('CREATE TABLE IF NOT EXISTS t1 (id unique, log)');  
     tx.executeSql('INSERT INTO t1 (id,log) VALUES (?, ?'), [e_id, e_log];  //e_id和e_log是外部变量
   });

读操作,如果要读取已经存在的记录,我们使用一个回调捕获结果:

   var db = openDatabase(mydatabase, '2.0', 'my db', 2*1024);     
	  db.transaction(function (tx) { 
      tx.executeSql('CREATE TABLE IF NOT EXISTS t1 (id unique, log)');  
      tx.executeSql('INSERT INTO t1 (id, log) VALUES (1, "foobar")');  
      tx.executeSql('INSERT INTO t1 (id, log) VALUES (2, "logmsg")');  
   });  
   db.transaction(function (tx) { 
      tx.executeSql('SELECT * FROM t1', [], function (tx, results) { 
      var len = results.rows.length, i;  
      msg = "<p>Found rows: " + len + "</p>";  
      document.querySelector('#status').innerHTML +=  msg;  
      for (i = 0; i < len; i++){ 
         alert(results.rows.item(i).log );  
      } 
    }, null);  
   });

indexDB

通俗地讲,IndexedDB 就是浏览器提供的本地数据库,它可以被网页脚本创建和操作。IndexedDB 允许储存大量数据,提供查找接口,还能建立索引。这些都是 LocalStorage 所不具备的。就数据库类型而言,IndexedDB 不属于关系型数据库(不支持 SQL 查询语句),更接近 NoSQL 数据库。

操作步骤

1. 创建/打开数据库。

2. 在数据库中创建一个对象仓库(object store)。

3. 启动一个事务,并发送一个请求来执行一些数据库操作,像增加或提取数据等。

4. 通过监听正确类型的 DOM 事件以等待操作完成。

5. 在操作结果上进行一些操作(可以在 request 对象中找到)

打开数据库

var db = null;
var request = window.indexedDB.open("MyTestDatabase");
request.onerror = function(event) {
  // 错误处理
  console.log(' 打开数据库报错');
};
request.onsuccess = function(event) {
  // 成功处理
  db = event.target.result;
  console.log('打开数据库成功');
};

创建和更新数据库版本号

如果指定的版本号,大于数据库的实际版本号,就会发生数据库升级事件upgradeneeded。这时通过事件对象的target.result属性,拿到数据库实例。

var db = null;
request.onupgradeneeded = function (event) {
  db = event.target.result;
}

新建数据库

新建数据库与打开数据库是同一个操作。如果指定的数据库不存在,就会新建。不同之处在于,后续的操作主要在upgradeneeded事件的监听函数里面完成,因为这时版本从无到有,所以会触发这个事件。

通常,新建数据库以后,第一件事是新建对象仓库(即新建表)。

request.onupgradeneeded = function(event) {
  db = event.target.result;
  var objectStore = null;
  if (!db.objectStoreNames.contains('imgLists')) {
    objectStore = db.createObjectStore('imgLists', { keyPath: 'id' });
    // unique name可能会重复
    objectStore.createIndex('name', 'name', { unique: false });
  }
}
//创建一张叫imgLists的表格,主键是id。

写入数据

新增数据指的是向对象仓库写入数据记录。这需要通过事务完成。

// new 一个blob对象
var obj1 = {hello: "world"};
var blob = new Blob([JSON.stringify(obj1, null, 2)], {type : 'application/json'});

function add() {
  var request = db.transaction(['imgLists'],  'readwrite')
    .objectStore('imgLists')
    .add({ id: 1, name: '图片1', path: '/static/image', blob:  blob});

  request.onsuccess = function (event) {
    console.log('数据写入成功');
  };

  request.onerror = function (event) {
    console.log('数据写入失败');
  }
}

查询数据

查询数据也是通过事物完成。

function read() {
   var transaction = db.transaction(['imgLists']);
   var objectStore = transaction.objectStore('imgLists');
   // 用户读取数据,参数是主键
   var request = objectStore.get(1);

   request.onerror = function(event) {
     console.log('事务失败');
   };

   request.onsuccess = function( event) {
      if (request.result) {
        console.log(request.result);
      } else {
        console.log('未获得数据记录');
      }
   };
}

遍历数据

遍历数据表格的所有记录,要使用指针对象 IDBCursor。

function readAll() {
  var objectStore = db.transaction('imgLists').objectStore('imgLists');

   objectStore.openCursor().onsuccess = function (event) {
     var cursor = event.target.result;

     if (cursor) {
       console.log(cursor);
       cursor.continue();
    } else {
      console.log('没有更多数据了!');
    }
  };
}

更新数据

function update() {
  var request = db.transaction(['imgLists'], 'readwrite')
    .objectStore('imgLists')
    // 主动更新主键为1
    .put({ id: 1, name: '图片2',  path: '/static/image2'});

  request.onsuccess = function (event) {
    console.log('数据更新成功');
  };

  request.onerror = function (event) {
    console.log('数据更新失败');
  }
}

删除数据

function remove() {
  var request = db.transaction(['imgLists'], 'readwrite')
    .objectStore('imgLists')
    .delete(1);

  request.onsuccess = function (event) {
    console.log('数据删除成功');
  };
}

remove();

创建/使用索引

索引的意义在于,可以让你搜索任意字段,也就是说从任意字段拿到数据记录。如果不建立索引,默认只能搜索主键(即从主键取值)。

objectStore.createIndex('name', 'name', { unique: false });
    function findIndex() {
      var transaction = db.transaction(['imgLists'], 'readonly');
      var store = transaction.objectStore('imgLists');
      var index = store.index('name');
      var request = index.get('图片1');

      request.onsuccess = function (e) {
        var result = e.target.result;
        if (result) {
          console.log(result);
        } else {
          // ...
        }
      }
    }

cookie

Cookie(或者Cookies),指一般网站为了辨别用户身份、进行session跟踪而储存在用户本地终端上的数据(通常经过加密)。cookie一般通过http请求中在头部一起发送到服务器端。一条cookie记录主要由键、值、域、过期时间、大小组成,一般用户保存用户的认证信息。cookie最大长度和域名个数由不同浏览器决定

不同域名之间的cookie信息是独立的,如果需要设置共享可以在服务器端设置cookie的path和domain来实现共享。浏览器端也可以通过document.cookie来获取cookie,并通过js浏览器端也可以方便地读取/设置cookie的值。

localstorage

localStorage是html5的一种新的本地缓存方案,目前用的比较多,一般用来存储ajax返回的数据,加快下次页面打开时的渲染速度。

//localStorage核心API:
localStorage.setItem(key, value)	//设置记录
localStorage.getItem(key)		//获取记录
localStorage.removeItem(key)		//删除该域名下单条记录
localStorage.clear()			//删除该域名下所有记录

值得注意的是,localstorage大小有限制,不适合存放过多的数据,如果数据存放超过最大限制会报错,并移除最先保存的数据。

sessionstorage

sessionStorage和localstorage类似,但是浏览器关闭则会全部删除,api和localstorage相同,实际项目中使用较少。

application cache

application cahce是将大部分图片资源、js、css等静态资源放在manifest文件配置中。当页面打开时通过manifest文件来读取本地文件或是请求服务器文件。HTML5 使用ApplicationCache 接口可以解决由离线带来的部分难题。前提是你需要访问的web页面至少被在线访问过一次。

使用缓存接口可为您的应用带来以下三个优势:

- 离线浏览 – 用户可在离线时浏览您的完整网站

- 速度 – 缓存资源为本地资源,因此加载速度较快。

- 服务器负载更少 – 浏览器只会从发生了更改的服务器下载资源。

一个简单的离线页面主要包含以下几个部分:

index.html

<html manifest="clock.manifest">
  <head>
    <title>AppCache Test</title>
    <link rel="stylesheet" href="clock.css">
    <script src="clock.js"></script>
  </head>
  <body>
    <p><output id="clock"></output></p>
    <div id="log"></div>
  </body>
</html>

clock.mainfest

CACHE MANIFEST
#VERSION 1.0
CACHE:
clock.css
clock.js

clock.js和clock.css为独立的另外文件。   另外需要注意的是更新缓存。在程序中,你可以通过window.applicationCache 对象来访问浏览器的app cache。你可以查看 status 属性来获取cache的当前状态:

var appCache = window.applicationCache;
switch (appCache.status) {
  case appCache.UNCACHED: // UNCACHED == 0
    return 'UNCACHED';
    break;
  case appCache.IDLE: // IDLE == 1
    return 'IDLE';
    break;
  case appCache.CHECKING: // CHECKING == 2
    return 'CHECKING';
    break;
  case appCache.DOWNLOADING: // DOWNLOADING == 3
    return 'DOWNLOADING';
    break;
  case appCache.UPDATEREADY:  // UPDATEREADY == 4
    return 'UPDATEREADY';
    break;
  case appCache.OBSOLETE: // OBSOLETE == 5
    return 'OBSOLETE';
    break;
  default:
    return 'UKNOWN CACHE STATUS';
    break;
};

为了通过编程更新cache,首先调用 applicationCache.update()。这将会试图更新用户的 cache(要求manifest文件已经改变)。最后,当 applicationCache.status 处于 UPDATEREADY 状态时, 调用applicationCache.swapCache(),旧的cache就会被置换成新的。

var appCache = window.applicationCache;
appCache.update(); // Attempt to update the user’s cache.
if (appCache.status == window.applicationCache.UPDATEREADY) {
  appCache.swapCache();  // The fetch was successful, swap in the new cache.
}

这里是通过更新menifest文件来控制其它文件更新的。

cacheStorage

CacheStorage是在ServiceWorker的规范中定义的。CacheStorage 可以保存每个serverWorker申明的cache对象,cacheStorage有open、match、has、delete、keys五个核心方法,可以对cache对象的不同匹配进行不同的响应。

cacheStorage.has() 如果包含cache对象,则返回一个promise对象。 cacheStorage.open() 打开一个cache对象,则返回一个promise对象。 cacheStorage.delete() 删除cache对象,成功则返回一个promise对象,否则返回false。 cacheStorage.keys() 含有keys中字符串的任意一个,则返回一个promise对象。 cacheStorage.delete() 匹配key中含有该字符串的cache对象,返回一个promise对象。

caches.has('v1').then(function() {
  caches.open('v1').then(function(cache) {
      return cache.addAll(myAssets);
  });
}).catch(function() {
  someCacheSetupfunction();
});;
var response;
var cachedResponse = caches.match(event.request).catch(function() {
  return fetch(event.request);
}).then(function(r) {
  response = r;
  caches.open('v1').then(function(cache) {
    cache.put(event.request, response);
  });  
  return response.clone();
}).catch(function() {
  return caches.match('/sw-test/gallery/myLittleVader.jpg');
});
then.addEventListener('activate', function(event) {
  var cacheWhitelist = ['v2'];

  event.waitUntil(
    caches.keys().then(function(keyList) {
      return Promise.all(keyList.map(function(key) {
        if (cacheWhitelist.indexOf(key) === -1) {
          return caches.delete(keyList[i]);
        }
      });
    })
  );
});