Web 应用已经具备了像保存大型数据集和二进制文件之类的离线功能。您甚至可以做一些事情,例如 缓存 MP3 文件。浏览器技术可以离线存储大量数据。但是,问题在于,用于实现此目的的技术选择是碎片化的。
localStorage
为您提供了非常基本的数据存储,但它速度很慢,无法处理二进制 Blob。IndexedDB
和 WebSQL
是异步的、快速的,并且支持大型数据集,但它们的 API 并不直观。即使这样,IndexedDB
和 WebSQL
也并非所有主要浏览器供应商都支持,而且这种情况在短期内似乎不会改变。
如果您需要编写一个支持离线功能的 Web 应用,并且不知道从哪里开始,那么这篇文章适合您。如果您曾经尝试过开始使用离线支持,但它让您头晕眼花,那么这篇文章也适合您。Mozilla 创建了一个名为 localForage 的库,它使在任何浏览器中离线存储数据变得更加轻松。
around 是一款我编写的 HTML5 Foursquare 客户端,它帮助我克服了离线存储的一些痛点。我们仍然将逐步介绍如何使用 localForage,但对于那些喜欢通过查看代码来学习的人来说,这里有一些源代码。
localForage 是一个 JavaScript 库,它使用非常简单的 localStorage API。localStorage
本质上为您提供了 get、set、remove、clear 和 length 的功能,但它还添加了
- 带有回调的异步 API
IndexedDB
、WebSQL
和localStorage
驱动程序(自动管理;将为您加载最佳驱动程序)Blob
和任意类型支持,因此您可以存储图像、文件等。- 对 ES6 Promise 的支持
包含 IndexedDB
和 WebSQL
支持,使您可以为 Web 应用存储比单独使用 localStorage
更多的数据。它们的 API 的非阻塞特性通过不在 get/set 调用上挂起主线程来提高应用速度。对 promise 的支持使编写没有回调堆的 JavaScript 成为一种乐趣。当然,如果您喜欢回调,localForage 也支持回调。
说够了,给我看看它怎么工作吧!
传统 localStorage
API 在许多方面实际上是非常不错的;它使用起来很简单,不会强制使用复杂的数据结构,并且不需要任何样板代码。如果您想在一个应用中保存一些配置信息,您只需要编写以下代码:
// Our config values we want to store offline.
var config = {
fullName: document.getElementById('name').getAttribute('value'),
userId: document.getElementById('id').getAttribute('value')
};
// Let's save it for the next time we load the app.
localStorage.setItem('config', JSON.stringify(config));
// The next time we load the app, we can do:
var config = JSON.parse(localStorage.getItem('config'));
请注意,我们需要将值以字符串形式保存在 localStorage
中,因此在与之交互时,我们将转换为/从 JSON 进行转换。
这看起来非常简单,但您会立即注意到 localStorage
的几个问题
-
它是同步的。我们会等到数据从磁盘读取并解析后,无论它有多大。这会降低应用的响应速度。这在移动设备上尤其糟糕;主线程将被暂停,直到数据被获取,这会使您的应用看起来很慢,甚至无响应。
-
它只支持字符串。您注意到我们必须使用
JSON.parse
和JSON.stringify
吗?这是因为localStorage
只支持 JavaScript 字符串的值。没有数字、布尔值、Blob 等。这使得存储数字或数组很麻烦,并且实际上使得存储 Blob 变得不可能(或者至少非常麻烦和缓慢)。
使用 localForage 的更好方法
localForage 通过使用异步 API 但使用 localStorage 的 API 来解决这两个问题。将使用 IndexedDB
与 localForage 比较,以查看同一部分数据
IndexedDB 代码
// IndexedDB.
var db;
var dbName = "dataspace";
var users = [ {id: 1, fullName: 'Matt'}, {id: 2, fullName: 'Bob'} ];
var request = indexedDB.open(dbName, 2);
request.onerror = function(event) {
// Handle errors.
};
request.onupgradeneeded = function(event) {
db = event.target.result;
var objectStore = db.createObjectStore("users", { keyPath: "id" });
objectStore.createIndex("fullName", "fullName", { unique: false });
objectStore.transaction.oncomplete = function(event) {
var userObjectStore = db.transaction("users", "readwrite").objectStore("users");
}
};
// Once the database is created, let's add our user to it...
var transaction = db.transaction(["users"], "readwrite");
// Do something when all the data is added to the database.
transaction.oncomplete = function(event) {
console.log("All done!");
};
transaction.onerror = function(event) {
// Don't forget to handle errors!
};
var objectStore = transaction.objectStore("users");
for (var i in users) {
var request = objectStore.add(users[i]);
request.onsuccess = function(event) {
// Contains our user info.
console.log(event.target.result);
};
}
WebSQL
不会那么冗长,但它仍然需要相当多的样板代码。使用 localForage,您可以编写以下代码
localForage 代码
// Save our users.
var users = [ {id: 1, fullName: 'Matt'}, {id: 2, fullName: 'Bob'} ];
localForage.setItem('users', users, function(result) {
console.log(result);
});
这比以前少了一些工作。
除字符串以外的数据
假设您想下载用户的个人资料图片以供您的应用使用,并将其缓存以供离线使用。使用 localForage 保存二进制数据很容易
// We'll download the user's photo with AJAX.
var request = new XMLHttpRequest();
// Let's get the first user's photo.
request.open('GET', "/users/1/profile_picture.jpg", true);
request.responseType = 'arraybuffer';
// When the AJAX state changes, save the photo locally.
request.addEventListener('readystatechange', function() {
if (request.readyState === 4) { // readyState DONE
// We store the binary data as-is; this wouldn't work with localStorage.
localForage.setItem('user_1_photo', request.response, function() {
// Photo has been saved, do whatever happens next!
});
}
});
request.send()
下次我们只需要三行代码就可以从 localForage 中获取照片
localForage.getItem('user_1_photo', function(photo) {
// Create a data URI or something to put the photo in an img tag or similar.
console.log(photo);
});
回调和 promise
如果您不喜欢在代码中使用回调,可以使用 ES6 Promise 而不是 localForage 中的回调参数。让我们从上一个示例中获取该照片,但使用 promise 而不是回调
localForage.getItem('user_1_photo').then(function(photo) {
// Create a data URI or something to put the photo in an
tag or similar.
console.log(photo);
});
诚然,这是一个有点牵强的例子,但 around 有一些 真实代码,如果您有兴趣了解该库在日常使用中的情况。
跨浏览器支持
localForage 支持所有现代浏览器。IndexedDB
在除 Safari 之外的所有现代浏览器中可用(IE 10+、IE Mobile 10+、Firefox 10+、Firefox for Android 25+、Chrome 23+、Chrome for Android 32+ 和 Opera 15+)。同时,Android 浏览器(2.1+)和 Safari 使用 WebSQL
。
在最坏的情况下,localForage 将退回到 localStorage,因此您至少可以离线存储基本数据(虽然不是 blob,而且速度更慢)。它至少会负责自动将您的数据转换为/从 JSON 字符串进行转换,这就是 localStorage 需要数据存储的方式。
在 GitHub 上了解更多关于 localForage 的信息,如果您想看到该库做更多的事情,请提交问题!
关于 Matthew Riley MacPherson
Matthew Riley MacPherson(又名 tofumatt)是一位生活在 Pythonista 世界中的 Rubyist。他来自加拿大,所以您会在他的文章中发现很多奇怪的拼写(比如“colour”或“labour”)。他对漂亮的代码、优质咖啡和非常快的摩托车有着浓厚的兴趣。查看他的 GitHub 代码 或 在 Twitter 上与他谈论摩托车。
Matthew Riley MacPherson 的更多文章...
关于 Robert Nyman [荣誉编辑]
Mozilla Hacks 的技术布道师和编辑。关于 HTML5、JavaScript 和开放网络进行演讲和博客。Robert 是 HTML5 和开放网络的坚定支持者,自 1999 年以来一直从事 Web 前端开发工作 - 在瑞典和纽约市。他经常在 http://robertnyman.com 上写博客,并且喜欢旅行和结识朋友。
关于 Angelina Fabbro
我是一名来自加拿大不列颠哥伦比亚省温哥华的开发者,在 Mozilla 工作,担任 Firefox OS 的技术布道师和开发者倡导者。我喜欢 JavaScript、Web 组件、Node.js、移动应用开发,以及这个我一直喜欢待的地方,叫做万维网。哦,还有,别忘了 Firefox OS。在我的业余时间,我会上唱歌课、玩万智牌、教人编程,以及与科学家合作,以促进程序员与科学家之间更好的互动。
33 条评论