web 數(shù)據(jù)庫
之前的章節(jié)有討論過 web 中的存儲方式,包括傳統(tǒng)的 cookie 和新的 localstorage,這兩種方式實現(xiàn)了 HTML 中的離線存儲,但是存儲方式比較簡單,在有些復(fù)雜的業(yè)務(wù)場景可能不能滿足條件。本章我們介紹一個計算機(jī)中一個重要的學(xué)科數(shù)據(jù)庫,以及它在 HTML5 中的支持。數(shù)據(jù)庫是一個內(nèi)容龐大的知識體系,本章只介紹一些簡單的用法以及它在 HTML 中的適用場景。
1. 適用場景
既然適用 localstorage 也可以做簡單的數(shù)據(jù)存儲,那么為什么還需要適用數(shù)據(jù)庫呢?假設(shè)一個業(yè)務(wù)場景中將所有用戶信息臨時存儲到瀏覽器中,這些信息包括昵稱、姓名、性別等,現(xiàn)在需要搜索出性別是男的所有用戶。如果使用 localstorage 的話,需要將所有的數(shù)據(jù)提取出來,一條條遍歷,得出結(jié)果。這樣的搜索算法的時間復(fù)雜度是 O(n),性能較差。如果使用數(shù)據(jù)庫存儲的話,只需要給性別列加上索引,然后使用 SQL 搜索,時間復(fù)雜度是 O(lgn),性能提升了一個等量級。關(guān)系型數(shù)據(jù)庫的特點是:
- 數(shù)據(jù)模型基于關(guān)系,結(jié)構(gòu)化存儲,完整性約束;
- 支持事務(wù),數(shù)據(jù)一致性;
- 支持 SQL,可以復(fù)雜查詢;
缺點是:
- SQL 解析會影響性能;
- 無法適應(yīng)非結(jié)構(gòu)化存儲;
- 橫向擴(kuò)展代價高;
- 入門門檻較高。
2. Web SQL
Web SQL不是 HTML5 標(biāo)準(zhǔn)中的一部分,它是一個獨立的規(guī)范,引入了 SQL 的 api,關(guān)于 SQL 的語法可以參考第三方的教程,在此不做解釋。Web SQL 有 3 個函數(shù)
2.1 openDatabase
這個函數(shù)用于打開一個數(shù)據(jù)庫,如果數(shù)據(jù)庫不存在就創(chuàng)建。它有 5 個參數(shù),分別表示:
- 數(shù)據(jù)庫名稱;
- 版本號;
- 數(shù)據(jù)庫備注;
- 初始化數(shù)據(jù)大小;
- 創(chuàng)建/打開成功回調(diào)函數(shù)
/**
* 創(chuàng)建數(shù)據(jù)庫 或者此數(shù)據(jù)庫已經(jīng)存在 那么就是打開數(shù)據(jù)庫
* name: 數(shù)據(jù)庫名稱
* version: 版本號
* displayName: 對數(shù)據(jù)庫的描述
* estimatedSize: 設(shè)置數(shù)據(jù)的大小
* creationCallback: 回調(diào)函數(shù)(可省略)
*/
var db = openDatabase("MySql", "1.0", "數(shù)據(jù)庫描述", 1024 * 1024);
2.2 transaction
這個函數(shù)使用事務(wù)執(zhí)行 SQL 語句,它是一個閉包,例如:
dataBase.transaction( function(tx) {
tx.executeSql(
"create table if not exists test (id REAL UNIQUE, name TEXT)",
[],
function(tx,result){ alert('創(chuàng)建test表成功'); },
function(tx, error){ alert('創(chuàng)建test表失敗:' + error.message);
});
});
2.3 executeSql
這個方法用于執(zhí)行 SQL 語句。
tx.executeSql(
"update stu set name = ? where id= ?",
[name, id],
function (tx, result) {
},
function (tx, error) {
alert('更新失敗: ' + error.message);
});
});
3. indexedDB
IndexedDB 是 HTML5 規(guī)范里新出現(xiàn)的瀏覽器里內(nèi)置的數(shù)據(jù)庫。它提供了類似數(shù)據(jù)庫風(fēng)格的數(shù)據(jù)存儲和使用方式。存儲在 IndexedDB 里的數(shù)據(jù)是永久保存,不像 cookies 那樣只是臨時的。IndexedDB 里提供了查詢數(shù)據(jù)的功能,在線和離線模式下都能使用。
3.1 對比 Web SQL
跟 WebSQL 不同的是,IndexedDB 更像是一個 NoSQL 數(shù)據(jù)庫,而 WebSQL 更像是關(guān)系型數(shù)據(jù)庫。
3.2 判斷瀏覽器是否支持
window.indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB;
if(!window.indexedDB){
console.log("你的瀏覽器不支持IndexedDB");
}
3.3 創(chuàng)建庫
使用 open 方法創(chuàng)建數(shù)據(jù)庫。
var request = window.indexedDB.open("testDB", 2);//第一個參數(shù)是數(shù)據(jù)庫的名稱,第二個參數(shù)是數(shù)據(jù)庫的版本號。版本號可以在升級數(shù)據(jù)庫時用來調(diào)整數(shù)據(jù)庫結(jié)構(gòu)和數(shù)據(jù)
request.onsuccess = function(event){
console.log("成功打開DB");
}//成功之后的回調(diào)函數(shù)
3.4 添加數(shù)據(jù)
var transaction = db.transaction(["students"],"readwrite");//先創(chuàng)建事務(wù),具有讀寫權(quán)限
transaction.oncomplete = function(event) {
console.log("Success");
};
transaction.onerror = function(event) {
console.log("Error");
};
var test = transaction.objectStore("test");
test.add({rollNo: rollNo, name: name});//添加數(shù)據(jù)
3.5 查詢
var request = db.transaction(["test"],"readwrite").objectStore("test").get(rollNo);//創(chuàng)建具備讀寫功能的事務(wù)
request.onsuccess = function(event){
console.log("結(jié)果 : "+request.result.name);
};//成功查詢的回調(diào)函數(shù)
3.6 修改
var transaction = db.transaction(["test"],"readwrite");//創(chuàng)建事務(wù)
var objectStore = transaction.objectStore("test");
var request = objectStore.get(rollNo);
request.onsuccess = function(event){
console.log("Updating : "+request.result.name + " to " + name);
request.result.name = name;//修改數(shù)據(jù)
objectStore.put(request.result);//執(zhí)行修改
};
3.7 刪除
//創(chuàng)建事務(wù),并刪除數(shù)據(jù)
db.transaction(["students"],"readwrite").objectStore("students").delete(rollNo);
4. 實際項目應(yīng)用
<!DOCTYPE html>
<html>
<head lang="en">
<meta charset="UTF-8">
<title>離線記事本</title>
<meta name="viewport" content="width=device-width,initial-scale=1">
<link rel="stylesheet" href="http://code.jquery.com/mobile/1.4.5/jquery.mobile-1.4.5.min.css" />
<script src="http://code.jquery.com/jquery-1.11.1.min.js"></script>
<script src="http://code.jquery.com/mobile/1.4.5/jquery.mobile-1.4.5.min.js"></script><!-- 引用jQuery插件 -->
</head>
<script>
var datatable = null;
var db = openDatabase("note", "", "notebook", 1024 * 100);
//初始化函數(shù)方法
function init() {
datatable = document.getElementById("datatable");
showAllData();
}
function removeAllData() {
for(var i = datatable.childNodes.length - 1; i >= 0; i--) {
datatable.removeChild(datatable.childNodes[i]);
}
var tr = document.createElement("tr");
var th1 = document.createElement("th");
var th2 = document.createElement("th");
var th3 = document.createElement("th");
th1.innerHTML = "標(biāo)題";
th2.innerHTML = "內(nèi)容";
th3.innerHTML = "時間";
tr.appendChild(th1);
tr.appendChild(th2);
tr.appendChild(th3);
datatable.appendChild(tr);
}
//顯示數(shù)據(jù)庫中的數(shù)據(jù)
function showData(row) {
var tr = document.createElement("tr");
var td1 = document.createElement("td");
td1.innerHTML = row.title;
var td2 = document.createElement("td");
td2.innerHTML = row.content;
var td3 = document.createElement("td");
var t = new Date();
t.setTime(row.time);
td3.innerHTML = t.toLocaleDateString() + " " + t.toLocaleTimeString();
tr.appendChild(td1);
tr.appendChild(td2);
tr.appendChild(td3);
datatable.appendChild(tr);
}
//顯示所有的數(shù)據(jù)
function showAllData() {
db.transaction(function(tx) {
tx.executeSql("CREATE TABLE IF NOT EXISTS item(title TEXT,content TEXT,time INTEGER)", []);
tx.executeSql("SELECT * FROM item", [], function(tx, rs) {
removeAllData();
for(var i = 0; i < rs.rows.length; i++) {
showData(rs.rows.item(i))
}
})
})
}
//添加一條記事本數(shù)據(jù)
function addData(title, content, time) {
db.transaction(function(tx) {
tx.executeSql("INSERT INTO item VALUES (?,?,?)", [title, content, time], function(tx, rs) {
alert("保存成功!");
},
function(tx, error) {
alert(error.source + "::" + error.message);
}
)
})
}
//點擊保存按鈕
function saveData() {
var title = document.getElementById("name").value;
var content = document.getElementById("memo").value;
var time = new Date().getTime();
addData(title, content, time);
showAllData();
}
</script>
<body onload="init()">
<div data-role="page" id="pageone">
<div data-role="header" data-position="fixed">
<h1>離線記事本</h1>
</div>
<div data-role="main" class="ui-content">
<p align="center">記事</p>
<table data-role="table" class="ui-responsive">
<thead>
<tr>
<th>標(biāo)題:</th>
<th>內(nèi)容:</th>
</tr>
</thead>
<tbody>
<tr>
<td><input type="text" id="name"></td>
<td><input type="text" id="memo"></td>
</tr>
</tbody>
</table>
<button type="submit" onclick="saveData()">保存</button>
<table data-role="table" data-mode="" class="ui-responsive" id="datatable">
</table>
</div>
</div>
</body>
</html>
上述代碼通過使用 websql 實現(xiàn)了一個簡單的離線記事本的功能,數(shù)據(jù)庫中保留 3 個字段,分別是標(biāo)題、內(nèi)容、時間,點擊保存按鈕調(diào)用insert
豫劇將數(shù)據(jù)添加到數(shù)據(jù)庫,然后通過使用 select
語句將數(shù)據(jù)庫中的數(shù)據(jù)展示出來。如果瀏覽器不主動清空數(shù)據(jù)的情況下離線數(shù)據(jù)將會永久保存,這樣的話借助 websql 可以實現(xiàn)與桌面應(yīng)用相差無幾的功能。
5. indexedDB 和 websql 對比
-
訪問限制: indexdb 和 websql 一致,均是在創(chuàng)建數(shù)據(jù)庫的域名下才能訪問,且不能指定訪問域名。
-
存儲時間: 這兩位的存儲時間也是永久,除非用戶清除瀏覽器數(shù)據(jù),可以用作長效的存儲。
-
大小限制: 理論上講,這兩種存儲的方式是沒有大小限制的。然而 indexeddb 的數(shù)據(jù)庫超過50M的時候瀏覽器會彈出確認(rèn),基本上也相當(dāng)于沒有限制了。但是由于不同的瀏覽器的實現(xiàn)有一定的差別,實際使用中需要根據(jù)不同的瀏覽器做相應(yīng)的容量判斷容錯。
-
性能測試: indexeddb 查詢少量數(shù)據(jù)花費差不多 20MS 左右。大量數(shù)據(jù)的情況下,相對耗時會變長一些,但是也就在 30MS 左右,也是相當(dāng)給力了,10W 數(shù)據(jù)+,畢竟 nosql。而 websql 的效率也不錯,10w+ 數(shù)據(jù),簡單查詢一下,只花費了20MS左右。
-
標(biāo)準(zhǔn)規(guī)范: Web SQL 數(shù)據(jù)庫是一個獨立的規(guī)范,因為安全性能等問題,官方現(xiàn)在也已經(jīng)放棄了維護(hù);indexedDB 則屬于 W3C 標(biāo)準(zhǔn)。
6. 小結(jié)
回顧本章,由關(guān)系數(shù)據(jù)庫的優(yōu)缺點及適用場景引申到 HTML5 中的數(shù)據(jù)庫解決方案,以及使用方法,需要注意的是在使用 HTML 數(shù)據(jù)庫的過程中需要檢測瀏覽器是否支持?jǐn)?shù)據(jù)庫。實際開發(fā)項目由于考慮前端數(shù)據(jù)庫的安全性以及性能等問題,如果切實需要使用需要謹(jǐn)慎,畢竟一般項目中數(shù)據(jù)庫保存的都是敏感數(shù)據(jù),即使保存在服務(wù)器中也需要一定的安全加密措施,所以一般前端存儲的都是一些臨時的數(shù)據(jù)。