indexedDB入门
localforage
- localStorage局限性:存储容量限制,仅支持字符串,如果是存对象还需要将使用JSON.stringify和JSON.parse方法互相转换;读取都是同步的。大多数情况ok。但如果存储数据比较大,例如一张重要图片base64格式存储了,再读可能会有可感知的延迟时间。
- localforage 是一个js库,通过简单类似locaStorage的异步存储来改进你的 Web 应用程序的离线体验。它能存储多种类型的数据,而不仅仅是字符串。localforage 有一个优雅降级策略,若浏览器不支持 IndexedDB 或 WebSQL,则使用 localStorage;
- API:
indexedDB
- IndexedDB数据库使用key-value键值对储存数据;任何操作都发生在事务(transaction)中,indexedDB API提供了索引(indexes), 表(tables), 指针(cursors)等, 但是所有这些必须是依赖于某种事务的;不使用sql,而是通过索引(index)所产生的指针(cursor)来完成查询操作,从而使你可以遍历到结果;和大多数web存储解决方案相同,indexedDB也遵从同源协议;indexedDB在结果准备好之后通过DOM事件通知用户(success,error);indexedDB是websql的替代品,是非关系型数据库(nosql)的一种;
- indexedDB数据库必须包含名字和版本
- 使用indexedDB的基本模式:
- 打开数据库并且开始一个事务。
- 创建一个 object store。
- 构建一个请求来执行一些数据库操作,像增加或提取数据等。
- 通过监听正确类型的 DOM 事件以等待操作完成。
- 在操作结果上进行一些操作(可以在 request 对象中找到)。
- 打开indexedDB数据库
var dbOpenRequest = window.indexedDB.open(dbName, version);
- 创建数据库的主键和字段
// onupgradeneeded用于数据库首次创建版本,或者window.indexedDB.open传递的新版本(版本要比现在的高) dbOpenRequest.onupgradeneeded = function(event) { var db = event.target.result; db.onerror = function(event) { console.log('数据库打开失败'); }; // 创建一个数据库存储对象 var objectStore = db.createObjectStore(dbname, { keyPath: 'id', autoIncrement: true }); // 定义存储对象的数据项 objectStore.createIndex(indexName, keyPath, {unique: true}); }
- 事务(可以理解为对数据库的操作,而且专指一个序列上的操作)
原子性(Atomicity):事务中的所有操作作为一个整体提交或回滚。
一致性(Consistemcy):事物完成时,数据必须是一致的,以保证数据的无损。
隔离性(Isolation):对数据进行修改的多个事务是彼此隔离的。
持久性(Durability):事务完成之后,它对于系统的影响是永久的,该修改即使出现系统故障也将一直保留。var transaction = db.transaction([dbName], 'readwrite'); // 打开已存储的数据对象 var objectStore = transaction.objectStore(dbName); // 增、删、清空、改 var objectStoreRequest = objectStore.add(); var objectStoreRequest = objectStore.delete(); var objectStoreRequest = objectStore.clear(); var objectStoreRequest = objectStore.put(); // 获取存储的对应键的存储对象 var objectStoreRequest = objectStore.get(id); objectStoreRequest.onsuccess = function(){ // 成功处理 }
- 游标
objectStore.openCursor().onsuccess = function(event) { var cursor = event.target.result; if (cursor) { console.log(cusor) // 继续下一个游标项 cursor.continue(); } else { alert("No more entries!"); } };
- 索引
var index = objectStore.index("name"); index.get(name).onsuccess = function(event) { console.log(event.target.result) }; index.openCursor().onsuccess = function(event) { var cursor = event.target.result; if (cursor) { // cursor.key 是一个 name, 然后 cursor.value 是整个对象。 cursor.continue(); } }; index.openKeyCursor().onsuccess = function(event) { var cursor = event.target.result; if (cursor) { // cursor.key 是一个 name, 然后 cursor.value 是那个主键。 // 没有办法可以得到存储对象的其余部分。 cursor.continue(); } }; // openCursor(val)可以传参数(具体的key或者范围)
- 如果是浏览器主窗体线程开发,同时存储数据结构简单,例如,就存个
true/false
,显然localStorage
上上选;如果数据结构比较复杂,同时对浏览器兼容性没什么要求,可以考虑使用indexedDB;如果是在Service Workers中开发应用,只能使用indexedDB数据存储。 - 局限性:
全球多种语言混合存储。国际化支持不好。需要自己处理
和服务器端数据库同步。你得自己写同步代码
全文搜索
ps(在以下情况下,数据库可能被清除):
用户请求清除数据
浏览器处于隐私模式。最后退出浏览器的时候,数据会被清除
硬盘等存储设备的容量到限
不正确的
不完整的改变 - 写一个demo(参考HTML5 indexedDB前端本地存储数据库实例教程),增加了查询,清空等功能;
<!DOCTYPE html> <html lang="en"> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta http-equiv="X-UA-Compatible" content="ie=edge"> <title>Document</title> </head> <body> <form id="form"> <p>项目名称:<input required name="name" autocomplete="off" required></p> <p>开始时间:<input type="date" value="2017-07-16" name="begin" required></p> <p>预计结束:<input type="date" value="2057-07-16" name="end" required></p> <p>参与人员:<input name="person" placeholder="多人空格分隔" required autocomplete="off"></p> <p>补充说明:<textarea rows="5" placeholder="非必填" name="remark"></textarea></p> <p><input type="submit" value="确定创建"></p> </form> <p>查询:<input type='text' required id='searchVal'><button id='search'>Search</button></p> <button id='clear'>clear</button> <div id="result" class="result"> <table> <thead> <tr> <th>项目名称</th> <th>开始时间</th> <th>结束时间</th> <th>参与人员</th> <th>补充说明</th> <th>操作</th> </tr> </thead> <tbody></tbody> </table> <div id="status" class="status">加载中...</div> </div> <!-- 列表数据模板 --> <script id="tplList" type="text/template"> <tr> <td data-key="name" data-id="$id$" contenteditable="plaintext-only">$name$</td> <td data-key="begin" data-id="$id$" contenteditable="plaintext-only">$begin$</td> <td data-key="end" data-id="$id$" contenteditable="plaintext-only">$end$</td> <td data-key="person" data-id="$id$" contenteditable="plaintext-only">$person$</td> <td data-key="remark" data-id="$id$" contenteditable="plaintext-only">$remark$</td> <td><a href="javascript:" role="button" class="jsListDel" data-id="$id$">删除</a></td> </tr> </script> <script> (function () { // 元素们 var eleForm = document.querySelector('#form'); var eleTbody = document.querySelector('#result tbody'); var eleStatus = document.getElementById('status'); var search = document.getElementById('search'); var searchVal = document.getElementById('searchVal'); var clear = document.getElementById('clear'); // 模板字符内容 var strTplList = document.getElementById('tplList').innerHTML; var logError = function (error) { eleStatus.style.display = 'block'; eleStatus.innerHTML = '<span class="error">'+ error +'</span>'; }, logInfo = function (info) { eleStatus.style.display = 'block'; eleStatus.innerHTML = '<span class="info">'+ info + '</span>'; }; // 简易模板方法 String.prototype.temp = function(obj) { return this.replace(/\$\w+\$/gi, function(matchs) { return obj[matchs.replace(/\$/g, "")] || ''; }); }; // 本演示使用的数据库名称 var dbName = 'project'; // 版本 var version = 1; // 数据库数据结果 var db; // 打开数据库 var DBOpenRequest = window.indexedDB.open(dbName, version); // 如果数据库打开失败 DBOpenRequest.onerror = function(event) { logError('数据库打开失败'); }; DBOpenRequest.onsuccess = function(event) { // 存储数据结果 db = DBOpenRequest.result; // 显示数据 method.show(); }; // 下面事情执行于:数据库首次创建版本,或者window.indexedDB.open传递的新版本(版本数值要比现在的高) DBOpenRequest.onupgradeneeded = function(event) { var db = event.target.result; db.onerror = function(event) { logError('数据库打开失败'); }; // 创建一个数据库存储对象 var objectStore = db.createObjectStore(dbName, { keyPath: 'id', autoIncrement: true }); // 定义存储对象的数据项 objectStore.createIndex('id', 'id', { unique: true }); objectStore.createIndex('name', 'name'); objectStore.createIndex('begin', 'begin'); objectStore.createIndex('end', 'end'); objectStore.createIndex('person', 'person'); objectStore.createIndex('remark', 'remark'); }; search.addEventListener('click', function(){ method.search(searchVal.value); }) clear.addEventListener('click', function(){ method.clear(); }) var method = { add: function (newItem) { var transaction = db.transaction([dbName], "readwrite"); // 打开已经存储的数据对象 var objectStore = transaction.objectStore(dbName); // 添加到数据对象中 var objectStoreRequest = objectStore.add(newItem); objectStoreRequest.onsuccess = function(event) { method.show(); }; }, edit: function (id, data) { // 编辑数据 var transaction = db.transaction([dbName], "readwrite"); // 打开已经存储的数据对象 var objectStore = transaction.objectStore(dbName); // 获取存储的对应键的存储对象 var objectStoreRequest = objectStore.get(id); // 获取成功后替换当前数据 objectStoreRequest.onsuccess = function(event) { // 当前数据 var myRecord = objectStoreRequest.result; // 遍历替换 for (var key in data) { if (typeof myRecord[key] != 'undefined') { myRecord[key] = data[key]; } } // 更新数据库存储数据 objectStore.put(myRecord); }; }, del: function (id) { // 打开已经存储的数据对象 var objectStore = db.transaction([dbName], "readwrite").objectStore(dbName); // 直接删除 var objectStoreRequest = objectStore.delete(id); console.log(objectStoreRequest); // 删除成功后 objectStoreRequest.onsuccess = function() { method.show(); }; }, clear: function(){ var objectStore = db.transaction([dbName], 'readwrite').objectStore(dbName); objectStore.clear(); method.show();lo }, search: function(val){ var objectStore = db.transaction([dbName], 'readwrite').objectStore(dbName); var index = objectStore.index('name'); var htmlProjectList = ''; index.openCursor(val).onsuccess = function(){ var cursor = event.target.result; if (cursor) { htmlProjectList = htmlProjectList + strTplList.temp(cursor.value); // 继续下一个游标项 cursor.continue(); // 如果全部遍历完毕 }else { logInfo(''); eleTbody.innerHTML = htmlProjectList; if (htmlProjectList == '') { logInfo('暂无数据'); } } }; }, show: function () { // 最终要展示的HTML数据 var htmlProjectList = ''; // 打开对象存储,获得游标列表 var objectStore = db.transaction(dbName).objectStore(dbName); objectStore.openCursor().onsuccess = function(event) { var cursor = event.target.result; // 如果游标没有遍历完,继续下面的逻辑 if (cursor) { htmlProjectList = htmlProjectList + strTplList.temp(cursor.value); // 继续下一个游标项 cursor.continue(); // 如果全部遍历完毕 } else { logInfo(''); eleTbody.innerHTML = htmlProjectList; if (htmlProjectList == '') { logInfo('暂无数据'); } } } } }; // 表单提交新增数据 eleForm.addEventListener('submit', function (event) { event.preventDefault(); var formData = {}; [].slice.call(this.querySelectorAll('input,textarea')).forEach(function (ele) { if (ele.name) { formData[ele.name] = ele.value; } }); // 添加新的数据 method.add(formData); this.reset(); }); // 编辑事件 eleTbody.addEventListener('focusout', function (event) { var eleTd = event.target; // 获取id,也就是获得主键 var id = eleTd && eleTd.getAttribute('data-id'); if (!id || !/td/.test(eleTd.tagName)) { return; } // 这是要替换的数据 var data = { id: id * 1 }; // 获得现在的数据 [].slice.call(eleTd.parentElement.querySelectorAll('td[data-key]')).forEach(function (td) { var key = td.getAttribute('data-key'); var value = td.innerText || td.textContent || ''; data[key] = value; }); // 更新本地数据库 method.edit(id, data); }); // 删除事件 eleTbody.addEventListener('click', function (event) { var eleBtn = event.target, id = ''; if (eleBtn && eleBtn.classList.contains('jsListDel') && (id = eleBtn.getAttribute('data-id'))) { method.del(id * 1); event.preventDefault(); } }); })(); </script> </body> </html>