Web前端开发学习之路——数据的保存与读取(一)

制作APP时必须考虑数据保存的问题。在保持网络连接的情况下,数据可以通过Ajax方式,利用Http Get或Post昂是访问远程数据库。不过,离线状态下就无法访问远程数据库了。本章将介绍如何使用IndexedDB和Web SQL在本地保存数据以及读取文本文件。

认识IndexedDB

HTML5提供的本地保存功能包括前面介绍过的Web Storage及本地数据库(Indexed Database和Web SQL Database)

Web SQL Database是关系型数据库系统,可以使用SQLite语法访问数据库,Indexed Database(简称IndexedDB)是索引数据库,通过数据键(key)进行访问。目前WebAPP大多数支持Web SQL,而对IndexedDB的兼容性并不理想,但是W3C组织(Web Applications Working Group)在2011年11月18日已经宣布启用Web SQL,建议使用Web Storage和IndexedDB。可想而知,IndexedDB将会继续发展,也许日后将取代Web SQL成为各种浏览器都支持的数据库,因此,用户必须熟悉这两种数据库的使用。

IndexedDB的概念

IndexedDB利用数据键(key)访问,通过索引功能搜索数据,适用于大量的结构化数据,如日历、通讯簿或记事本等。IndexedDB和Web SQL相比,IndexedDB开发的难度比较高,不管是在概念还是在操作上都大不相同,先来看看IndexedDB的几个重要概念

1.以key/value成对保存数据

IndexedDB与Web Storage都是以数值键来保存数据,只要创建索引,就可以进行数据搜索以及排序

2.交易数据库模型(transactional database model)

IndexedDB进行数据库操作之前要先进行交易(transaction)。所谓交易,简单来说就是将数据库所作的访问操作(如新增、删除、修改、查询等)包装成一个任务来执行,这个任务可能包含多个步骤,只有所有步骤执行成果,交易才算成功,只要有一个步骤是爱,整个交易就取消并且交易所作的更改都会被恢复。

3.IndexedDB大部分的异步API

IndexedDB数据库操作并不会立即执行,而是先创建数据库操作要求,然后定义事件处理函数来响应这些要求是成功还是失败。

4.通过监听DOM事件取得执行结果

数据库操作完成时,通过监听DOM事件来取得执行结果,DOM事件的type属性会返回成功或失败

5.每个读写操作都是请求(request)

IndexedDB随时随地都在使用请求,上述的监听DOM事件也是一个请求。

6.面向对象

IndexedDB是面向对象数据库,不使用SQL语法,必须以面向对象的方式来获取数据。

7.NoSQL的数据库系统

IndexedDB的查询语句并非SQL(结构化查询语言,Structured Query Language),而是查询索引获取指针(Cursor),然后用指针访问查询结果

8.同源策略(Same-origin policy)

基于“同源策略”,限制来自相同来源才能访问。

IndexedDB基本操作

要操作IndexedDB数据库,建议遵循以下几个步骤:

  • 打开数据库和交易(transaction)
  • 创建存储对象(objectStore)
  • 对存储对象发出操作请求(request),例如新增或获取数据;
  • 监听DOM事件等待操作完成
  • 从result对象上获取结果进行其他工作

由于IndexedDB的标准仍然在演变中,并不是所有的浏览器都广泛支持,在使用之前可以添加浏览器前缀标识来确定浏览器是否支持,以Gecko为核心的浏览器(例如Firefox)前缀标识为moz;以WebKit为核心的浏览器(例如Chrome)前缀标识为webkit,以MSHTML为核心的浏览器(例如IE)前缀标识为ms。可以利用下列通用于语法进行测试,当不支持时则显示提示信息:

window.indexedDB = window.indexedDB || window.mozIndexedDB ||window.webkitIndexedDB || window.msIndexedDB;
if(!window.indexedB){
    alert("你的浏览器不支持indexedDB");
}

打开数据库

var request = window.indexedDB.open(dbName,dbVersion);
//例如:打开一个名称为MyDatabase、版本编号为3的数据库
var request = window.indexedDB.open('MyDatabase',3)

当数据库结构发生改变时,就必须更新版本号,版本编号更改时会先触发onupgradeneeded事件

request.onupgradeneeded = function(event){
    //更新存储对象和索引的语句
}

onupgradeneeded事件的处理在后面的更新数据库版本部分会有更详细的说明

接下来会触发success事件,当打开数据库成功时,就可以使用request的result属性来取得IndexedDB的IDBDatabase对象

request.onsuccess = function(event){
    var db = request.result;
};

失败时触发的error事件处理函数语法如下:

request.onerror = function(event){
    //失败时执行的语句
};

完整的打开数据程序代码举例如下,供参考:

var request =indexedDB.open("MyDatabase");
request.onerror = function(event){
    alert("IndexedDB打开失败!");
};
request.onsuccess = function(event){
    var db = request.result;
};

创建存储对象(objectStore)

刚才提到在新版本中创建数据库时会触发onupgradeneeded事件,第一次创建数据库时也会触发这个事件。在这个事件处理函数中要创建存储对象,也就是数据库结构,其语法如下:

request.onupgradeneeded = function(event){
    var db = event.target.result;
    //创建 objectStore
    var objectStore = db.createObjectStore("customer",{keyPath:"user_id"});
    objectStore.createIndex("name","name",{unique:false});
    objectStore.createIndex("address","address",{unique:false});
    objectStore.createIndex("by_tel","tel",{unique:false});
};

createObjectStore方法会创建一个存储对象,就好像数据库中的一个数据表,第一个参数是存储对象的名称,另一个是参数对象(可省略)。

参数对象有两个属性,即keyPath和autoIncrement,属性以逗号分隔,例如:

{keyPath:"myKey",autoIncrement:true}
keyPath和autoIncrement属性说明
属性 说明
keyPath 数据键,此存储对象的数据不允许重复,必须是唯一值
autoIncrement

自动编号,类型为布尔值(true或false),默认值为false

当值为true时,标识此存储对象数据由整数1开始,自动累加;值为false表示每次新增数据时自动设置

createObjectStore的createIndex方法会创建索引,create方法有3个参数,分别是索引名称、索引查找目标以及unique,程序代码如下所示:

objectStore.createIndex("title","title",{unique:false});

Unique的值是布尔值(true或false),设置为true表示唯一值,false表示非唯一值,例如,每个人的身份证好吗就不会有重复的数据,unique就可设置为true

新增数据

add方法语句如下:

objectStore.add(value,key);

put方法语句如下:

objectStore.put(value,key);

add方法仅在objectStore中数据键不存在相同数据时有用,如果keypath的值已经存在,put方法会直接更新数据,否则就会新增数据

IndexedDB不适用数据表而时使用对象存盘,一条objectStore中的数据值(value)对应一条数据键(key),每条数据称为一条记录(record)

数据键可以是string、date、float以及array类型,举例如下:

var request = objectStore.add({name:"eileen",adress:"上海市",tel:"021"});

除了一条一条地输入外,还可以采用循环的方式来新增到objectStore,下面看看创建数据库并新增初始值的范例:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
    <title>创建数据库和初始值</title>
    <script src="../jquery-3.3.1.min.js"></script>
    <script>
         $(function () {
            window.indexedDB = window.indexedDB ||  window.mozIndexedDB  ||window.webkitIndexedDB
             ||  window.msIndexedDB;
            if(!window.indexedDB){
                alert("你的浏览器不支持IndexedDB!");
            }
            //要新增的数据array
             const customerData = [
                 {name:"eileen",adress:"上海市",tel:"021"},
                 {name:"brain",adress:"南京市",tel:"025"}
             ];
            //打开数据库
             var req = window.indexedDB.open("MyDatabase");
             req.onsuccess = function (evt) {
                 db = this.result;
                 alert("openDb Done!");
             };
             req.onerror =function (evt) {
                 alert("openDb:",evt.target.errorCode);
             };
             //onupgradeneeded事件
             req.onupgradeneeded =function (event) {
                 var db  =event.target.result;
                 //创建objectStore
                 var objectStore = db.createObjectStore("customer",{keyPath:"user_id",autoIncrement:true});
                 objectStore.createIndex("name","name",{unique:false});
                 objectStore.createIndex("address","address",{unique:false});
                 objectStore.createIndex("by_tel","tel",{unique:false});
                 //数据到objectStore
                 for (var i in customerData){
                     objectStore.add(customerData[i]);
                 }
             };
        })
    </script>
</head>
<body>

</body>
</html>

执行结果如下:

对于本例程,我们建议采用Google Chrome浏览器打开,因为其提供了相当方便的Web Developer Tolols(简称DEV Tools),可以让我们预览IndexedDB的内容,只要在Chrome浏览器中按F12打开DEV Tools,再单击Application标签就能看到IndexedDB项目

创建objectStore之后,就会有数据库进行新增、读取与删除等操作的需求,下面介绍新增、读取与删除操作。

再操作之前必须先进行交易(transaction),交易中要指定objectStore名称和操作权限,其格式如下所示:

var transaction = db.transaction(objectStore Name,操作权限);

操作权限有3种模式:只读模式(readonly)、读写模式(readwrite)、版本升级模式(versionchange)

如果不指定操作权限,默认为readonly。例如,要将数据写入objectStore命名为customer,就必须设为读写交易,如下所示:

var transaction = db.transaction("customer","readwrite");

打开交易(transaction)来获取objectStore,才能新增数据,语法如下:

store = transaction.objectStore("customer");//获取objectStore
request = store.add({name:"Jenny",address:"北京",tel:"001"});//新增数据

新增成功时,request的成功事件(success)会被触发,失败时触发错误(error)事件

request.onsuccess = function(e){...};
request.onerror = function(e){...};

交易完成与失败也会收到相应的事件,包括错误(error)、中断(abort)以及完成(complete)

transaction.oncomplete = function(event) {...};
transaction.onerror = function(event) {...};

下面看一个范例:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta http-equiv="Content-Type" content="text/html;charset=utf-8"/>
    <title>test</title>
    <script src="../jquery-3.3.1.min.js"></script>
    <style>
        div {
            border: 2px dotted #ff0000;
            padding: 5px;
        }
    </style>
    <script>
      var db,str;
      $(function () {
          window.indexedDB = window.indexedDB ||  window.mozIndexedDB  ||window.webkitIndexedDB
             ||  window.msIndexedDB;
            if(!window.indexedDB){
                alert("你的浏览器不支持IndexedDB!");
            }
            //打开数据库
          var req =window.indexedDB.open("MyDatabase");
            req.onsuccess = function (evt) {
                db = this.result;
                str = "MyDatabase创建完成"+">状态:"+this.readyState+">版本:"+db.version+"<br>";
                $("div").html(str);
            };
            req.onerror = function (evt) {
                $("div").html("打开数据库错误:"+evt.target.errorCode);
            };
            //onupgradeneeded事件
          req.onupgradeneeded = function (event) {
              //创建objectStore
              var objectStore = event.target.result.createObjectStore("customer",{keyPath:"user_id"});
              objectStore.createIndex("name","name",{unique:false});
          };
          $("#addbtn").click(function () {
              add_click('add');
          });
          $("#putbtn").click(function () {
              add_click('put');
          });
      })
        //新增数据
        function add_click(add_way) {
            var transaction =db.transaction("customer","readwrite");
            transaction.oncomplete = function (event) {
                str+"交易成功<br>";
                $("div").html(str);
            };
            transaction.onerror = function (event) {
                $("div").html("交易失败");
            };
            store = transaction.objectStore("customer");
            
            if(add_way = "add")
                request = store.add({user_id:$("#user_id").val(),name:$("#name").val()});
            else
                request = store.put({user_id:$("#user_id").val(),name:$("#name").val()});
            request.onsuccess = function (e) {
                str+="新增数据成功<br>";
                $("div").html(str);
            }
            request.onerror = function (e) {
                $("div").html("新增数据失败:"+e.target.error);
            }
        }
    </script>
</head>
<body>
账号:<input type="text" id="user_id"><br/>
姓名:<input type="text" id="name"><br/>
<button id="addbtn">新 增</button>
<button id="putbtn">新增/更新</button>
<div id ="message"></div>
</body>
</html>

在敲代码的时候..切记注意括号不要弄错了...不然真的很头疼...我修改代码花了一个小时...

执行结果如下:

范例中的"新增"按钮使用的是add方法,"新增/更新"按钮用的是put方法,两者有所不同,put既可以新增数据,也可以更新数据,取决于是否为唯一数据键值,而add只能新增,并且不能有重复信息,不然会显示错误信息。

读取数据

IndexedDB提供了get方法,可以读取数据,语法如下:

objectStore.get(key);

例如想要读取上范例中账号为168007300的数据,那么程序代码就可以表示如下:

var request = store.get("168007300");
request.onerror =function(e){
    $("div").html("读取数据失败"+e.target.error)
};
request.onsuccess = function(e){
    str = "账号" + request.result.user_id+"姓名:"+request.result.name;
    $("div").html(str);
};

如果要以姓名来查询账号,必须逐一检查每条数据,效率很低。此时,可以献给姓名创建索引(index),就可以利用姓名来获取账号。语法如下:

var index = store.index("name");
var request = index.get("Joseph");
request.onsuccess  =function(e){
    alert(e.target.result.user_id);
};

删除数据

IndexedDB提供了delete方法来删除数据,语法如下:

objectStore.delete(key);

例如想要把上述范例中账号为168007300的数据删除,那么可以用下面代码表示:

var request = store.delete("168007300");
request.onerror = function(e){
    $("div").html("删除数据失败"+e.target.error)    
};
request.onsuccess =function(e){
    $("div").html("删除成功");
};

调用objectStore的clear方法可以清空objectStore的数据,语法如下:

var transaction = db.transaction(objectStore Name,'readwirte');
var store = transaction.objectStore(objectStore Name);
store.clear();

只要调用deleteDatabase方法就能删除IndexedDB的objectStore,语法如下:

var req = windows.indexeDB.deleteDatabase(dbName);

删除成功时会触发success事件,失败时会触发error事件,语法如下:

req.onsuccess = function(){
$("div").html("删除成功");
};
req.onerror = function(){
$("div").html("删除失败");
};

使用指针对象

get方法只能用key或Index进行读取,然而查询通常不会这么简单,特别是想要查询某个范围这类高级查询时,不要担心,objectStore还可以利用指针对象(cursor)来获取想要的数据,指针对象通过调用openCursor()方法来取得数据,语法如下所示

objectStore.openCursor().onsuccess = function(event){
    var cursor = event.target.result;
    if(cursor){
        alert("账号:"+cursor.key+”姓名:"+cursor.value.name);
        cursor.continue();
    }else{
        alert("已无数据");
    }
};

执行成功时,指针对象会存放在result属性内,也就是上述程序中的event.target.result。指针对象有两个属性:key属性是数据键、value数小时数据值,每次会返回一条数据。如果要继续获取下一条数据,就调用cursor的continue()方法,当没有数据时,cursor会返回undefined。

openCursor有两个对象可供设置:一个是IDBKeyRange,用来限制范围,另一个是IDBCursor,用来控制数据库浏览方向。例如,要搜索姓名符合”Eileen“的数据,代码如下:

var index =store.index("name");
var request = index.openCursor(IDBKeyRange.only("Eileen"),IDBCursor.NEXT);

当要设置搜索某个范围的数据时,IDBKeyRange也有4种方法可供使用

  • lowerBound:指定范围下限
  • upperBound:指定范围上限
  • bound:指定范围上下限
  • only:指定固定值
IDBKeyRange范围的用法
范围 语法
value=z IDBKeyRange.only(z);
value<=x IDBKeyRange.upperBound(x);
value<x IDBKeyRange.upperBound(x,true);
value>=y IDBKeyRange.lowerBound(x);
value>y IDBKeyRange.lowerBound(x,true);
value>=x&&value<=y IDBKeyRange.bound()x,y;
value>x&&value<y IDBKeyRange.bound(x,y,true,true)
value>x&&value<=y IDBKeyRange.bound(x,y,true,false)

第二个参数true和false是设置包含或不包含搜索值,默认值是false,也就是包含搜索值本身,true表示不包含搜索值本身。例如下面是取得日期范围2019/03/25到2019/04/25,包含2019/03/25,但不包含2019/04/25

IDBKeyRange.bound("2019/03/25","2019/04/25",false,true);
IDBCursor的4个参数
参数 说明
next 从小到大
nextunique 有多条相同数据时,仅返回数据键最小的数据
prev 从大到小
prevunique 有多条相同数据时,仅返回数据键最大的数据

接下来看看Cursor的操作范例。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title></title>
    <script src="../jquery-3.3.1.min.js"></script>
    <style>
        table{border:0;margin:0;border-collapse: collapse;}
        table td{padding: 3px;}
    </style>
    <script>
        var db;
        $(function () {
            window.indexedDB = window.indexedDB ||  window.mozIndexedDB  ||window.webkitIndexedDB
             ||  window.msIndexedDB;
            if(!window.indexedDB) {
                alert("你的浏览器不支持IndexedDB!");
            }
            //打开数据库
            var req = window.indexedDB.open("MyDatabase");
             req.onsuccess = function (e) {
                 db = this.result;
                 alert("openDb Done!");
             };
             req.onerror =function (e) {
                 alert("openDb:",e.target.errorCode);
             };
        //onupgradeneeded事件
            req.onupgradeneeded = function (e) {
               //创建objectStore
                var objectStore = e.target.result.createObjectStore("customer",{keyPath:"user_id"});
                objectStore.createIndex("name","name",{unique:false});
                objectStore.createIndex("birthday","birthday",{unique:false});
                //要新增的数据Array
                const customerData = [
                    {user_id:"A001",name:"Eileen",birthday:"1995/05/05"},
                    {user_id:"A002",name:"Brian",birthday:"1992/03/18"},
                    {user_id:"A003",name:"Andy",birthday:"1998/05/16"},
                    {user_id:"A004",name:"Jennifer",birthday:"1996/05/15"},
                    {user_id:"A005",name:"Mark",birthday:"1998/03/01"}
                ];
                for ( var i in customerData){
                    objectStore.add(customerData[i]);
                }

            };
            $("#findbtn").click(function () {
                find_data($("#name").val(),$("#birthday_s").val(),$("#birthday_e").val(),$("#sorting:checked").val());
                // alert("执行");
            });
        })
        function find_data(name,births,birthe,sorting) {
            $("div").empty();
            var transaction = db.transaction("customer","readwrite");
            var store = transaction.objectStore("customer");
            //搜索姓名
            if(name !=""){
                range = IDBKeyRange.bound(name,name +'\uffff',false,false);//\uffff是unicode字符的最大值也就是z
            }
            index = store.index("name");
            var request = index.openCursor(range,sorting);
            //搜索生日
            if(births !=""||birthe!=""){
                if (births !=""&&birthe!=""){
                    range = IDBKeyRange.bound(births,birthe,false,false);//起始日期~结束日期
                } else if (births =="")
                {
                    range = IDBKeyRange.upperBound(birthe,false);//只输入结束日
                }else{
                    range = IDBKeyRange.lowerBound(births,false);//只输入起始日
                }
                index = store.index("birthday");
                var request = index.openCursor(range,sorting);
            }
            var str = "查询结果:<table border=1><tr><td><td>账号</td></th>姓名</td><td>生日</td></tr>";
            request.onsuccess = function (e) {
                var cursor = e.target.result;
                if (cursor)
                {
                    str+="<tr><td>"+cursor.value.user_id+"</td><td>"+cursor.value.name+"</td><td>"+
                        cursor.value.birthday+"</td></tr>";
                    cursor.continue();
                }else{
                    str+="</table>";
                    $("div").html(str);
                }

            };
            request.onerror = function (e) {
                $("div").html(e.target.error);
            }
        }
    </script>
</head>
<body>
搜索条件:<br>
姓名:<input type="text" id='name'><br/>
生日:<input type="date" id='birthday_s' value="1990/01/01">~<input type="date" id="birthday_e" value="1995/12/01"><br/>
<input type="radio" name="sorting" id="sorting" value="prev" checked>从小到大
<input type="radio" name="sorting" id="sorting" value="next">从大到小<br/>
<button id ="findbtn">发送查询</button>
<div id="message"></div>
</body>
</html>

敲代码的时候...切记小心...不然敲代码花十分钟,找BUG花一小时...我就是又在找bug...

执行结果如下:

猜你喜欢

转载自blog.csdn.net/CSDN_XUWENHAO/article/details/88785776
今日推荐