使用 jQuery Mobile 与 HTML5 开发 Web App —— HTML5 Web Storage

绝大多数的软件都需要使用某种具有持久性的方式来存储数据,Web Apps 也不例外,涉及到完整后台的 Web Apps ,可以直接在后台使用 mysql 等数据库来存储数据,但过多的 sql 查询会影响服务器性能,甚至是整个 App 的性能。因此,一些简单有效的存储方式对与 Web Apps 显得很重要,Web Storage 似乎正是为此而设计。下面开始正式介绍 Web Storage 。

一. Web Storage 是什么?

Web Storage 是 HTML5 中用于在客户端存储数据的方法,有两种形式:localStorage(本地存储)和 sessionStorage(会话存储)。这两种方法都允许使用 JavaScript 设置“键值对”,并保存在客户端中,在重新加载页面时读出它们。熟悉 Web 开发的读者会发现,这与 cookie 的机制很相似,但与 cookie 相比,它们有很大的差异:

cookie

cookie 不适合大量数据的存储,因为它们由每个对服务器的请求来传递,这使得 cookie 速度很慢而且效率也不高。

Web Storage

Web Storage 的数据完全存储在客户端,不需要通过浏览器的请求再传输到服务器,这样不但可以存储更多的数据,速度也较快。并且,Web Storage 具有统一的编程接口,使得数据操作更为简便。

二. localStorage(本地存储)和 sessionStorage(会话存储)

上面有提及,Web Storage 有 localStorage 和 sessionStorage 两种形式,它们在功能上是一样的,只是持久性和范围有所不同,具体如下:

1. localStorage

localStorage 方式存储的数据没有时间限制,即使浏览窗口关闭了,数据也会保存下来,这些数据也可用于所有来自同源(即域名,协议和端口均要相同)窗口(或者标签页)的加载,实际开发中用于 Web Apps 的选项设置或用户偏好设置会很有用。

2. sessionStorage

sessionStorage 方式存储的数据实际上存储在窗口对象中,当关闭浏览器窗口后,数据会被删除。由于这些数据是存储于窗口对象中,所以它们对于其他窗口或标签页不可见。实际开发中可以用于记录暂时的状态或排序。

三. 浏览器支持

关于浏览器对 Web Storage 的支持情况需要分开 API 和事件两个方面进行说明

1. API 支持情况

IE8+ ,Chrome 4+ , Firefox 3.5+ , Safari 4+ 和 Opera 10.5+ 对于 Web Storage API 有良好及基本统一的支持,

2. 事件支持情况

相对于 API ,Storage 的事件比较晚才有良好的浏览器支持,具体如下:Firefox 5+ , Safari 5+ , Chrome 12+ , Opera 10.5 + 和 IE9+

四. 使用 Web Storage API

下面会例举一些例子说明如何设置、访问和删除 Web Storage 。例子中会统一使用 localStorage ,但实际上在这些例子中使用 localStorage 或 sessionStorage 并没有区别,不过读者需要注意 sessionStorage 在关闭窗口或标签页后会丢失。

但在之前 Kayo 需要说明一点,对于不同的域(包括子域),Web Storage 的数据存储于不同的区域,并且一个网站只能访问其自身的数据。这也是 localStorage 只能访问同源窗口或标签页的原因。

1. 设置

localStorage.setItem('username', 'Kayo'); // 设置 username 为 Kayo

2. 访问

var username = localStorage.getItem('Kayo'); // 访问 username 并把其键值存储在一个变量 username 中

3. 删除

localStorage.removeItem('username'); // 删除 username 键

以上是标准的 Web Storage 基本操作,实际上还有另外一系列更实用的操作方式,假如开发者的键是有效的 JavaScript token (即没有空格、没有除下划线之外的标点),那么可以使用如下的操作方式:

localStorage.username = 'Kayo'; // 设置 username 为 Kayo
var username = localStorage.username; // 访问 username 并把其键值存储在一个变量 username 中
delete localStorage.username; // 删除 username 键

由于 Web Storage 的操作基于 JavaScript ,因此 Kayo 建议使用第二种操作方式,这样会更加方便并且利于整段 JavaScript 代码的阅读。

除了上面三种基本操作外,Web Storage 还包括一个 length 属性以及以下两个方法:

4. clear()

清空一个域下的全部键值。

5. key(n)

返回一个域下 localStorage 列表或 sessionStorage 列表的第 n 个键的键名。

6. length

返回一个域下 localStorage 列表或 sessionStorage 列表的键的总数。

五. 实例

在说明了 Web Storage 的基本使用后,下面举例说明 Web Storage 的具体使用。Web Storage 虽然相对 cookie 可以存储更大的数据量,但这里的更大只是数据的大小,由于 Web Storage 采用的是“键值对”的存储方式,即一个“键名”对应一个值,这样并不利于处理大量的数据,因此 Web Storage 更适合存储辅助性的数据,尤其是处理大量相同性质的数据时,Web Storage 便显得很不灵活。在例子中,Kayo 会以大家比较熟悉的保存用户留言信息进行说明,即使用 Web Storage 代替大家熟悉的 cookie 记录用户留言信息。

例子中会有一个表单,填入表单的内容并提交,但这里会阻止默认的表单提交动作,改以存储在 localStorage ,为了方便操作,会把设置、访问键值写成独立的函数,具体的情况可以测试完整 Demo (为了简化例子结构,这里只使用 addEventListener 监听事件,请使用 Chrome, Firefox 等现代浏览器浏览 Demo ,下同)。测试方法:填写相关信息,提交表单,刷新页面,观察用户信息部分是否得到保留,为了更容易理解 Web Storage 的使用,例子中并没有制作相应的后台,并且由于阻止了表单提交,提交表单不会出现反馈,因此提交表单后直接刷新页面即可。

下面列出例子中主要的代码。

主要 HTML 结构

<form action="" id="add-comment" name="add-comment" />
 	<h1>发表评论</h1>
 	<div id="userinfo">
 		<div class="item">
 			<span>用户名: </span>
 			<input type="text" name="username" id="username" />
 		</div>
 		<div class="item">
 			<span>E-mail: </span>
 			<input type="email" name="email" id="email" />
 		</div>
 		<div class="item">
 			<span>网址: </span>
 			<input type="text" name="website" id="website" />
 		</div>
 	</div>
 	<div class="item">
 		<span>评论: </span>
 		<textarea name="comment" id="comment" cols="33" rows="8"></textarea>
 	</div>
 	<div id="submit-info">
 		<input type="submit" value="发表">
 	</div>
</form>

JavaScript 代码

var username = document.getElementById('username'),
	email = document.getElementById('email'),
	website = document.getElementById('website'),
	submit = document.getElementById('submit');

// 加载已保存的 localStorage
function loadUserInfo(){
	username.value = localStorage.username;
	email.value = localStorage.email;
	website.value = localStorage.website;
}

// 存储表单数据为 localStorage
function saveUserInfo(){
	localStorage.username = username.value;
 	localStorage.email = email.value;
	localStorage.website = website.value;
}

// 打开页面时加载数据
loadUserInfo();

// 使用存储表单数据为 localStorage 代替表单提交
submit.addEventListener('click', function(e){
	e.preventDefault();
	saveUserInfo();
});

从代码量上看,由于例子比较简单,因此这并不会比 cookie 版的代码量少,但结构却简洁很多,并且操作方式很简单,即使增加更多的信息项也很方便。

除此之外,在本系列文章的第一篇中,Kayo 介绍了一个使用 jQuery Mobile 和 HTML5 开发的作品,其中“设置”的功能也是利用 Web Storage 制作,详情可以浏览第一篇文章的第六部分“作品”。

六. Web Storage 事件

Web Storage 支持一个 "storage" 事件,Web Storage 中的数据被保存后,修改或删除,都会触发 storage 事件。但由于浏览器对于该事件的支持并不完善,因此实际上在大多浏览器中,"storage" 并没有完全按照上面所说的触发。在介绍 "storage" 事件的实际效果时,Kayo 先介绍一下该事件的具体情况。

"storage" 事件会把一系列的属性封装在相应的 event 对象中,用于绑定事件回调函数内部调用,具体的属性如下:

  • key 返回发生变化的 key (即键名)
  • oldValue 返回发生变化的 key 的原值(即改变前的值)
  • newValue 返回发生变化的 key 的新值(即改变后的值)
  • url 返回触发事件的 URL
  • storageArea 返回触发事件的对象

这里需要提醒一点,Storage 的 事件需要在带有域名的目录下才能触发,即需要 Web 服务的支持,直接打开相应的文件并不能触发 Storage 事件,而 Storage 的 API 则无须这样。

接下来对上面的例子进行扩展,监听其中的 Web Storage ,你可以点击下面的 Demo 进行测试。这里问题就会出现了,在 Chrome 22 , Firefox 16 , Safari 5.1 中,直接改变表单中的值并提交是不会触发 "storage" 事件,但当用户打开两个 Demo 标签,改变其中一个标签中表单的值并提交,另一个页面反而会触发事件。事实上在较低版本的这三款浏览器中也会出现这个情况,这似乎是这三款浏览器对于 Web Storage 的处理机制并不完善。也因为这样,Kayo 并不建议在实际开发中利用 "storage" 事件。

监听 storage 事件

function triggerEvent(e){
	var tips = 'key:' + e.key + ', newValue:' + e.newValue + ', oldValue:' + e.oldValue + ', url:' + e.url + ', storageArea:' + e.storageArea;
	alert(tips);
}
 
window.addEventListener('storage', triggerEvent, true);

完整 Demo

七. 不足之处

Web Storage 虽然功能强大并且使用也很方便,但实际上它仍存在一些问题,除了上面有提及的浏览器支持仍存在不足外,安全问题也是开发者必须注意的重要问题。W3C 为开发者列出了以下三点安全问题:

1. DNS 欺骗攻击 (DNS spoofing attacks)

因为一些潜在的 DNS 欺骗攻击,Web Storage 无法保证发出请求的脚本所在域是当前的域,这意味着域 A 中嵌入域 B 的脚本,该脚本可以访问域 A 的 Web Storage 。为了尽量减轻这种情况发生,可以使用对页面 TLS 。使用 TLS 可以确保用户或软件代表真实使用者,这样其他页面需要使用能确定它们来自同一域的 TLS 证书才可以访问 storage 。

2. 跨目录攻击 (Cross-directory attacks)

若不同的用户共享使用一个域名,例如几位用户共用 abc.com 域名,所有人都将会共享 abc.com 下的 storage 对象,但没有任何特性可以限制特定访问路径。因此不建议共享域名的用户使用 Web Storage ,这样可以避免域名下的其他用户可以阅读你的 Web Storage 甚至重写它们。

即使可以限制访问的路径,也可以利用 DOM 脚本安全模型轻松地跳过这保护并访问数据。当然,Web Storage 是存储在本地端,如果这些共享域名的用户之间不会接触到其他人的本地端,那这个问题就不用担心了。

3. 实施风险 (Implementation risks)

当使用这些持久化存储特性时,会让有不好意图的网站跨域阅读到这些数据,甚至还会让这些网站写入信息,然后再跨域利用这些信息。

直白的说,让第三方网站写入持久性的数据可能会导致信息泄漏,例如:一个域上的一个用户购买清单可以被另一个域作为定向广告。

最后关于兼容性的问题,由于移动端上的主流浏览器(Android, IOS 的系统浏览器, opera 等现代浏览器)对于 Web Storage 乃至很多 HTML5 的特性支持已经很完善和统一,如果你是利用这项特性开发 Web Apps ,兼容性的问题反而不用过于担心,这也是使用 HTML5 开发 Web Apps 的重要优势。

因此,虽然 Web Storage 仍会存在一些不足,但从综合的角度考虑,Web Storage 已经具有很好的实用价值。利用 Web Storage 保存一些非机密的设置信息无疑是 Web Apps 开发中一个很好的选择。

本文由 Kayo Lee 发表,本文链接:https://kayosite.com/web-app-by-jquery-mobile-and-html5-web-storage.html

评论列表

  • 评论者头像
    回复

    看来都成系列文章了。
    很久没有写html5了。在学校的时候写过而已。
    出来工作了,就没有这么多时间让自己乱搞了。 :mrgreen:

    • 评论者头像
      回复

      @大叔 我也有一段时间没有写 HTML5 了,得慢慢折腾一下才能想起来了!

  • 评论者头像
    回复

    百度一下,技术贴都是这么长。
    弱弱问下,有很多优化的说把JS和CSS调用外部域名,避免cookie上传,这样和把那些文件缓存了效果有什么区别

    • 评论者头像
      回复

      @哼哼猪 哈哈,不长一点无法说明清楚。
      把 JS 和 CSS 独立存放起来,然后进行外部调用可以使到浏览器更快地缓存这些 JS 和 CSS,这样一方面可以加快页面载入,另一方面可以减少当前的文档体积,同时又不会增加 HTTP 请求,这也是 Yslow 优化的规则之一,大型的站点一般都会这样做。避免 cookie 上传具体指的是什么呢?

      • 评论者头像
        回复

        @Kayo 确实,有时候想把问题描述的清楚,但回头看看却越来越长,像文档说明了。
        嗯,我说的cookie 上传是一个http请求,比对本地缓存有没更新,然后使用304缓存。
        这是看到Yslow 优化的,不过有一点我不太明白,如果JS是本网站资源,当然是放在底部的body里面,我们就可以使用GZIP和304缓存,这样就有效减小了JS的体积,理论上网站会响应快点;如果是调用外部资源,那么那个资源就不会被压缩也不会被缓存,貌似又会慢点

        • 评论者头像
          回复

          @哼哼猪 304缓存没有研究过,不是很清楚了。对于外部调用的文件,也可以做GZIP和304缓存,只是需要在外部文件所在的服务器上再做一次,比较麻烦了。

          • 评论者头像
            回复

            @Kayo O(∩_∩)O~,咱们做外部调用也只是调用的新浪谷歌等开放项目的,无服务器权限的。
            等有钱了咱买3台主机,一台放图片,一台放js+css,一台放PHP :mrgreen:

            • 评论者头像
              回复

              @哼哼猪 哈哈,这就成了传送中的个人云平台了! :wink:

              • 评论者头像
                回复

                @Kayo 咨询个问题,今天我网站出现了很多英文的垃圾留言。
                我已经用了防纯英文的代码,但奇怪的是,只有在直接回复是才能拦截,如果是回复他人就无效,有空了帮忙看下咋回事
                $pattern = ‘/[一-龥]/u’;
                if(!preg_match($pattern, $comment_content)) {
                err( __(‘亲,来点能听得懂的吧!’));
                }

                • 评论者头像
                  回复

                  @哼哼猪 看来你是用代码拦截的,我直接用Akismet,虽然也有极少数回复他人的留言无法拦截,但基本上都可以解决垃圾留言的问题了!

                  • 评论者头像
                    回复

                    @Kayo 以前也没发现有,今天可能机器人知道了回复他人拦截无效,就拼命的来,我现在只能关键词屏蔽了。
                    很奇怪,我查看了直接回复和回复他人的输入框ID都一样,就是不知道为何却不行

  • 评论者头像
    回复

    楼主有没有研究过html5+sqlite的存储?我是做后台开发的,不清楚这样做的性能以及安全性如何,但总的看这样可以某种程度上实现客户端的缓存机制:)

    • 评论者头像
      回复

      @timelyxyz 之前有研究过 Web SQL database,就是使用浏览器内嵌的 sqlite 实现的 API。首先兼容性就已经杯具了,Firefox 到现在(27.0.1)都不支持,并且根据 Moliza 反馈,暂时都不打算支持,Opera 在换内核后总算支持了。
      性能方面研究不是很深,但毕竟是内嵌在浏览器内的 sqlite,比起服务器端的数据操作肯定慢不少,根据我之前的测试,执行数据量稍大(几k)的 insert 就已经很不流畅,甚至直接挂掉线程。辛亏可以用 web worker 提升效率,尤其是大数据量时,使用 web worker 后提升很明显。
      安全性方面与传统的服务器端数据库一样,面临着 SQL 注入和 XSS 风险,但无论如何这种方式操作的是本地 sqlite ,个人认为风险还是可以接受的。
      总的来说,设计好性能出色的 SQL 语句以及程序逻辑,把消耗资源大的操作交给 web worker,HTML5 + sqlite 的模式还是值得尝试的!

      • 评论者头像
        回复

        @Kayo 噢,多谢解答!其实我对前端的性能没具体概念,不过我这边的数据量现在还少,性能上还没出现问题,所以先用着js+sqlite来作一些用户数据状态的存储及更新,顺便自己学习以下:mrgreen:
        前段时间发现某大神整理了一套类似后台EntityManager的方法库来管理sql,https://github.com/leotsai/html5sqlite,我这边先玩玩

回复

你正在以游客身份访问网站,请输入你的昵称和 E-mail
:wink: :roll: :oops: :mrgreen: :idea: :cry: :?: :-| :-o :-P :-? :) :( 8-O