Web应用中的离线数据存储

简介: 为了提升Web应用的用户体验,想必很多开发者都会项目中引入离线数据存储机制。可是面对各种各样的离线数据技术,哪一种才是最能满足项目需求的呢?本文将帮助各位找到最合适的那一个。

为了提升Web应用的用户体验,想必很多开发者都会项目中引入离线数据存储机制。可是面对各种各样的离线数据技术,哪一种才是最能满足项目需求的呢?本文将帮助各位找到最合适的那一个。


引言

随着HTML5的到来,各种Web离线数据技术进入了开发人员的视野。诸如AppCache、localStorage、sessionStorage和IndexedDB等等,每一种技术都有它们各自适用的范畴。比如AppCache就比较适合用于离线起动应用,或者在离线状态下使应用的一部分功能照常运行。接下来我将会为大家作详细介绍,并且用一些代码片段来展示如何使用这些技术。


AppCache

如果你的Web应用中有一部分功能(或者整个应用)需要在脱离服务器的情况下使用,那么就可以通过AppCache来让你的用户在离线状态下也能使用。你所需要做的就是创建一个配置文件,在其中指定哪些资源需要被缓存,哪些不需要。此外,还能在其中指定某些联机资源在脱机条件下的替代资源。

AppCache的配置文件通常是一个以.appcache结尾的文本文件(推荐写法)。文件以CACHE MANIFEST开头,包含下列三部分内容:

  • CACHE – 指定了哪些资源在用户第一次访问站点的时候需要被下载并缓存
  • NETWORK – 指定了哪些资源需要在联机条件下才能访问,这些资源从不被缓存
  • FALLBACK – 指定了上述资源在脱机条件下的替代资源


示例

首先,你需要在页面上指定AppCache的配置文件:

<!DOCTYPE html>

<htmlmanifest="manifest.appcache">

...

</html>

在这里千万记得在服务器端发布上述配置文件的时候,需要将MIME类型设置为text/cache-manifest,否则浏览器无法正常解析。

接下来是创建之前定义好的各种资源。我们假定在这个示例中,你开发的是一个交互类站点,用户可以在上面联系别人并且发表评论。用户在离线的状态下依然可以访问网站的静态部分,而联系以及发表评论的页面则会被其它页面替代,无法访问。

好的,我们这就着手定义那些静态资源:

CACHE MANIFEST

CACHE:

/about.html

/portfolio.html

/portfolio_gallery/image_1.jpg

/portfolio_gallery/image_2.jpg

/info.html

/style.css

/main.js

/jquery.min.js

备注:配置文件写起来有一点很不方便。举例来说,如果你想缓存整个目录,你不能直接在CACHE部分使用通配符(*),而是只能在NETWORK部分使用通配符把所有不应该被缓存的资源写出来。

你不需要显式地缓存包含配置文件的页面,因为这个页面会自动被缓存。接下来我们为联系和评论的页面定义FALLBACK部分:

FALLBACK:

/contact.html /offline.html

/comments.html /offline.html

最后我们用一个通配符来阻止其余的资源被缓存:

NETWORK:

*

最后的结果就是下面这样:

CACHE MANIFEST

CACHE:

/about.html

/portfolio.html

/portfolio_gallery/image_1.jpg

/portfolio_gallery/image_2.jpg

/info.html

/style.css

/main.js

/jquery.min.js

FALLBACK:

/contact.html/offline.html

/comments.html/offline.html

NETWORK:

*

还有一件很重要的事情要记得:你的资源只会被缓存一次!也就是说,如果资源更新了,它们不会自动更新,除非你修改了配置文件。所以有一个最佳实践是,在配置文件中增加一项版本号,每次更新资源的时候顺带更新版本号:

CACHE MANIFEST

# version 1

CACHE:

...

LocalStorage 和 SessionStorage

如果你想在Javascript代码里面保存些数据,那么这两个东西就派上用场了。前一个可以保存数据,永远不会过期(expire)。只要是相同的域和端口,所有的页面中都能访问到通过LocalStorage保存的数据。举个简单的例子,你可以用它来保存用户设置,用户可以把他的个人喜好保存在当前使用的电脑上,以后打开应用的时候能够直接加载。后者也能保存数据,但是一旦关闭浏览器窗口(译者注:浏览器窗口,window,如果是多tab浏览器,则此处指代tab)就失效了。而且这些数据不能在不同的浏览器窗口之间共享,即使是在不同的窗口中访问同一个Web应用的其它页面。

备注:有一点需要提醒的是,LocalStorage和SessionStorage里面只能保存基本类型的数据,也就是字符串和数字类型。其它所有的数据可以通过各自的toString()方法转化后保存。如果你想保存一个对象,则需要使用JSON.stringfy方法。(如果这个对象是一个类,你可以复写它默认的toString()方法,这个方法会自动被调用)。


示例

我们不妨来看看之前的例子。在联系人和评论的部分,我们可以随时保存用户输入的东西。这样一来,即使用户不小心关闭了浏览器,之前输入的东西也不会丢失。对于jQuery来说,这个功能是小菜一碟。(注意:表单中每个输入字段都有id,在这里我们就用id来指代具体的字段)

$('#comments-input, .contact-field').on('keyup', function () {

  // let's check if localStorage is supported

  if (window.localStorage) {

     localStorage.setItem($(this).attr('id'), $(this).val());

  }

});

每次提交联系人和评论的表单,我们需要清空缓存的值,我们可以这样处理提交(submit)事件:

$('#comments-form, #contact-form').on('submit', function() {

  // get all of the fields we saved

  $('#comments-input, .contact-field').each(function() {

     // get field's id and remove it from local storage

     localStorage.removeItem($(this).attr('id'));

  });

});

最后,每次加载页面的时候,把缓存的值填充到表单上即可:

// get all of the fields we saved

$('#comments-input, .contact-field').each(function() {

  // get field's id and get it's value from local storage

  var val = localStorage.getItem($(this).attr('id'));

  // if the value exists, set it

  if (val) {

     $(this).val(val);

  }

});

IndexedDB

在我个人看来,这是最有意思的一种技术。它可以保存大量经过索引(indexed)的数据在浏览器端。这样一来,就能在客户端保存复杂对象,大文档等等数据。而且用户可以在离线情况下访问它们。这一特性几乎适用于所有类型的Web应用:如果你写的是邮件客户端,你可以缓存用户的邮件,以供稍后再看;如果你写的是相册类应用,你可以离线保存用户的照片;如果你写的是GPS导航,你可以缓存用户的路线……不胜枚举。

IndexedDB是一个面向对象的数据库。这就意味着在IndexedDB中既不存在表的概念,也没有SQL,数据是以键值对的形式保存的。其中的键既可以是字符串和数字等基础类型,也可以是日期和数组等复杂类型。这个数据库本身构建于存储(store,一个store类似于关系型数据中表的概念)的基础上。数据库中每个值都必须要有对应的键。每个键既可以自动生成,也可以在插入值的时候指定,也可以取自于值中的某个字段。如果你决定使用值中的字段,那么只能向其中添加Javascript对象,因为基础数据类型不像Javascript对象那样有自定义属性。


示例

在这个例子中,我们用一个音乐专辑应用作为示范。不过我并不打算在这里从头到尾展示整个应用,而是把涉及IndexedDB的部分挑出来解释。如果大家对这个Web应用感兴趣的话,文章的后面也提供了源代码的下载。首先,让我们来打开数据库并创建store:

// checkif the indexedDB is supported

if (!window.indexedDB) {

   throw 'IndexedDB is not supported!'; // of course replace that withsomeuser-friendly notification

}

// variable which will hold the databaseconnection

var db;

// open the database

// first argument is database's name, second is it's version (I will talk about versions in a while)

var request = indexedDB.open('album', 1);

request.onerror = function (e) {

   console.log(e);

};

// this will fire when the versionof the database changes

request.onupgradeneeded = function (e) {

   // e.target.result holds the connectiontodatabase

   db = e.target.result;

   // create a store to hold the data

   // first argument is the store's name, second is for options

   // here we specify the field that will serve as the key and also enable the automatic generation of keys with autoIncrement

   var objectStore = db.createObjectStore('cds', { keyPath: 'id', autoIncrement: true });

   // create an index to search cds by title

   // first argument is the index's name, second is the field in the value

   // in the last argument we specify other options, here we only state that the indexisunique, because there can be only one album with specific title

   objectStore.createIndex('title', 'title', { unique: true });

   // create an indextosearch cds by band

   // this one isnotunique, since one band can have several albums

   objectStore.createIndex('band', 'band', { unique: false });

};

相信上面的代码还是相当通俗易懂的。估计你也注意到上述代码中打开数据库时会传入一个版本号,还用到了onupgradeneeded事件。当你以较新的版本打开数据库时就会触发这个事件。如果相应版本的数据库尚不存在,则会触发事件,随后我们就会创建所需的store。接下来我们还创建了两个索引,一个用于标题搜索,一个用于乐队搜索。现在让我们再来看看如何增加和删除专辑:

// adding

$('#add-album').on('click', function () {

   // create the transaction

   // first argument is a list of stores that will be used, second specifies the flag

   // since we want to add something we need write access, so we use readwrite flag

   var transaction = db.transaction([ 'cds' ], 'readwrite');

   transaction.onerror = function (e) {

       console.log(e);

   };

   var value = { ... }; // read from DOM

   // add the album to the store

   var request = transaction.objectStore('cds').add(value);

   request.onsuccess = function (e) {

       // add the album to the UI, e.target.result is a key of the item that was added

   };

});

// removing

$('.remove-album').on('click', function () {

   var transaction = db.transaction([ 'cds' ], 'readwrite');

   var request = transaction.objectStore('cds').delete(/* some id got from DOM, converted to integer */);

   request.onsuccess = function () {

       // remove the album from UI

   }

});

是不是看起来直接明了?这里对数据库所有的操作都基于事务的,只有这样才能保证数据的一致性。现在最后要做的就是展示音乐专辑:

request.onsuccess = function (e) {

   if (!db) db = e.target.result;

   var transaction = db.transaction([ 'cds' ]); // no flag since we are only reading

   var store = transaction.objectStore('cds');

   // open a cursor, which will getall the items fromdatabase

   store.openCursor().onsuccess = function (e) {

       var cursor = e.target.result;

       if (cursor) {

           var value = cursor.value;

           $('#albums-list tbody').append('

'+ value.title +''+ value.band +''+ value.genre +''+ value.year +'

‘); // move to the next item in the cursor cursor.continue(); } }; }

这也不是十分复杂。可以看见,通过使用IndexedDB,可以很轻松的保存复杂对象,也可以通过索引来检索想要的内容:

function getAlbumByBand(band) {

   var transaction = db.transaction([ 'cds' ]);

   var store = transaction.objectStore('cds');

   var index = store.index('band');

   // open a cursortogetonly albums with specified band

   // notice the argument passed to openCursor()

   index.openCursor(IDBKeyRange.only(band)).onsuccess = function (e) {

       var cursor = e.target.result;

       if (cursor) {

           // render the album

           // moveto the next item in the cursor

           cursor.continue();

       }

   });

}

使用索引的时候和使用store一样,也能通过游标(cursor)来遍历。由于同一个索引值名下可能有好几条数据(如果索引不是unique的话),所以这里我们需要用到IDBKeyRange。它能根据指定的函数对结果集进行过滤。这里,我们只想根据指定的乐队进行检索,所以我们用到了only()函数。也能使用其它类似于lowerBound(),upperBound()和bound()等函数,它们的功能也是不言自明的。


总结

可以看见,在Web应用中使用离线数据并不是十分复杂。希望通过阅读这篇文章,各位能够在Web应用中加入离线数据的功能,使得你们的应用更加友好易用。你可以在这里下载所有的源码,尝试一下,或者修修改改,或者用在你们的应用中。

相关文章
|
14天前
|
前端开发 JavaScript 关系型数据库
从前端到后端:构建现代化Web应用的技术探索
在当今互联网时代,Web应用的开发已成为了各行各业不可或缺的一部分。从前端到后端,这篇文章将带你深入探索如何构建现代化的Web应用。我们将介绍多种技术,包括前端开发、后端开发以及各种编程语言(如Java、Python、C、PHP、Go)和数据库,帮助你了解如何利用这些技术构建出高效、安全和可扩展的Web应用。
|
30天前
|
监控 Serverless 测试技术
Serverless 应用引擎常见问题之做的web服务计费如何解决
Serverless 应用引擎(Serverless Application Engine, SAE)是一种完全托管的应用平台,它允许开发者无需管理服务器即可构建和部署应用。以下是Serverless 应用引擎使用过程中的一些常见问题及其答案的汇总:
401 3
|
1月前
|
前端开发 数据库 UED
构建高性能Web应用的关键技术
本文将介绍构建高性能Web应用的关键技术,包括前端优化、后端优化、数据库优化等方面。通过深入讨论各项技术的原理和实践方法,帮助开发者们提升Web应用的响应速度和用户体验。
|
7天前
|
缓存 负载均衡 数据库
优化后端性能:提升Web应用响应速度的关键策略
在当今数字化时代,Web应用的性能对于用户体验至关重要。本文探讨了如何通过优化后端架构和技术手段,提升Web应用的响应速度。从数据库优化、缓存机制到异步处理等多个方面进行了深入分析,并提出了一系列实用的优化策略,以帮助开发者更好地应对日益增长的用户访问量和复杂的业务需求。
10 1
|
7天前
|
缓存 监控 数据库
Flask性能优化:打造高性能Web应用
【4月更文挑战第16天】本文介绍了提升Flask应用性能的七大策略:优化代码逻辑,减少数据库查询,使用WSGI服务器(如Gunicorn、uWSGI),启用缓存(如Flask-Caching),优化数据库操作,采用异步处理与并发(如Celery、Sanic),以及持续监控与调优。通过这些手段,开发者能有效优化Flask应用,适应大型或高并发场景,打造高性能的Web服务。
|
8天前
|
数据库 开发者 Python
Python中使用Flask构建简单Web应用的例子
【4月更文挑战第15天】Flask是一个轻量级的Python Web框架,它允许开发者快速搭建Web应用,同时保持代码的简洁和清晰。下面,我们将通过一个简单的例子来展示如何在Python中使用Flask创建一个基本的Web应用。
|
12天前
|
JavaScript 前端开发 API
Vue.js:构建高效且灵活的Web应用的利器
Vue.js:构建高效且灵活的Web应用的利器
|
20天前
|
XML JSON JavaScript
使用JSON和XML:数据交换格式在Java Web开发中的应用
【4月更文挑战第3天】本文比较了JSON和XML在Java Web开发中的应用。JSON是一种轻量级、易读的数据交换格式,适合快速解析和节省空间,常用于API和Web服务。XML则提供更强的灵活性和数据描述能力,适合复杂数据结构。Java有Jackson和Gson等库处理JSON,JAXB和DOM/SAX处理XML。选择格式需根据应用场景和需求。
|
1月前
|
前端开发 JavaScript 安全
深入探索 Qt6 web模块 WebEngineCore:从基础原理到高级应用与技巧
深入探索 Qt6 web模块 WebEngineCore:从基础原理到高级应用与技巧
71 0
|
1月前
|
缓存 监控 应用服务中间件
如何使用负载均衡器提升Python Web应用的性能?
【2月更文挑战第27天】【2月更文挑战第94篇】如何使用负载均衡器提升Python Web应用的性能?