Web存储是一个规范,也HTML5规范集的一部分。目前,Web存储只是一个草案,但是几个主流的浏览器(甚至是IE8)都已经实现了Web存储,只是命名有些不一样,“本地存储”或“DOM存储”。下面,我将解释什么是Web存储,以及怎样在Web程序或Web站点中使用Web存储。
Web存储诞生之前
在Web应用程序或Web站点中,以无状态的方式工作必须要保存数据和状态。在早期,我们使用缓存Cookies来保存数据。但是,使用Cookies有很多限制和不利因素。比如,对于每一个HTTP请求,只能有4KB的存储量,不能超过这个限制。Web存储就是为了解决此限制而兴起的规范。
什么是Web Storage?
Web存储是一个键/值字典,在浏览器中保存和持久化数据,即使你关闭了浏览器(从这点上看,类似于Cookies机制)。与Cookies机制不同的是,Web存储的数据是供本地浏览器使用,而不是发送到服务器端。而且,在规范中对Web存储的本地磁盘空间没做限制。但是,实际上浏览器会限制Web存储的数量。
Web存储分为两种不同的存储对象:
1、会话存储Session Storage:在窗口中同一网站的任何页面都可以访问它存储的数据。
2、本地存储Local Storage:数据可跨越多个窗口,无视当前会话,被共同访问、使用。
每个Web应用程序/Web站点都有它自己专门的存储。
Web存储API
Web存储API包括下列方法:
length – 存储的键/值对的数量。
key(n) – 返回存储的第N个键。
getItem(key) – 返回键key对应的值。如果值不存在,则返回空null。注意,返回的值是一个字符串。如果你存储的值是整数或布尔型,你需要类型转换。
setItem(key, value) – 把值插入到key键中。
removeItem(key) – 移除某个键对应的值(包含键本身)。如果键不存在,此方法什么也不做。
clear – 清空存储的键/值数据。
下面是本地存储中get和set键值项的例子:
- localStorage.setItem("key", "value);
- var val = localStorage.getItem("key");
复制代码
还可以不使用get和set方法来存取键值数据。你可以直接把存储看做是一个带属性的类,直接访问其属性。
- localStorage.key = "value";
- var val = localStorage.key;
复制代码
一些浏览器把Web存储看做是一个字典(或JavaScript数组),通过数组的序号访问数据:
- localStorage["key"] = "value";
- var val = localStorage["key"];
复制代码
但是,目前序号的访问方式在规范中还没有(浏览器厂家自行定义的,未来在规范中或许会加上)。
Web存储的另一个方面是存储事件。当在Web存储中出现改变时,存储事件被触发。存储事件关联到窗口对象,无论是setItem()方法、removeItem()方法,或者是clear()方法,都会触发该事件。开发者可以为事件写一个接收事件参数的函数:
- if (window.addEventListener) {
- window.addEventListener("storage", handleStorageEvent, false);
- } else {
- // IE9以下的版本
- window.attachEvent("onstorage", handleStorageEvent);
- };
- function handleStorageEvent(eventData) {
- // 做一些工作
- }
复制代码
上面例子中的eventData参数,可以保存下列数据:
key – 要修改的键;
oldValue – 要修改的键对应的旧值;
newValue – 要修改的键对应的新值;
url – 要修改的键的文档的地址;
storageArea – 存储的区域。
实战例子
下面的例子,我将使用本地存储LocalStorage,目的是保存页载入事件,并把它展现给用户:
- <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN"
- "http://www./TR/xhtml1/DTD/xhtml1-transitional.dtd">
- <html xmlns="http://www./1999/xhtml">
- <head runat="server">
- <title></title>
- <script type="text/javascript">
- function pageLoadCounter() {
- if (!localStorage.getItem('loadCounter')) {
- localStorage.setItem('loadCounter', 0);
- }
- localStorage.setItem('loadCounter', parseInt(localStorage.getItem('loadCounter')) + 1);
- document.getElementById('counter').innerHTML = localStorage.getItem('loadCounter');
- }
- </script>
- </head>
- <body onload="pageLoadCounter()">
- <form id="form1">
- <p>You have viewed this page <span id="counter"></span> times.</p>
- <p><input type="button" onclick="localStorage.clear();" value="Clear Storage" /></p>
- </form>
- </body>
- </html>
复制代码
这个例子很简单,但它展示了一些API方法的用法,如getItem()、setItem()和clear()。你可以看到,存储的数据是字符串方式,必须进行类型转换后再使用。
在例子中,我把counter数据转换成整数,使用了parseInt()方法。你可以关闭浏览器或Tab页,然后打开它,查看最后持久化存储的counter数据。
总结
Web存储是一个新规范,它试图对客户端的数据进行保存。在客户端还有其他方法保存数据,比如Cookies机制或使用Google Gears框架内嵌的数据库(它基于SQLite)。
HTML5 离线存储之Web SQL本篇没有考虑异步,多线程及SQL注入WebDatabase 规范中说这份规范不再维护了,原因是同质化(几乎实现者都选择了Sqlite), 且不说这些,单看在HTML5中如何实现离线数据的CRUD,最基本的用法(入门级别) 1,打开数据库 2,创建表 3,新增数据 4,更新数据 5,读取数据 6,删除数据
事实上,关键点在于如何拿到一个可执行SQL语句的上下文, 像创建表,删除表,CRUD操作等仅区别于SQL语句的写法.OK,貌似"SqlHelper"啊,换个名字,dataBaseOperator就它了 executeReader,executeScalar两个方法与executeNonQuery严重同质, 下边的代码产生定义了我们的dataBaseOperator"类",第二行 3-5行则定义打开数据库连接方法,"类方法",效果类似C#中的静态方法,直接类名.方法调用 6-15行则定义executeNonQuery方法,意指查询数据库,与executeReader方法和executeScalar方法同质,均可返回记录集 整个 dataBaseOperator就完整了,很简单,唯一要指出的是,测试以下代码时请选择一个支持HTML5的浏览器!如Google Chrome 1 //TODO;SQL注入 2 function dataBaseOperator() {}; 3 dataBaseOperator.openDatabase = function () { 4 return window.openDatabase("dataBaseUserStories", "1.0", "dataBase used for user stories", 2 * 1024 * 1024); 5 } 6 dataBaseOperator.executeNonQuery = function (sql, parameters, callback) { 7 var db = this.openDatabase(); 8 db.transaction(function (trans) { 9 trans.executeSql(sql, parameters, function (trans, result) { 10 callback(result); 11 }, function (trans, error) { 12 throw error.message; 13 }); 14 }); 15 } 16 dataBaseOperator.executeReader = dataBaseOperator.executeNonQuery; 17 dataBaseOperator.executeScalar = dataBaseOperator.executeNonQuery;
有了"SqlHeper",再看业务处理层(Business Logic Layer) 业务处理类包括了创建表,删除表,新增记录,删除记录以及读取记录,这里没有写更新,实际上先删后增一样滴,即使要写也不复杂 1 function userStoryProvider() { 2 this.createUserStoryTable = function () { 3 dataBaseOperator.executeNonQuery("CREATE TABLE tbUserStories(id integer primary key autoincrement,role,ability,benefit,name,importance,estimate,notes)"); 4 }; 5 this.dropUserStoryTable = function () { 6 dataBaseOperator.executeNonQuery("DROP TABLE tbUserStories"); 7 }; 8 this.addUserStory = function (role, ability, benefit, name, importance, estimate, notes) { 9 dataBaseOperator.executeNonQuery("INSERT INTO tbUserStories(role,ability,benefit,name,importance,estimate,notes) SELECT ?,?,?,?,?,?,?", 10 [role, ability, benefit, name, importance, estimate, notes], function (result) { 11 //alert("rowsAffected:" + result.rowsAffected); 12 }); 13 }; 14 this.removeUserStory = function (id) { 15 dataBaseOperator.executeNonQuery("DELETE FROM tbUserStories WHERE id = ?", [id], function (result) { 16 //alert("rowsAffected:" + result.rowsAffected); 17 }); 18 }; 19 this.loadUserStories = function (callback) { 20 dataBaseOperator.executeReader("SELECT * FROM tbUserStories", [], function (result) { 21 callback(result); 22 }); 23 //result.insertId,result.rowsAffected,result.rows 24 }; 25 }
createUserStoryTable,dropUserStoryTable,addUserStory,removeUserStory又是严重同质,不说了,仅SQL语句不同而已 但loadUserStories与上述四个方法均不同,是因为它把SQLResultSetRowList返回给了调用者,这里仍然是简单的"转发", 页面在使用的时候需要首先创建provider实例(使用类似C#中的类实例上的方法调用) 1 var _userStoryProvider = new userStoryProvider();
之后就可以调用该实例的方法了,仅举个例子,具体代码省去 function loadUserStory() { try { _userStoryProvider.loadUserStories(function (result) { var _userStories = new Array(); for (var i = 0; i < result.rows.length; i++) { var o = result.rows.item(i); var _userStory = new userStory(o.id, o.name, o.role, o.ability, o.benefit, o.importance, o.estimate, o.notes); _userStories.push(_userStory); } //... } catch (error) { alert("_userStoryProvider.loadUserStories:" + error); } }
得到_userStories这个数组后,就没有下文了,是自动创建HTML还是绑定到EXT,发挥想象力吧...继续 userStory是一个自定义的"Model" "类" 1 function userStory(id, name, role, ability, benefit, importance, estimate, notes) { 2 this.id = id; 3 this.name = name; 4 this.role = role; 5 this.ability = ability; 6 this.benefit = benefit; 7 this.importance = importance; 8 this.estimate = estimate; 9 this.notes = notes; 10 };
最后贴出应用的代码,业务相关的代码,不看也罢,谁家与谁家的都不同 1 /* 2 http:///questions/2010892/storing-objects-in- html5-localstorage 3 http://www./TR/webdatabase/#sqlresultset 4 http:///introducing-web-sql-databases/ 5 http:///questions/844885/sqlite-insert-into-with-unique-names-getting-id 6 */ 7 var _userStoryProvider = new userStoryProvider(); 8 $(document).ready(function () { 9 loadUserStory(); 10 11 /* 添加用户故事 */ 12 $("#btnAdd").click(function () { 13 var item = { role: $("#role").val(), ability: $("#ability").val(), benefit: $("#benefit").val(), name: $("#Name").val(), importance: $("#Importance").val(), estimate: $("#Estimate").val(), notes: $("#Notes").val() }; 14 try { 15 _userStoryProvider.addUserStory(item.role, item.ability, item.benefit, item.name, item.importance, item.estimate, item.notes); 16 loadUserStory(); 17 } catch (error) { 18 alert("_userStoryProvider.addUserStory:" + error); 19 } 20 }); 21 22 /* 创建用户故事表 */ 23 $("#btnCreateTable").click(function () { 24 try { 25 _userStoryProvider.createUserStoryTable(); 26 } catch (error) { 27 alert("_userStoryProvider.createUserStoryTable:" + error); 28 } 29 }); 30 31 /* 删除用户故事表 */ 32 $("#btnDropTable").click(function () { 33 try { 34 _userStoryProvider.dropUserStoryTable(); 35 } catch (error) { 36 alert("_userStoryProvider.dropUserStoryTable:" + error); 37 } 38 }); 39 }); 40 41 /* 加载用户故事 */ 42 function loadUserStory() { 43 try { 44 _userStoryProvider.loadUserStories(function (result) { 45 var _userStories = new Array(); 46 for (var i = 0; i < result.rows.length; i++) { 47 var o = result.rows.item(i); 48 var _userStory = new userStory(o.id, o.name, o.role, o.ability, o.benefit, o.importance, o.estimate, o.notes); 49 _userStories.push(_userStory); 50 } 51 52 if (!_userStories) return; 53 var table = document.getElementById("user_story_table"); 54 if (!table) return; 55 var _trs = table.getElementsByTagName("tr"); 56 var _len = _trs.length; 57 for (var i = 0; i < _len; i++) { 58 table.removeChild(_trs[i]); 59 } 60 { 61 var tr = document.createElement("tr"); 62 tr.setAttribute("class", "product_backlog_row header"); 63 { 64 tr.appendChild(CreateTd("id", "id")); 65 tr.appendChild(CreateTd("name", "name")); 66 tr.appendChild(CreateTd("importance", "importance")); 67 tr.appendChild(CreateTd("estimate", "estimate")); 68 tr.appendChild(CreateTd("description", "role")); 69 tr.appendChild(CreateTd("notes", "notes")); 70 tr.appendChild(CreateTd("delete", "delete")); 71 }; 72 table.appendChild(tr); 73 } 74 for (var i = 0; i < _userStories.length; i++) { 75 CreateRow(table, _userStories[i]); 76 } 77 }); 78 } catch (error) { 79 alert("_userStoryProvider.loadUserStories:" + error); 80 } 81 } 82 function CreateRow(table, userStory) { 83 if (!table) return; 84 if (!userStory) return; 85 { 86 var tr = document.createElement("tr"); 87 tr.setAttribute("class", "product_backlog_row"); 88 { 89 tr.appendChild(CreateTd("id", userStory.id)); 90 tr.appendChild(CreateTd("name", userStory.name)); 91 tr.appendChild(CreateTd("importance", userStory.importance)); 92 tr.appendChild(CreateTd("estimate", userStory.estimate)); 93 tr.appendChild(CreateTd("description", userStory.role)); 94 tr.appendChild(CreateTd("notes", userStory.notes)); 95 tr.appendChild(CreateDeleteButton("delete_button", userStory.id)); 96 }; 97 table.appendChild(tr); 98 } 99 } 100 function CreateTd(name, value) { 101 var td = document.createElement("td"); 102 td.setAttribute("class", "user_story " + name); 103 td.innerText = value; 104 return td; 105 }; 106 function CreateDeleteButton(name, id) { 107 var td = document.createElement("td"); 108 td.setAttribute("class", "user_story " + name); 109 /* 删除用户故事 */ 110 td.innerHTML = "<a href=\"###\" title=\"delete\" onclick=\"javascript:_userStoryProvider.removeUserStory(\'" + id + "');removeRow(this);\">>>delete</a>"; 111 return td; 112 } 113 function removeRow(obj) { 114 document.getElementById("user_story_table").deleteRow(obj.parentNode.parentNode.rowIndex); 115 //obj.parentNode.parentNode.removeNode(true); 116 }
有一个小例子,点这里下载(占位,有点小毛病改好就放) 看完代码复习下基本功课 1,WindowDatabase接口,注意openDatabase方法 [Supplemental, NoInterfaceObject] interface WindowDatabase { Database openDatabase(in DOMString name, in DOMString version, in DOMString displayName, in unsigned long estimatedSize, in optional DatabaseCallback creationCallback); }; Window implements WindowDatabase;
[Supplemental, NoInterfaceObject] interface WorkerUtilsDatabase { Database openDatabase(in DOMString name, in DOMString version, in DOMString displayName, in unsigned long estimatedSize, in optional DatabaseCallback creationCallback); DatabaseSync openDatabaseSync(in DOMString name, in DOMString version, in DOMString displayName, in unsigned long estimatedSize, in optional DatabaseCallback creationCallback); }; WorkerUtils implements WorkerUtilsDatabase;
[Callback=FunctionOnly, NoInterfaceObject] interface DatabaseCallback { void handleEvent(in Database database); };
2,SQLTransaction接口,关注executeSql方法 typedef sequence<any> ObjectArray;
interface SQLTransaction { void executeSql(in DOMString sqlStatement, in optional ObjectArray arguments, in optional SQLStatementCallback callback, in optional SQLStatementErrorCallback errorCallback); };
[Callback=FunctionOnly, NoInterfaceObject] interface SQLStatementCallback { void handleEvent(in SQLTransaction transaction, in SQLResultSet resultSet); };
[Callback=FunctionOnly, NoInterfaceObject] interface SQLStatementErrorCallback { boolean handleEvent(in SQLTransaction transaction, in SQLError error); };
3,最后看下SQLResultSetRowList定义 interface SQLResultSetRowList { readonly attribute unsigned long length; getter any item(in unsigned long index); };
和SQLResultSet定义 1 interface SQLResultSet { 2 readonly attribute long insertId; 3 readonly attribute long rowsAffected; 4 readonly attribute SQLResultSetRowList rows; 5 };
类图
 由于window对象实现了WindowDatabase接口,从而可以直接在window对象上调用openDatabase等方法 同时实现WindowDatabse接口的还有WorkerUtils对象,点这里了解
|