前端的数据库:IndexedDB入门

2014/12/27 · 未分类 · IndexedDB

本文由 伯乐在线 –
cucr
翻译,黄利民
校稿。未经许可,禁止转发!
英文出处:www.codemag.com。欢迎参与翻译组。

应用程序须要多少。对大多数Web应用程序来说,数据在服务器端组织和管制,客户端通过互连网请求获取。随着浏览器变得尤为有力量,由此可选取在浏览器存款和储蓄和控制应用程序数据。

正文向您介绍名为IndexedDB的浏览器端文档数据库。使用lndexedDB,你能够透过惯于在劳务器端数据库差不多如出一辙的不二法门成立、读取、更新和删除大量的记录。请使用本文中可工作的代码版本去体会,完整的源代码能够透过GitHub库找到。

读到本学科的终极时,你将熟习IndexedDB的基本概念以及如何促成2个选拔IndexedDB执行总体的CRUD操作的模块化JavaScript应用程序。让大家有点亲近IndexedDB并起始吧。

什么是IndexedDB

诚如的话,有二种分化门类的数据库:关系型和文书档案型(也叫做NoSQL或对象)。关全面据库如SQL
Server,MySQL,Oracle的多寡存储在表中。文书档案数据库如MongoDB,CouchDB,Redis将数据集作为个人对象存款和储蓄。IndexedDB是一个文书档案数据库,它在一齐内放置浏览器中的3个沙盒环境中(强制依据(浏览器)同源策略)。图1体现了IndexedDB的数码,突显了数据库的协会

亚洲城ca88 1

图1:开发者工具查看3个object
store

全套的IndexedDB API请参见完整文书档案

介绍

IndexedDB正是二个数据库
其最大的表征是:
应用对象保存数据,而不是利用表来保存数据,同时,它是异步的

IndexedDB是HTML5正式里新出现的浏览器里放置的数据库。对于在浏览器里积存数据,你能够选取cookies或local
storage,但它们都以比较不难的技术,而IndexedDB提供了就如数据库风格的多寡存款和储蓄和行使方法。存款和储蓄在IndexedDB里的数据是恒久保存,不像cookies那样只是一时半刻的。IndexedDB里提供了查询数据的遵守,在online和offline方式下都能利用。你能够用IndexedDB存储大型数据。

倘使还在大学学习,这门课一定叫做前端概论。

设计规范

IndexedDB的架构很像在部分流行的劳动器端NOSQL数据库实现中的设计规范类型。面向对象数据经过object
stores(对象仓库)举行持久化,全部操作基于请求同时在事情限制内实施。事件生命周期使您能够决定数据库的配备,错误通过荒谬冒泡来使用API管理。

采纳办法

IndexedDB里多少以指标的款式储存,每种对象都有2个key值索引。IndexedDB里的操作都以事务性的。一种对象存款和储蓄在八个objectStore里,objectStore就也正是关周密据Curry的表。IndexedDB能够有好多objectStore,objectStore里能够有好多指标。各样对象能够用key值获取。

重在内容有:

对象仓库

object
store是IndexedDB数据库的根底。假如你使用过关周到据库,平日能够将object
store等价于2个数目库表。Object
stores包蕴一个或多少个目录,在store中依照一对键/值操作,那提供一种高效稳定数据的不二法门。

当您铺排四个object
store,你无法不为store选取二个键。键在store中能够以“in-line”或“out-of-line”的艺术存在。in-line键通过在数据对象上引用path来维系它在object
store的唯一性。为了印证那或多或少,想想贰个归纳电子邮件地址属性Person对象。您能够配备你的store使用in-line键emailAddress,它能担保store(持久化对象中的数据)的唯一性。其余,out-of-line键通过单独于数据的值识别唯一性。在那种景观下,你能够把out-of-line键比作贰个整数值,它(整数值)在关周到据库中担任记录的主键。

图1显得了职务数据保存在职务的object
store,它利用in-line键。在那么些案例中,键对应于对象的ID值。

连日数据库

要利用它必须先打开,通过 indexDB.open(name, version)措施打开一个数据库

  • name : 表示数据要打开的数据库的称谓
  • version:为开拓数据库的本子号

IndexedDB vs LocalStorage

IndexedDB和LocalStorage都以用来在浏览器里积存数据,但它们选用区别的技术,有例外的用处,你须求依照本人的情状11分的选拔使用哪一类。LocalStorage是用key-value键值形式存款和储蓄数据,但跟IndexedDB分裂的是,它的数目并不是按对象方式储存。它存款和储蓄的数码都以字符串格局。假若您想让LocalStorage存款和储蓄对象,你须要借助JSON.stringify()能将目的变成字符串方式,再用JSON.parse()将字符串还原成对象。但万一要存款和储蓄大量的扑朔迷离的多少,这并不是一种很好的方案。终归,localstorage便是特意为小数目多少安插的,它的api是共同的。

IndexedDB很吻合储存大批量数据,它的API是异步调用的。IndexedDB使用索引存款和储蓄数据,种种数据库操作放在工作中实践。IndexedDB甚至还扶助简单的数据类型。IndexedDB比localstorage强大得多,但它的API也相对复杂。

对于简易的多寡,你应当继承行使localstorage,但当你指望存款和储蓄多量数码时,IndexedDB会明显的更适合,IndexedDB能提供您尤其复杂的查询数据的法门。

1.自个儿的率先个页面;

依照事务

分裂于一些观念的关全面据库的达成,每一种对数据库操作是在1个作业的上下文中执行的。事务限制1回影响三个或两个object
stores,你通过传播二个object store名字的数组到开创工作限制的函数来定义。

成立工作的第二个参数是业务格局。当呼吁二个事务时,必须控制是奉公守法只读依然读写情势请求访问。事务是能源密集型的,所以即使您不须要更改data
store中的数据,你只必要以只读格局对object stores集合举办呼吁访问。

清单2示范了什么样行使卓越的情势开创2个作业,并在那片作品的 亚洲城ca88,Implementing
Database-Specific Code
 部分开始展览了详细座谈。

indexDB.open()方式的原理

分为二种景况:
1. 传来的数据库不设有
当传入的数据库不存在时,该措施就会创设四个名为name的数据库,并打开它,此时,会先触发upgradeneeded事件;调用该函数会重临几个IDBRequest目的,能够在该对象上添加onsuccess事件onerror事件
注意:当打开三个不存在的数据库时会触发upgradeneeded事件,那是触发该事件的一种途径,为啥会触发该事件吧?该事件有哪些功能?留个难点在那儿,等会解答。

2. 传到的数据仓库储存在
此间分为二种状态:

  • 当传入的数据仓库储存在,且version版本号与即将打开的数据库版本号也同等
    则一向打开该数据库,如若成功,则会接触onsuccess事件,失利则触发onerror事件
    注意:那里并不会触发upgradeneeded事件,为何?留个难点

  • 当传入的数据仓库储存在,可是传入的version版本号高于即将打开的数据库的本子号
    则一贯打开该数据库,同时触发upgradeneeded事件,然后再触及onsuccess事件onerror事件,这里也接触了onupdateneeded事件

IndexedDB vs Web SQL

WebSQL也是一种在浏览器里储存数据的技能,跟IndexedDB区别的是,IndexedDB更像是三个NoSQL数据库,而WebSQL更像是关系型数据库,使用SQL查询数据。W3C已经不复协理那种技能。具体景况请看:。

因为不再协理,所以您就不要在档次中选用那种技术了。

2.开发工具的选项与主干使用;

根据请求

截止这里,有1个往往出现的核心,您也许早已注意到。对数据库的历次操作,描述为经过3个呼吁打开数据库,访问三个object
store,再持续。IndexedDB
API天生是基于请求的,这也是API异步天性提示。对于你在数据库执行的每一次操作,你必须首先为那些操作成立二个伸手。当呼吁达成,你能够响应由请求结果发生的风浪和不当。

正文达成的代码,演示了哪些利用请求打开数据库,创制1个工作,读取object
store的内容,写入object store,清空object store。

upgradeneeded事件

触发该事件的规范:当打开的数据库不存在,大概传播的数据库版本version高于当前版本,则会触发该事件

upgradeneeded事件的机能:当打开了2个数据库之后,须求开拓多个名为:指标存款和储蓄空间
的钱物(能够知道为数据正是存放在那一个空间里面,2个数据库能够创设多个对象存储空间),而
对象存储空间 只能在upgradeneeded事件的处理函数中创设

行使时,注意以下二种情况:

  1. 当大家首先次打开创制数据库时,会接触upgradeneeded事件,大家就需求在当中创造对象存款和储蓄空间

  2. 当我们对数据库版本实行立异时,也会触发该事件,那时能够在此创立新的目的存款和储蓄空间,原来的指标存款和储蓄空间如故存在

注意:假诺供给对指标存款和储蓄空间拓展改动,那么只好先将积存在它个中的多少读取出来,再将其除去,然后选取新的选项去成立它,再写入原来的数据

打开数据库并创设对象存款和储蓄空间的代码:

// 对于该API,各浏览器还未同一,所以需要对一些接口添加前缀
window.indexedDB = window.indexedDB || window.msIndexedDB || window.mozIndexedDB || window.webkitIndexedDB;
window.IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction || window.msIDBTransaction || {READ_WRITE: "readwrite"};
window.IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange || window.msIDBKeyRange;
window.IDBCursor = window.IDBCursor || window.webkitIDBTransaction;

// 判断浏览器是否支持IndexedDB
if (!window.indexedDB) {
window.alert("Your browser doesn't support a stable version of IndexedDB.")
}

var request , db;
// 打开或创建 名为dbName的数据库
request = window.indexedDB.open('dbName', 2)
request.onsuccess = function (event) {
   db = event.target.result;
}

request.onerror = function (event) {
   console.log('错误代码: ' + event.target.errorCode);
}

request.onupgradeneeded = function(event) {
  db = event.target.result;  // 
  // 创建一个   对象存储空间,名为customers
  var objectStore = db.createObjectStore('customers', {keyPath: 'ssn'});
  // 对于某些数据,可以为一个对象存储空间指定多个键。比如,若要通过用户ID 和用户名 两种方式来保存用户资料,就需要通过两个键来存取记录
  // 因此可以使用createIndex,名字是有可能重复的,所以其unique 设置为 false ;第一个name是索引的名字,该名字是索引的名字,第二个name是索引的属性的名字,该名字要与对象中的属性相同
  objectStore.createIndex('name', 'name', { unique: false});

  // 创建一个email的索引,该email是独特的,所以 unique 设置为 true
  objectStore.createIndex('email', 'email', { unique: true});
}

IndexedDB vs Cookies

Cookies(小甜点)听起来很好吃,但实则并不是。每一趟HTTP接受和殡葬都会传递Cookies数据,它会占用额外的流量。例如,若是你有1个10KB的Cookies数据,发送十三回呼吁,那么,总括就会有100KB的多寡在互联网上传输。Cookies只可以是字符串。浏览器里储存Cookies的半空中有限,很多用户禁止浏览器选拔Cookies。所以,Cookies只好用来囤积小量的非关键的数码。

3.询问调节和测试工具;

开拓数据库的央求生命周期

IndexedDB使用事件生命周期管理数据库的开拓和布局操作。图2示范了三个打开的请求在肯定的条件下产生upgrade
need事件。

亚洲城ca88 2

图2:IndexedDB打开请求的生命周期

拥有与数据库的并行伊始于贰个开辟的请求。试图打开数据库时,您必须传递1个被呼吁数据库的本子号的整数值。在打开请求时,浏览器相比较你传入的用来打开请求的版本号与事实上数据库的版本号。要是所请求的版本号高于浏览器中当前的版本号(或然今后从未有过存在的数据库),upgrade
needed事件触发。在uprade
need事件时期,你有机遇通过丰硕或移除stores,键和索引来操纵object stores。

假设所请求的数据库版本号和浏览器的此时此刻版本号相同,也许升级进度完成,2个开拓的数据库将回来给调用者。

存款和储蓄数据

储存数据有二种办法:add()方法put()方法

那三种办法的差别首要显示在:当要添加数据的指标存款和储蓄空间中早就存在有相同键的数码时,使用add()方法添加数据会报错误,而put()方法则会对现有数据开始展览翻新,所以add()方法一般用于初步化数据,而put()方法用于立异数据

代码如下:

// customerData 为要存储的数据
const customerData = [{ ssn: '444-44-4444', name: 'AAA', age: 35, email: '[AAA@company.com](mailto:AAA@company.com)'},{ ssn: '666-66-6666', name: 'CCC', age: 35, email: '[CCC@company.com](mailto:CCC@company.com)'},{ ssn: '777-77-7777', name: 'DDD', age: 32, email: '[DDD@home.org](mailto:DDD@home.org)'},{ ssn: '555-55-5555', name: 'BBB', age: 32, email: '[BBB@home.org](mailto:BBB@home.org)'},
];

// 创建一个事务,该事务将要对名为“customers”的对象存储空间进行 read和write 操作,并返回事务索引
let transaction = db.transaction('customers', 'readwrite'); 

// 取得索引后,使用objectStore()方法并传入存储空间的名称,就可以访问特定的存储空间,这两步是必须的
let store = transaction.objectStore('customers'); 

// 添加数据到数据库中
for (var i in customerData) {
  // 返回的req也是一个对象,可以为其添加onsuccess和onerror事件,来检测数据是否添加成功
  let req = store.put(customerData[i]);   // 往一个存储空间中添加数据

}
// 判断事务整个操作完成
transaction.oncomplete = function(event) {
  console.log(event.target);
  alert('存储数据完成');
};
}

如上就将数据存款和储蓄到数据库dbNames的customers对象存款和储蓄空间中

上边代码中涉嫌了
[事务],那里先记住:举凡涉及到对数据库的读写删除操作,都亟待经过
[事务] 来完成

IndexedDB的用法

想要明白IndexedDB,最好的法门是创办2个简易的web应用:把你们班的学生的学号和姓名存款和储蓄在IndexedDB里。IndexedDB里提供了总结的增、删、改、查接口。

4.本期科目布置;

不当冒泡

当然,有时候,请求大概不会按预想达成。IndexedDB
API通过荒谬冒泡效果来增派跟踪和管制不当。假若二个特定的呼吁蒙受错误,你能够尝尝在伸手对象上处理错误,只怕你能够允许错误通过调用栈冒泡向上传递。那几个冒泡性子,使得你不要求为各种请求完成特定错误处理操作,而是可以挑选只在3个更高级别上添加错误处理,它给你二个火候,保持你的错误处理代码简洁。本文中落到实处的例证,是在三个高级别处理错误,以便更细粒度操作发生的其余不当冒泡到通用的错误处理逻辑。

事情和询问操作数据

最简易的始建筑工程作的办法是:
var transaction = db.transaction(); // db就是前面的数据库对象
那种方法成立的事体,只可以读取数据库中保存的享有目的

一般用法是:
var transaction = db.transaction('customes', 'readwrite');
意味着只加载customers对象存款和储蓄空间中的数据,并且是以可读可写的法子加载

假设不传第二个参数,则表示只可访问,不可修改;

此间重临的transaction是事情的目录

下一场使用objectStore()主意并传到对象存款和储蓄空间的称号,就足以访问特定的囤积空间了;

如下:

let transaction = db.transaction('customers', 'readwrite'); 
let store = transaction.objectStore('customers'); 

取得了下边包车型地铁store后,大家得以行使如下方法对数据进行操作:

  • add()和put()方法:用于存款和储蓄数据
    let req = store.add(data);
  • get(key)方法:获取键为key的目的
    let req = store.get(key);
  • 浏览器里内置的数据库,前端的数据库。delete(key)方法:删除键为key的对象
    let req = store.delete(key);
  • clear()方法:清空对象存款和储蓄空间中的全部指标
    let req = store.clear();
    利用上述方法会重返3个指标,通过对其添加onsuccess和onerror事件,能够检查和测试操作是还是不是成功

注意:通过oncomplete事件指标,访问不到get()请求再次来到的别的数据,必须在响应请求的onsuccess事件处理程序中才能访问到数码

打开贰个IndexedDB数据库

第2,你须要知道您的浏览器是或不是支持IndexedDB。请使用新型版的谷歌(Google)浏览器或火狐浏览器。低版本的IE是丰硕的。

window.indexedDB = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB;

if(!window.indexedDB)
{
    console.log("你的浏览器不支持IndexedDB");
}

若是你的浏览器扶助IndexedDB,大家就足以打开它。你不能够向来打开IndexedDB数据库。IndexedDB须要你创立二个伸手来打开它。

 var request = window.indexedDB.open("testDB", 2);

第②个参数是数据库的称号,第一个参数是数据库的版本号。版本号能够在升级数据库时用来调动数据库结构和数码。

但您扩展数据库版本号时,会触发onupgradeneeded事件,那时可能会冒出成功、退步和阻止事件三种状态。

var db;
request.onerror = function(event){
    console.log("打开DB失败", event);
}
request.onupgradeneeded   = function(event){
    console.log("Upgrading");
    db = event.target.result;
    var objectStore = db.createObjectStore("students", { keyPath : "rollNo" });
};
request.onsuccess  = function(event){
    console.log("成功打开DB");
    db = event.target.result;
}

onupgradeneeded事件在率先次打开页面伊始化数据库时会被调用,或在当有版本号变化时。所以,你应该在onupgradeneeded函数里创制你的囤积数据。如若没有版本号变化,而且页面在此之前被打开过,你会赢得二个onsuccess事件。倘诺有不当发生时则触发onerror事件。假诺您前面从未关闭连接,则会接触onblocked事件。

在地点的代码片段里,大家创制了一个Object
Store,叫做“students”,用“rollNo”做多少键名。

5.参考资料。

浏览器支持

或然在支付Web应用程序最注重的标题是:“浏览器是不是帮忙自身想要做的?“就算浏览器对IndexedDB的支撑在持续增加,选用率并不是大家所企盼的那么普遍。图3展现了caniuse.com网站的报告,帮忙IndexedDB的为66%多一小点。最新版本的银狐,Chrome,Opera,Safar,iOS
Safari,和Android完全协助IndexedDB,Internet
Explorer和华为部分协助。尽管这几个列表的维护者是令人鼓舞的,但它没有告诉全部传说。

亚洲城ca88 3

图3:浏览器对IndexedDB的支持,来自caniuse.com

唯有丰裕新本子的Safari和iOS Safari
协理IndexedDB。据caniuse.com展现,那只占大致0.01%的大世界浏览器选择。IndexedDB不是一个你以为能够理所当然获得帮衬的现世Web
API,然而你将高速会那样认为。

动用游标查询数据

使用工作能够直接通过
已知的键检索单个对象。而在需求寻找多少个对象时,则供给在事情内成立游标。

游标并不会提早收集结果,游标先指向结果中的第2项,在吸收接纳查找下一项的一声令下时,才会指向下一项

如下:

let transaction = db.transaction('customers', 'readwrite'),
let store = transaction.objectStore('customers'),
let request = store.openCursor(null) ; // 这里创建游标
request.onsuccess = function (event) {
  // event.target.result 中保存的是在存储空间中查询到的对象
  // event.target.result 中有几个属性值,可以了解到查询到的对象中的细节,
  // key: 当前访问的对象的键
  // value:当前访问的实际对象
  // primaryKey: 游标使用的键
  // direction:数值,表示游标移动的方向
  let cursor = event.target.result;
  let value, updateRequest, deleteRequest;

  // 这里必须要检查游标中是否有数据
  if (cursor) {
    if (cursor.key === '555-55-5555') {
      value = cursor.value;   // 获取到实际的访问对象
      value.name = 'hexon';   // 修改对象的name属性
      // 调用update()方法可以用指定的对象,更新对象的value
      updateRequest = cursor.update(value);     
      updateRequest.onsuccess = function() {
          // 处理成功
       }
    }
    cursor.continue() ;  // 移动到下一项,会触发下一次请求,同时成功则触发request.onsuccess
  }
}

地点例子中,能够使用cursor.delete()措施删除当前项

往ObjectStore里增加产量对象

为了往数据Curry新增多少,大家先是要求创立叁个事务,并供给拥有读写权限。在indexedDB里别的的存取对象的操作都亟需放在工作里实施。

var transaction = db.transaction(["students"],"readwrite");
transaction.oncomplete = function(event) {
    console.log("Success");
};

transaction.onerror = function(event) {
    console.log("Error");
};  
var objectStore = transaction.objectStore("students");

objectStore.add({rollNo: rollNo, name: name});

一 、入门案例

另一种选取

浏览器帮忙地点数据库并不是从IndexedDB才初步落到实处,它是在WebSQL落实之后的一种新点子。类似IndexedDB,WebSQL是三个客户端数据库,但它看作三个关全面据库的兑现,使用结构化查询语言(SQL)与数据库通信。WebSQL的历史充满了曲折,但底线是绝非主流的浏览器厂商对WebSQL继续援救。

万一WebSQL实际上是二个丢掉的技艺,为啥还要提它吗?有趣的是,WebSQL在浏览器里拿走巩固的扶助。Chrome,
Safari, iOS Safari, and
Android 浏览器都扶助。此外,并不是那些浏览器的风行版本才提供扶助,许多这个最新最好的浏览器此前的本子也足以匡助。有趣的是,如果你为WebSQL添加援救来支撑IndexedDB,你突然发现,许多浏览器厂商和版本成为援救浏览器内置数据库的某种化身。

就此,借使您的应用程序真正供给贰个客户端数据库,你想要达到的最高级别的选用大概,当IndexedDB不可用时,大概你的应用程序大概看起来须要采用使用WebSQL来支撑客户端数据架构。尽管文书档案数据库和关周全据库管理数据有明显的距离,但一旦您有不利的虚幻,就足以采纳当地数据库营造八个应用程序。

键范围

游标也可以承受一个键,约等于通过键来设定游标查找的界定;
代码如下:

<!DOCTYPE html>
<html lang=”en”>
<head>
<meta charset=”UTF-8″>
<title>WebStorage DEMO</title>
</head>
<body>
<div class=”networkStatus”>
<button class=”clear”>清空数据</button>
<button class=”add”>添加数据</button>
<button class=”query”>查询数据</button>
<button class=”delete”>删除数据</button>
<button class=”cursor”>使用游标查询</button>
<button class=”keyRange”>使用keyrange查询</button>
<button class=”index”>使用index</button>
</div>

<script>
let network = document.querySelector(‘.networkStatus’),
addBtn = document.querySelector(‘.add’),
queryBtn = document.querySelector(‘.query’),
deleteBtn = document.querySelector(‘.delete’),
cursorBtn = document.querySelector(‘.cursor’),
clearBtn = document.querySelector(‘.clear’),
keyRange = document.querySelector(‘.keyRange’),
indexBtn = document.querySelector(‘.index’)
;

// 判断网路是或不是在线
// if (navigator.onLine) {
// network.innerText = “互联网在线”;
// } else {
// network.innerText = “互联网掉线”;
// }

// // 监察和控制互连网状态的事件:online 和 offline, 那多个事件在window对象上
// window.addEventListener(‘online’, () => {
// network.innerText = “互联网在线”;
// });

// window.addEventListener(‘offline’, () => {
// network.innerText = “互联网掉线”;
// });

//——–cookie的使用—————
let CookieUtil = {
get: (name) => {
let cookieName = encodeURIComponent(name) + “=”,
cookieStart = document.cookie.indexOf(cookieName),
cookieValue = null;

  if (cookieStart > -1) {
    let cookieEnd = document.cookie.indexOf(';', cookieStart);
    if (cookieEnd === -1) {
      cookieEnd = document.cookie.length;
    }
    cookieValue = decodeURIComponent(document.cookie.substring(cookieStart + cookieName.length, cookieEnd));
  }

  return cookieValue;
},
set: function (name, value, expires, path, domain, secure) {
  let cookieText = encodeURIComponent(name) + '=' +
                   encodeURIComponent(value);

  if (expires instanceof Date) {
    cookieText += '; expires=' + expires.toGMTString();
  }

  if (path) {
    cookieText += '; path=' + path;
  }

  if (domain) {
    cookieText += '; domain=' + domain;
  }

  if (secure) {
    cookieText += '; secure';
  }

  document.cookie = cookieText;
},

// 删除cookie, 并没有直接的删除cookie的方法,这里通过重新设置cookie名称,来对cookie进行替换
// 同时 将过期时间expires设置为过去的时间,
unset: function(name, path, domain, secure) {
  this.set(name, '', new Date(0), path, domain, secure);
}

}

CookieUtil.set(‘name’, ‘hexon’);
CookieUtil.set(‘book’, ‘Profession Javascript’);

// 读取cookie的值
// console.log(CookieUtil.get(‘name’));
// console.log(CookieUtil.get(‘book’));

// 删除cookie
CookieUtil.unset(‘name’);
CookieUtil.unset(‘book’);

// 设置cookie, 包蕴它的不二法门、域、失效日期
CookieUtil.set(‘name’, ‘Hexon’, ‘books/projs/’,
‘www.wrox.com’, new
Date(‘January 1, 2017’));

// 删除刚刚安装的cookie
CookieUtil.unset(‘name’, ‘books/projs/’,
‘www.www.wrox.com’);

// 设置安全的cookie
CookieUtil.unset(‘name’, ‘hexon’, null, null, null, null, true)

// — IndexedDB 数据库的施用
var request = window.indexedDB.open(‘dbName’, 2)
var db;
const dbName = ‘the_name’;
// 创立三个数量
const customerData = [
{ ssn: ‘444-44-4444’, name: ‘AAA’, age: 35, email:
‘AAA@company.com’},
{ ssn: ‘666-66-6666’, name: ‘CCC’, age: 35, email:
‘CCC@company.com’},
{ ssn: ‘777-77-7777’, name: ‘DDD’, age: 32, email:
‘DDD@home.org’},
{ ssn: ‘555-55-5555’, name: ‘BBB’, age: 32, email:
‘BBB@home.org’},

];

window.indexedDB = window.indexedDB || window.msIndexedDB ||
window.mozIndexedDB || window.webkitIndexedDB;
window.IDBTransaction = window.IDBTransaction ||
window.webkitIDBTransaction || window.msIDBTransaction || {READ_WRITE:
“readwrite”};
window.IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange ||
window.msIDBKeyRange;
window.IDBCursor = window.IDBCursor || window.webkitIDBTransaction;

if (!window.indexedDB) {
window.alert(“Your browser doesn’t support a stable version of
IndexedDB.”)
}

// 3
是确立的数据库版本,如若名为MyTestDatabase的数据库不存在,就会创制该数据库,然后
onupgradeneeded 事件会被触发;
// 假如数据仓库储存在,可是对版本升级了,也会触发onupgradeneeded事件,
// 注意:版本号是贰个 unsigned long long
类型的值,因而不用使用float,否则会将其更换为其最接近的平头

// 生成处理程序
request.onerror = function (event) {
// do Something
alert(‘Database error: ‘ + event.target.errorCode);
};

request.onsuccess = function (event) {
// do Something
console.log(‘创设数据库成功’);
db = event.target.result; // 创造成功后,e.target.result
中存款和储蓄的是IDBDatabase对象的实例
}

// 当创造多个新的数量库 恐怕 更新已存在数据库的版本,
onupgradeneeded事件将会被触发,新的指标存款和储蓄在event.target.result中。
//
在该处理程序中,数据库已经颇具先前版本的对象存款和储蓄,由此不必再次创设那些指标存款和储蓄,只供给创造任何大家需求的靶子存款和储蓄,只怕
//
从原先版本中去除不在必要的对象存款和储蓄。倘使必要变更当前指标存款和储蓄,则必须先删除旧的靶子存款和储蓄,然后在运用新的选项创立。
// 删除旧的靶子存款和储蓄,在其上的新闻都会被删除;
//
注意:该事件是绝无仅有三个力所能及对数据库举办操作的地点,在该事件之中,你对目标存款和储蓄举行删减、修改或移除索引
request.onupgradeneeded = function(event) {
console.log(‘onupgradeneeded’);
var db = event.target.result;

// 创建一个   对象存储空间,名为customers
var objectStore = db.createObjectStore('customers', {keyPath: 'ssn'});
// 对于某些数据,可以为一个对象存储空间指定多个键。比如,若要通过用户ID 和用户名 两种方式来保存用户资料,就需要通过两个键来存取记录
// 因此可以使用createIndex,名字是有可能重复的,所以其unique 设置为 false ;第一个name是索引的名字,该名字是索引的名字,第二个name是索引的属性的名字,该名字要与对象中的属性相同
objectStore.createIndex('name', 'name', { unique: false});

// // 创建一个email的索引,该email是独特的,所以 unique 设置为 true
objectStore.createIndex('email', 'email', { unique: true});

}

function save(data) {
///
对于数据库的对象存储空间中数量的读取或修改数据,都要透过事物来组织全数操作
// 最简便的始建事物的主意是:var transaction = db.transaction();
let transaction = db.transaction(‘customers’, ‘readwrite’); //
创设3个政工,并定义该事务的操作为 “readwrite” ,并赶回其索引
let store = transaction.objectStore(‘customers’); //
取得索引后,使用objectStore()方法并传到存款和储蓄空间的称谓,就可以访问特定的贮存空间

for (var i in customerData) {
  let req = store.put(customerData[i]);   // 往一个存储空间中添加数据
}

transaction.oncomplete = function(event) {
  console.log(event.target);
  alert('存储数据完成');
};

transaction.onsuccess = function(event ) {
  console.log('onsuccess 事件');
}

}

function clear() {
// body…
let transaction = db.transaction(‘customers’, ‘readwrite’);
let store = transaction.objectStore(‘customers’).clear();
store.onerror = function(event) {
console.log(‘清空数据战败’);
}
store.onsuccess = function(event) {
console.log(‘清空数据成功’);
}
}

// 使用事务 直接通过已知的键索引 单个对象 (只可以索引单个对象)
function getData() {
let transaction = db.transaction(‘customers’, ‘readwrite’); //
创立一个东西, 并定义该事情的操作为 “readonly”
let store = transaction.objectStore(‘customers’).get(‘444-44-4444’); //
使用get() 能够取得值

store.onerror = function (event) {
  alert('did not get the object');
}

store.onsuccess = function (event) {
  var result = event.target.result;
  console.log(result);
  alert('获取数据完成! 年龄是: ' + result.age);
}

}

function deleteData() {
let transaction = db.transaction(‘customers’, ‘readwrite’);
let store = transaction.objectStore(‘customers’);
store.delete(‘444-44-4444’);
alert(‘s删除数据形成’);
}

// 在作行业内部成立游标查询 能够索引 八个目的(注意: 是七个对象)
// 游标不提前手提式有线电话机结果
function cursorQuery() {
let transaction = db.transaction(‘customers’, ‘readwrite’),
store = transaction.objectStore(‘customers’),
request = store.openCursor(null) ; // 那里创办游标

request.onsuccess = function (event) {

  // event.target.result 中保存的是在存储空间中查询到的对象
  // event.target.result 中有几个属性值,可以了解到查询到的对象中的细节,
  // key: 当前访问的对象的键
  // value:当前访问的实际对象
  // primaryKey: 游标使用的键
  // direction:数值,表示游标移动的方向

  let cursor = event.target.result;
  let value, updateRequest, deleteRequest;
  if (cursor) {
  //   if (cursor.key === '555-55-5555') {
  //     value = cursor.value;   // 获取到实际的访问对象
  //     value.name = 'hexon';   // 修改对象的name属性

  //     updateRequest = cursor.update(value);      // 调用update()方法可以用指定的对象,更新对象的value
  //     updateRequest.onsuccess = function() {
  //       // 处理成功
  //     }
  //     updateRequest.onerror = function() {
  //       // 处理失败
  //     }


  //     // 使用游标删除当前项
  //     // deleteRequest = cursor.delete();
  //     // deleteRequest.onsuccess = function() {
  //     //   // 删除成功处理
  //     // }
  //     // deleteRequest.onerror = function() {
  //     //   // 删除失败处理
  //     // }


  //   }
  //   console.log(event.target.result);
  // }
  console.log(cursor.value);
  cursor.continue();      // 移动到下一项,
  }
  request.onerror = function(event) {
    console.log('游标查询创建失败')
  }
}

}

// 使用keyrange查询
function keyRangeQuery() {
let transaction = db.transaction(‘customers’, ‘readwrite’)
let store = transaction.objectStore(‘customers’);
// 使用bound()方法 定义键范围
let range = IDBKeyRange.bound(‘555-55-5555’, ‘777-77-7777’, true,
false);
// 将键传入游标成立
let request = store.openCursor(range);

request.onsuccess = function(event) {
  let cursor = event.target.result;
  if (cursor) {
    console.log('游标查询到的值' + JSON.stringify(cursor.value));
    cursor.continue()     // 移动到下一项
  }

}

request.onerror = function(event) {
  console.log("使用游标 + keyrange 查询失败")
}

}

// 使用索引
function useIndex() {
let store = db.transaction(‘customers’).objectStore(‘customers’),
index = store.index(‘name’);
request = index.openCursor();
request.onsuccess = function (event) {
let cursor = event.target.result;
if (cursor) {
console.log(cursor);
cursor.continue();
}
}
}

addBtn.addEventListener(‘click’, function(e) {
save();
}, false);

deleteBtn.addEventListener(‘click’, function(e) {
deleteData();
}, false);

queryBtn.addEventListener(‘click’, function(e) {
getData();
}, false);

cursorBtn.addEventListener(‘click’, function(e) {
cursorQuery();
}, false);

clearBtn.addEventListener(‘click’, function(e) {
clear();
}, false);

keyRange.addEventListener(‘click’, function(e) {
keyRangeQuery();
}),

indexBtn.addEventListener(‘click’, function(e) {
useIndex();
})

</script>

</body>
</html>

从ObjectStore里删除对象

删去跟新增一样,必要创立工作,然后调用删除接口,通过key删除对象。

db.transaction(["students"],"readwrite").objectStore("students").delete(rollNo);

本身把语句合并到了一块儿,变得更不难,但职能是同等的。

先是阶段没什么好说的,先上手,让您感触到编制程序的魔力,不难的代码demo,敲出来就有成就感(简称:敲有成就感)。第一回就一向用记事本吧,终归,用记事本和浏览器就足以让您入前端的坑,代码如下:

IndexedDB是或不是顺应本人的应用程序?

明日最重点的题材:“IndexedDB是不是相符本人的应用程序?“像在此以前一致,答案是自但是然的:“视意况而定。“首先当您准备在客户端保存数据时,你会设想HTML5本土存款和储蓄。本地存款和储蓄获得大规模浏览器的支撑,有非凡便于使用的API。不难有其优势,但其劣势是心有余而力不足支撑复杂的追寻策略,存款和储蓄大量的多寡,并提供工作协理。

IndexedDB是叁个数据库。所以,当您想为客户端做出决定,考虑你哪些在服务端选用三个持久化介质的数据库。你可能会问本人某些题材来救助控制客户端数据库是或不是符合你的应用程序,包涵:

  • 您的用户通过浏览器访问您的应用程序,(浏览器)协助IndexedDB API吗 ?
  • 你必要仓储大批量的数量在客户端?
  • 您须求在一个巨型的数额集合中快速稳定单个数据点?
  • 你的架构在客户端供给工作支持呢?

一旦你对在那之中的别的难题回复了“是的”,很有或者,IndexedDB是你的应用程序的3个很好的候选。

通过key取出对象

get()主意里流传对象的key值,取出相应的对象。

var request = db.transaction(["students"],"readwrite").objectStore("students").get(rollNo);
request.onsuccess = function(event){
    console.log("Name : "+request.result.name);    
};

<!DOCTYPE html>

使用IndexedDB

前几天,你已经有机会熟练了有的的完全概念,下一步是始于兑现基于IndexedDB的应用程序。第②个步骤供给统一IndexedDB在不一致浏览器的实现。您能够很不难地增加各个厂商本性的精选的检查,同时在window对象上把它们设置为官方对象相同的称号。上边包车型客车清单显示了window.indexedDB,window.IDBTransaction,window.IDBKeyRange的末尾结果是何许都被更新,它们被设置为对应的浏览器的一定完成。

JavaScript

window.indexedDB = window.indexedDB || window.mozIndexedDB ||
window.webkitIndexedDB || window.msIndexedDB; window.IDBTransaction =
window.IDBTransaction || window.webkitIDBTransaction ||
window.msIDBTransaction; window.IDBKeyRange = window.IDBKeyRange ||
window.webkitIDBKeyRange || window.msIDBKeyRange;

1
2
3
4
5
6
7
8
9
10
window.indexedDB = window.indexedDB ||
                   window.mozIndexedDB ||
                   window.webkitIndexedDB ||
                   window.msIndexedDB;
window.IDBTransaction = window.IDBTransaction ||
                   window.webkitIDBTransaction ||
                   window.msIDBTransaction;
window.IDBKeyRange = window.IDBKeyRange ||
                   window.webkitIDBKeyRange ||
                   window.msIDBKeyRange;

未来,每一个数据库相关的全局对象拥有正确的本子,应用程序能够准备采用IndexedDB开头工作。

立异1个目的

为了立异1个对象,首先要把它取出来,修改,然后再放回去。

var transaction = db.transaction(["students"],"readwrite");
var objectStore = transaction.objectStore("students");
var request = objectStore.get(rollNo);
request.onsuccess = function(event){
    console.log("Updating : "+request.result.name + " to " + name);
    request.result.name = name;
    objectStore.put(request.result);
};

所有的源代码都在这里。如果有任何的问题,请留言,或通过 @歪脖骇客 给我私信

<html>

动用概述

在本教程中,您将学习怎么着成立四个行使IndexedDB存款和储蓄数据的模块化JavaScript应用程序。为了理解应用程序是哪些做事的,参考图4,它讲述了任务应用程序处于空白状态。从此处您能够为列表添加新义务。图5展现了录入了多少个职责到系统的画面。图6出示怎么删除二个职分,图7显示了正在编写任务时的应用程序。

亚洲城ca88 4

图4:空白的天职应用程序

亚洲城ca88 5

图5:职责列表

亚洲城ca88 6

图6:删除职分

亚洲城ca88 7

图7:编辑任务
前几天您熟识的应用程序的意义,下一步是伊始为网站铺设基础。

    <head>

铺设基础

那些事例从实现那样2个模块开端,它肩负从数据库读取数据,插入新的目的,更新现有对象,删除单个对象和提供在一个object
store删除全部目的的选项。这些事例完成的代码是通用的数量访问代码,您能够在任何object
store上应用。

其一模块是经过三个应声施行函数表明式(IIFE)实现,它选择对象字面量来提供协会。上面包车型地铁代码是模块的摘要,表明了它的基本构造。

JavaScript

(function (window) { ‘use strict’; var db = { /* implementation here
*/ }; window.app = window.app || {}; window.app.db = db; }(window));

1
2
3
4
5
6
7
8
(function (window) {
    ‘use strict’;
    var db = {
        /* implementation here */
    };
    window.app = window.app || {};
    window.app.db = db;
}(window));

用这么的结构,能够使这一个应用程序的富有逻辑封装在2个名为app的单对象上。别的,数据库相关的代码在贰个称作db的app子对象上。

这些模块的代码应用IIFE,通过传递window对象来有限匡助模块的熨帖限制。使用use
strict确定保障那么些函数的代码函数是遵照(javascript严酷方式)严峻编写翻译规则。db对象作为与数据库交互的享有函数的重要容器。最终,window对象检查app的实例是或不是存在,要是存在,模块使用当前实例,如果不设有,则创制一个新对象。一旦app对象成功再次来到或创办,db对象附加到app对象。

本文的其他部分将代码添加到db对象内(在implementation
here会
评论),为应用程序提供一定于数据库的逻辑。由此,如您所见本文前边的一对中定义的函数,想想父db对象活动,但全数任何职能都是db对象的积极分子。完整的数据库模块列表见清单2。

        <meta charset = “gbk”>

Implementing Database-Specific Code

对数据库的各种操作关联着三个先决条件,即有八个打开的数据库。当数据库正在被打开时,通过检查数据库版本来判断数据库是不是供给别的改变。上面包车型地铁代码展现了模块怎样跟踪当前版本,object
store名、某成员(保存了假如数据库打开请求实现后的数据库当前实例)。

JavaScript

version: 1, objectStoreName: ‘tasks’, instance: {},

1
2
3
version: 1,
objectStoreName: ‘tasks’,
instance: {},

在此间,数据库打开请求发生时,模块请求版本1数据库。假设数据库不存在,或许版本小于1,upgrade
needed事件在开辟请求达成前触发。那么些模块被安装为只利用二个object
store,所以名字直接定义在此间。最终,实例成员被创建,它用来保存一旦打开请求实现后的数据库当前实例。

接下去的操作是促成upgrade
needed事件的事件处理程序。在此处,检查当前object
store的名字来判断请求的object store名是或不是留存,假若不设有,成立object
store。

JavaScript

upgrade: function (e) { var _db = e.target.result, names =
_db.objectStoreNames, name = db.objectStoreName; if
(!names.contains(name)) { _db.createObjectStore( name, { keyPath: ‘id’,
autoIncrement: true }); } },

1
2
3
4
5
6
7
8
9
10
11
12
13
14
upgrade: function (e) {
    var
        _db = e.target.result,
        names = _db.objectStoreNames,
        name = db.objectStoreName;
    if (!names.contains(name)) {
        _db.createObjectStore(
            name,
            {
                keyPath: ‘id’,
                autoIncrement: true
            });
    }
},

在那一个事件处理程序里,通过事件参数e.target.result来访问数据库。当前的object
store名称的列表在_db.objectStoreName的字符串数组上。以往,如若object
store不设有,它是经过传递object
store名称和store的键的定义(自增,关联到数码的ID成员)来创建。

模块的下1个功用是用来捕获错误,错误在模块分化的请求创立时冒泡。

JavaScript

errorHandler: function (error) { window.alert(‘error: ‘ +
error.target.code); debugger; },

1
2
3
4
errorHandler: function (error) {
    window.alert(‘error: ‘ + error.target.code);
    debugger;
},

在这里,errorHandler在三个警告框展现任何错误。这么些函数是假意保持简单,对开发协调,当你学习应用IndexedDB,您能够很不难地察看任何不当(当他们爆发时)。当你准备在生产条件使用这一个模块,您须求在那个函数中落到实处部分错误处理代码来和您的应用程序的上下文打交道。

现行反革命基础落成了,这一节的别的部分将演示如何贯彻对数据库执行一定操作。第一个供给检查的函数是open函数。

JavaScript

open: function (callback) { var request = window.indexedDB.open(
db.objectStoreName, db.version); request.onerror = db.errorHandler;
request.onupgradeneeded = db.upgrade; request.onsuccess = function (e) {
db.instance = request.result; db.instance.onerror = db.errorHandler;
callback(); }; },

1
2
3
4
5
6
7
8
9
10
11
12
open: function (callback) {
    var request = window.indexedDB.open(
        db.objectStoreName, db.version);
    request.onerror = db.errorHandler;
    request.onupgradeneeded = db.upgrade;
    request.onsuccess = function (e) {
        db.instance = request.result;
        db.instance.onerror =
            db.errorHandler;
        callback();
    };
},

open函数试图打开数据库,然后实施回调函数,告知数据库成功开拓方可准备使用。通过访问window.indexedDB调用open函数来成立打开请求。这些函数接受你想打开的object
store的称谓和您想利用的数据库版本号。

假诺请求的实例可用,第②步要拓展的干活是设置错误处理程序和晋升函数。记住,当数据库被打开时,即便脚本请求比浏览器里更高版本的数据库(大概一旦数据库不存在),升级函数运转。不过,如若请求的数据库版本匹配当前数据库版本同时没有错误,success事件触发。

如若全数成功,打开数据库的实例能够从呼吁实例的result属性获得,这一个实例也缓存到模块的实例属性。然后,onerror事件设置到模块的errorHandler,作为今后其他请求的荒唐捕捉处理程序。最终,回调被实施来告诉调用者,数据库已经开辟并且正确地配备,能够选用了。

下1个要兑现的函数是helper函数,它回到所请求的object store。

JavaScript

getObjectStore: function (mode) { var txn, store; mode = mode ||
‘readonly’; txn = db.instance.transaction( [db.objectStoreName],
mode); store = txn.objectStore( db.objectStoreName); return store; },

1
2
3
4
5
6
7
8
9
getObjectStore: function (mode) {
    var txn, store;
    mode = mode || ‘readonly’;
    txn = db.instance.transaction(
        [db.objectStoreName], mode);
    store = txn.objectStore(
        db.objectStoreName);
    return store;
},

在此处,getObjectStore接受mode参数,允许你决定store是以只读照旧读写情势请求。对于那个函数,暗许mode是只读的。

各样针对object
store的操作都以在3个东西的左右文中执行的。事务请求接受1个object
store名字的数组。这么些函数这一次被布置为只利用一个object
store,不过要是你必要在工作中操作多个object store,你须要传递多少个object
store的名字到数组中。事务函数的第③个参数是二个方式。

一经事情请求可用,您就足以因而传递须求的object
store名字来调用objectStore函数以获得object
store实例的访问权。那么些模块的任何函数使用getObjectStore来获取object
store的访问权。

下一个兑现的函数是save函数,执行插入或更新操作,它根据传入的数码是不是有三个ID值。

JavaScript

save: function (data, callback) { db.open(function () { var store,
request, mode = ‘readwrite’; store = db.getObjectStore(mode), request =
data.id ? store.put(data) : store.add(data); request.onsuccess =
callback; }); },

1
2
3
4
5
6
7
8
9
10
11
12
save: function (data, callback) {
    db.open(function () {
        var store, request,
            mode = ‘readwrite’;
 
        store = db.getObjectStore(mode),
        request = data.id ?
            store.put(data) :
            store.add(data);
        request.onsuccess = callback;
    });
},

save函数的七个参数分别是亟需保留的数码对象实例和操作成功后供给实施的回调。读写方式用于将数据写入数据库,它被盛传到getObjectStore来博取object
store的四个可写实例。然后,检查数据对象的ID成员是或不是存在。假设存在ID值,数据必须立异,put函数被调用,它创制持久化请求。不然,若是ID不存在,那是新数据,add请求再次回到。最后,不管put也许add
请求是不是举办了,success事件处理程序需求安装在回调函数上,来报告调用脚本,一切进展顺遂。

下一节的代码在清单1所示。getAll函数首先打开数据库和走访object
store,它为store和cursor(游标)分别设置值。为数据库游标设置游标变量允许迭代object
store中的数据。data变量设置为三个空数组,充当数据的容器,它回到给调用代码。

在store访问数据时,游标遍历数据库中的每条记下,会触发onsuccess事件处理程序。当每条记下走访时,store的数码能够透过e.target.result事件参数获得。即便事实上数据从target.result的value属性中赢得,首先须要在盘算访问value属性前确认保障result是贰个得力的值。假若result存在,您能够添加result的值到数据数组,然后在result对象上调用continue函数来继续迭代object
store。最后,尽管没有reuslt了,对store数据的迭代截止,同时数据传递到回调,回调被实践。

今昔模块能够从data
store得到全数数据,下三个急需贯彻的函数是肩负访问单个记录。

JavaScript

get: function (id, callback) { id = parseInt(id); db.open(function () {
var store = db.getObjectStore(), request = store.get(id);
request.onsuccess = function (e){ callback(e.target.result); }; }); },

1
2
3
4
5
6
7
8
9
10
11
get: function (id, callback) {
    id = parseInt(id);
    db.open(function () {
        var
            store = db.getObjectStore(),
            request = store.get(id);
        request.onsuccess = function (e){
            callback(e.target.result);
        };
    });
},

get函数执行的率先步操作是将id参数的值转换为2个平头。取决于函数被调用时,字符串或整数都或然传递给函数。那个完结跳过了对假若所给的字符串不能够转换成整数该怎么办的动静的拍卖。一旦一个id值准备好了,数据库打开了和object
store能够访问了。获取访问get请求出现了。请求成功时,通过传播e.target.result来实行回调。它(e.target.result)是通过调用get函数到手的单条记录。

近日封存和挑选操作已经冒出了,该模块还亟需从object store移除数量。

JavaScript

‘delete’: function (id, callback) { id = parseInt(id); db.open(function
() { var mode = ‘readwrite’, store, request; store =
db.getObjectStore(mode); request = store.delete(id); request.onsuccess =
callback; }); },

1
2
3
4
5
6
7
8
9
10
11
‘delete’: function (id, callback) {
    id = parseInt(id);
    db.open(function () {
        var
            mode = ‘readwrite’,
            store, request;
        store = db.getObjectStore(mode);
        request = store.delete(id);
        request.onsuccess = callback;
    });
},

delete函数的名号用单引号,因为delete是JavaScript的保留字。那足以由你来决定。您能够选用命名函数为del或任何名目,可是delete用在那一个模块为了API尽大概好的发挥。

传递给delete函数的参数是目的的id和二个回调函数。为了维持这一个实现不难,delete函数约定id的值为整数。您能够采取创造3个更强健的完毕来拍卖id值不能分析成整数的错误例子的回调,但为了指引原因,代码示例是明知故问的。

假定id值能担保转换来三个整数,数据库被打开,二个可写的object
store获得,delete函数字传送入id值被调用。当呼吁成功时,将实施回调函数。

在某个景况下,您大概需求删除一个object
store的有所的记录。在那种情状下,您访问store同时排除全体内容。

JavaScript

deleteAll: function (callback) { db.open(function () { var mode, store,
request; mode = ‘readwrite’; store = db.getObjectStore(mode); request =
store.clear(); request.onsuccess = callback; }); }

1
2
3
4
5
6
7
8
9
deleteAll: function (callback) {
    db.open(function () {
        var mode, store, request;
        mode = ‘readwrite’;
        store = db.getObjectStore(mode);
        request = store.clear();
        request.onsuccess = callback;
    });
}

此间deleteAll函数负责打开数据库和走访object
store的1个可写实例。一旦store可用,叁个新的伸手通过调用clear函数来创制。一旦clear操作成功,回调函数被实施。

        <title>小编的率先个页面</title>

执行用户界面特定代码

近来有着特定于数据库的代码被封装在app.db模块中,用户界面特定代码能够利用此模块来与数据库交互。用户界面特定代码的全部清单(index.ui.js)能够在清单3中赢得,完整的(index.html)页面包车型客车HTML源代码能够在清单4中收获。

        <style>

结论

趁着应用程序的急需的增高,你会发觉在客户端高效存款和储蓄大批量的多少的优势。IndexedDB是足以在浏览器中一向利用且协理异步事务的文书档案数据库达成。就算浏览器的支撑大概或无法保证,但在适用的情景下,集成IndexedDB的Web应用程序具有强大的客户端数据的拜访能力。

在大多数情景下,全部针对IndexedDB编写的代码是后天基于请求和异步的。官方正式有同步API,不过那种IndexedDB只适合web
worker的光景文中使用。那篇小说发表时,还未曾浏览器完毕的联名格式的IndexedDB
API。

必然要确认保证代码在其余函数域外对厂商特定的indexedDB, IDBTransaction, and
IDBKeyRange实例进行了规范化且使用了残忍形式。这允许你防止浏览器错误,当在strict
mode下解析脚本时,它不会容许你对那一个对象重新赋值。

您无法不确认保证只传递正整数的版本号给数据库。传递到版本号的小数值会四舍五入。由此,假设您的数据库如今版本1,您打算访问1.2版本,upgrade-needed事件不会触发,因为版本号最终评估是一模一样的。

及时施行函数表明式(IIFE)有时叫做分化的名字。有时能够寓目如此的代码组织格局,它叫做self-executing
anonymous functions(自进行匿名函数)或self-invoked anonymous
functions(自调用匿名函数)。为尤其表达这么些名称相关的来意和含义,请阅读Ben
Alman的作品Immediately Invoked Function Expression (IIFE) 。

Listing 1: Implementing the getAll function

JavaScript

getAll: function (callback) { db.open(function () { var store =
db.getObjectStore(), cursor = store.openCursor(), data = [];
cursor.onsuccess = function (e) { var result = e.target.result; if
(result && result !== null) { data.push(result.value);
result.continue(); } else { callback(data); } }; }); },

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
getAll: function (callback) {
 
    db.open(function () {
 
        var
            store = db.getObjectStore(),
            cursor = store.openCursor(),
            data = [];
 
        cursor.onsuccess = function (e) {
 
            var result = e.target.result;
 
            if (result &&
                result !== null) {
 
                data.push(result.value);
                result.continue();
 
            } else {
 
                callback(data);
            }
        };
 
    });
},

Listing 2: Full source for database-specific code
(index.db.js)

JavaScript

// index.db.js ; window.indexedDB = window.indexedDB ||
window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB;
window.IDBTransaction = window.IDBTransaction ||
window.webkitIDBTransaction || window.msIDBTransaction;
window.IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange ||
window.msIDBKeyRange; (function(window){ ‘use strict’; var db = {
version: 1, // important: only use whole numbers! objectStoreName:
‘tasks’, instance: {}, upgrade: function (e) { var _db =
e.target.result, names = _db.objectStoreNames, name =
db.objectStoreName; if (!names.contains(name)) { _db.createObjectStore(
name, { keyPath: ‘id’, autoIncrement: true }); } }, errorHandler:
function (error) { window.alert(‘error: ‘ + error.target.code);
debugger; }, open: function (callback) { var request =
window.indexedDB.open( db.objectStoreName, db.version); request.onerror
= db.errorHandler; request.onupgradeneeded = db.upgrade;
request.onsuccess = function (e) { db.instance = request.result;
db.instance.onerror = db.errorHandler; callback(); }; }, getObjectStore:
function (mode) { var txn, store; mode = mode || ‘readonly’; txn =
db.instance.transaction( [db.objectStoreName], mode); store =
txn.objectStore( db.objectStoreName); return store; }, save: function
(data, callback) { db.open(function () { var store, request, mode =
‘readwrite’; store = db.getObjectStore(mode), request = data.id ?
store.put(data) : store.add(data); request.onsuccess = callback; }); },
getAll: function (callback) { db.open(function () { var store =
db.getObjectStore(), cursor = store.openCursor(), data = [];
cursor.onsuccess = function (e) { var result = e.target.result; if
(result && result !== null) { data.push(result.value);
result.continue(); } else { callback(data); } }; }); }, get: function
(id, callback) { id = parseInt(id); db.open(function () { var store =
db.getObjectStore(), request = store.get(id); request.onsuccess =
function (e){ callback(e.target.result); }; }); }, ‘delete’: function
(id, callback) { id = parseInt(id); db.open(function () { var mode =
‘readwrite’, store, request; store = db.getObjectStore(mode); request =
store.delete(id); request.onsuccess = callback; }); }, deleteAll:
function (callback) { db.open(function () { var mode, store, request;
mode = ‘readwrite’; store = db.getObjectStore(mode); request =
store.clear(); request.onsuccess = callback; }); } }; window.app =
window.app || {}; window.app.db = db; }(window));

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
// index.db.js
 
;
 
window.indexedDB = window.indexedDB ||
                   window.mozIndexedDB ||
                   window.webkitIndexedDB ||
                   window.msIndexedDB;
 
window.IDBTransaction = window.IDBTransaction ||
                   window.webkitIDBTransaction ||
                   window.msIDBTransaction;
 
window.IDBKeyRange = window.IDBKeyRange ||
                   window.webkitIDBKeyRange ||
                   window.msIDBKeyRange;
 
(function(window){
 
    ‘use strict’;
 
    var db = {
 
        version: 1, // important: only use whole numbers!
 
        objectStoreName: ‘tasks’,
 
        instance: {},
 
        upgrade: function (e) {
 
            var
                _db = e.target.result,
                names = _db.objectStoreNames,
                name = db.objectStoreName;
 
            if (!names.contains(name)) {
 
                _db.createObjectStore(
                    name,
                    {
                        keyPath: ‘id’,
                        autoIncrement: true
                    });
            }
        },
 
        errorHandler: function (error) {
            window.alert(‘error: ‘ + error.target.code);
            debugger;
        },
 
        open: function (callback) {
 
            var request = window.indexedDB.open(
                db.objectStoreName, db.version);
 
            request.onerror = db.errorHandler;
 
            request.onupgradeneeded = db.upgrade;
 
            request.onsuccess = function (e) {
 
                db.instance = request.result;
 
                db.instance.onerror =
                    db.errorHandler;
 
                callback();
            };
        },
 
        getObjectStore: function (mode) {
 
            var txn, store;
 
            mode = mode || ‘readonly’;
 
            txn = db.instance.transaction(
                [db.objectStoreName], mode);
 
            store = txn.objectStore(
                db.objectStoreName);
 
            return store;
        },
 
        save: function (data, callback) {
 
            db.open(function () {
 
                var store, request,
                    mode = ‘readwrite’;
 
                store = db.getObjectStore(mode),
 
                request = data.id ?
                    store.put(data) :
                    store.add(data);
 
                request.onsuccess = callback;
            });
        },
 
        getAll: function (callback) {
 
            db.open(function () {
 
                var
                    store = db.getObjectStore(),
                    cursor = store.openCursor(),
                    data = [];
 
                cursor.onsuccess = function (e) {
 
                    var result = e.target.result;
 
                    if (result &&
                        result !== null) {
 
                        data.push(result.value);
                        result.continue();
 
                    } else {
 
                        callback(data);
                    }
                };
 
            });
        },
 
        get: function (id, callback) {
 
            id = parseInt(id);
 
            db.open(function () {
 
                var
                    store = db.getObjectStore(),
                    request = store.get(id);
 
                request.onsuccess = function (e){
                    callback(e.target.result);
                };
            });
        },
 
        ‘delete’: function (id, callback) {
 
            id = parseInt(id);
 
            db.open(function () {
 
                var
                    mode = ‘readwrite’,
                    store, request;
 
                store = db.getObjectStore(mode);
 
                request = store.delete(id);
 
                request.onsuccess = callback;
            });
        },
 
        deleteAll: function (callback) {
 
            db.open(function () {
 
                var mode, store, request;
 
                mode = ‘readwrite’;
                store = db.getObjectStore(mode);
                request = store.clear();
 
                request.onsuccess = callback;
            });
 
        }
    };
 
    window.app = window.app || {};
    window.app.db = db;
 
}(window));

Listing 3: Full source for user interface-specific code
(index.ui.js)

JavaScript

// index.ui.js ; (function ($, Modernizr, app) { ‘use strict’;
$(function(){ if(!Modernizr.indexeddb){
$(‘#unsupported-message’).show(); $(‘#ui-container’).hide(); return; }
var $deleteAllBtn = $(‘#delete-all-btn’), $titleText =
$(‘#title-text’), $notesText = $(‘#notes-text’), $idHidden =
$(‘#id-hidden’), $clearButton = $(‘#clear-button’), $saveButton =
$(‘#save-button’), $listContainer = $(‘#list-container’),
$noteTemplate = $(‘#note-template’), $emptyNote = $(‘#empty-note’);
var addNoTasksMessage = function(){ $listContainer.append(
$emptyNote.html()); }; var bindData = function (data) {
$listContainer.html(”); if(data.length === 0){ addNoTasksMessage();
return; } data.forEach(function (note) { var m = $noteTemplate.html(); m
= m.replace(/{ID}/g, note.id); m = m.replace(/{TITLE}/g, note.title);
$listContainer.append(m); }); }; var clearUI = function(){
$titleText.val(”).focus(); $notesText.val(”); $idHidden.val(”); }; //
select individual item $listContainer.on(‘click’, ‘a[data-id]’,
function (e) { var id, current; e.preventDefault(); current =
e.currentTarget; id = $(current).attr(‘data-id’); app.db.get(id,
function (note) { $titleText.val(note.title); $notesText.val(note.text);
$idHidden.val(note.id); }); return false; }); // delete item
$listContainer.on(‘click’, ‘i[data-id]’, function (e) { var id,
current; e.preventDefault(); current = e.currentTarget; id =
$(current).attr(‘data-id’); app.db.delete(id, function(){
app.db.getAll(bindData); clearUI(); }); return false; });
$clearButton.click(function(e){ e.preventDefault(); clearUI(); return
false; }); $saveButton.click(function (e) { var title =
$titleText.val(); if (title.length === 0) { return; } var note = {
title: title, text: $notesText.val() }; var id = $idHidden.val(); if(id
!== ”){ note.id = parseInt(id); } app.db.save(note, function(){
app.db.getAll(bindData); clearUI(); }); }); $deleteAllBtn.click(function
(e) { e.preventDefault(); app.db.deleteAll(function () {
$listContainer.html(”); addNoTasksMessage(); clearUI(); }); return
false; }); app.db.errorHandler = function (e) { window.alert(‘error: ‘ +
e.target.code); debugger; }; app.db.getAll(bindData); }); }(jQuery,
Modernizr, window.app));

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
// index.ui.js
 
;
 
(function ($, Modernizr, app) {
 
    ‘use strict’;
 
    $(function(){
 
        if(!Modernizr.indexeddb){
            $(‘#unsupported-message’).show();
            $(‘#ui-container’).hide();
            return;
        }
 
        var
          $deleteAllBtn = $(‘#delete-all-btn’),
          $titleText = $(‘#title-text’),
          $notesText = $(‘#notes-text’),
          $idHidden = $(‘#id-hidden’),
          $clearButton = $(‘#clear-button’),
          $saveButton = $(‘#save-button’),
          $listContainer = $(‘#list-container’),
          $noteTemplate = $(‘#note-template’),
          $emptyNote = $(‘#empty-note’);
 
        var addNoTasksMessage = function(){
            $listContainer.append(
                $emptyNote.html());
        };
 
        var bindData = function (data) {
 
            $listContainer.html(”);
 
            if(data.length === 0){
                addNoTasksMessage();
                return;
            }
 
            data.forEach(function (note) {
              var m = $noteTemplate.html();
              m = m.replace(/{ID}/g, note.id);
              m = m.replace(/{TITLE}/g, note.title);
              $listContainer.append(m);
            });
        };
 
        var clearUI = function(){
            $titleText.val(”).focus();
            $notesText.val(”);
            $idHidden.val(”);
        };
 
        // select individual item
        $listContainer.on(‘click’, ‘a[data-id]’,
 
            function (e) {
 
                var id, current;
 
                e.preventDefault();
 
                current = e.currentTarget;
                id = $(current).attr(‘data-id’);
 
                app.db.get(id, function (note) {
                    $titleText.val(note.title);
                    $notesText.val(note.text);
                    $idHidden.val(note.id);
                });
 
                return false;
            });
 
        // delete item
        $listContainer.on(‘click’, ‘i[data-id]’,
 
            function (e) {
 
                var id, current;
 
                e.preventDefault();
 
                current = e.currentTarget;
                id = $(current).attr(‘data-id’);
 
                app.db.delete(id, function(){
                    app.db.getAll(bindData);
                    clearUI();
                });
 
                return false;
        });
 
        $clearButton.click(function(e){
            e.preventDefault();
            clearUI();
            return false;
        });
 
        $saveButton.click(function (e) {
 
            var title = $titleText.val();
 
            if (title.length === 0) {
                return;
            }
 
            var note = {
                title: title,
                text: $notesText.val()
            };
 
            var id = $idHidden.val();
 
            if(id !== ”){
                note.id = parseInt(id);
            }
 
            app.db.save(note, function(){
                app.db.getAll(bindData);
                clearUI();
            });
        });
 
        $deleteAllBtn.click(function (e) {
 
            e.preventDefault();
 
            app.db.deleteAll(function () {
                $listContainer.html(”);
                addNoTasksMessage();
                clearUI();
            });
 
            return false;
        });
 
        app.db.errorHandler = function (e) {
            window.alert(‘error: ‘ + e.target.code);
            debugger;
        };
 
        app.db.getAll(bindData);
 
    });
 
}(jQuery, Modernizr, window.app));

Listing 3: Full HTML source (index.html)

JavaScript

<!doctype html> <html lang=”en-US”> <head> <meta
charset=”utf-8″> <meta http-equiv=”X-UA-Compatible”
content=”IE=edge”> <title>Introduction to
IndexedDB</title> <meta name=”description”
content=”Introduction to IndexedDB”> <meta name=”viewport”
content=”width=device-width, initial-scale=1″> <link
rel=”stylesheet”
href=”//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css”>
<link rel=”stylesheet” href=”//cdnjs.cloudflare.com/ajax/libs
/font-awesome/4.1.0/css/font-awesome.min.css” > <link
rel=”stylesheet” href=”//cdnjs.cloudflare.com/ajax/libs
/font-awesome/4.1.0/fonts/FontAwesome.otf” > <link
rel=”stylesheet” href=”//cdnjs.cloudflare.com/ajax/libs
/font-awesome/4.1.0/fonts/fontawesome-webfont.eot” > <link
rel=”stylesheet” href=”//cdnjs.cloudflare.com/ajax/libs
/font-awesome/4.1.0/fonts/fontawesome-webfont.svg” > <link
rel=”stylesheet” href=”//cdnjs.cloudflare.com/ajax/libs
/font-awesome/4.1.0/fonts/fontawesome-webfont.ttf” > <link
rel=”stylesheet” href=”//cdnjs.cloudflare.com/ajax/libs
/font-awesome/4.1.0/fonts/fontawesome-webfont.woff” > <style>
h1 { text-align: center; color:#999; } ul li { font-size: 1.35em;
margin-top: 1em; margin-bottom: 1em; } ul li.small { font-style: italic;
} footer { margin-top: 25px; border-top: 1px solid #eee; padding-top:
25px; } i[data-id] { cursor: pointer; color: #eee; }
i[data-id]:hover { color: #c75a6d; } .push-down { margin-top: 25px; }
#save-button { margin-left: 10px; } </style> <script
src=”//cdnjs.cloudflare.com/ajax/libs/modernizr /2.8.2/modernizr.min.js”
></script> </head> <body class=”container”>
<h1>Tasks</h1> <div id=”unsupported-message” class=”alert
alert-warning” style=”display:none;”> <b>Aww snap!</b>
Your browser does not support indexedDB. </div> <div
id=”ui-container” class=”row”> <div class=”col-sm-3″> <a
href=”#” id=”delete-all-btn” class=”btn-xs”> <i class=”fa
fa-trash-o”></i> Delete All</a> <hr/> <ul
id=”list-container” class=”list-unstyled”></ul> </div>
<div class=”col-sm-8 push-down”> <input type=”hidden”
id=”id-hidden” /> <input id=”title-text” type=”text”
class=”form-control” tabindex=”1″ placeholder=”title” autofocus
/><br /> <textarea id=”notes-text” class=”form-control”
tabindex=”2″ placeholder=”text”></textarea> <div
class=”pull-right push-down”> <a href=”#” id=”clear-button”
tabindex=”4″>Clear</a> <button id=”save-button” tabindex=”3″
class=”btn btn-default btn-primary”> <i class=”fa
fa-save”></i> Save</button> </div> </div>
</div> <footer class=”small text-muted text-center”>by <a
href=”” target=”_blank”>Craig
Shoemaker</a> <a href=””
target=”_blank”> <i class=”fa fa-twitter”></i></a>
</footer> <script id=”note-template” type=”text/template”>
<li> <i data-id=”{ID}” class=”fa fa-minus-circle”></i>
<a href=”#” data-id=”{ID}”>{TITLE}</a> </li>
</script> <script id=”empty-note” type=”text/template”>
<li class=”text-muted small”>No tasks</li> </script>
<script src=”//ajax.googleapis.com/ajax/libs
/jquery/1.11.1/jquery.min.js”></script> <script
src=”index.db.js” type=”text/javascript”></script> <script
src=”index.ui.js” type=”text/javascript”></script>
</body> </html>

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
<!doctype html>
<html lang="en-US">
    <head>
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <title>Introduction to IndexedDB</title>
        <meta name="description"
              content="Introduction to IndexedDB">
        <meta name="viewport"
              content="width=device-width, initial-scale=1">
        <link rel="stylesheet"
              href="//maxcdn.bootstrapcdn.com/bootstrap/3.2.0/css/bootstrap.min.css">
        <link rel="stylesheet"
              href="//cdnjs.cloudflare.com/ajax/libs
/font-awesome/4.1.0/css/font-awesome.min.css" >
        <link rel="stylesheet"
              href="//cdnjs.cloudflare.com/ajax/libs
/font-awesome/4.1.0/fonts/FontAwesome.otf" >
        <link rel="stylesheet"
              href="//cdnjs.cloudflare.com/ajax/libs
/font-awesome/4.1.0/fonts/fontawesome-webfont.eot" >
        <link rel="stylesheet"
              href="//cdnjs.cloudflare.com/ajax/libs
/font-awesome/4.1.0/fonts/fontawesome-webfont.svg" >
        <link rel="stylesheet"
              href="//cdnjs.cloudflare.com/ajax/libs
/font-awesome/4.1.0/fonts/fontawesome-webfont.ttf" >
        <link rel="stylesheet"
              href="//cdnjs.cloudflare.com/ajax/libs
/font-awesome/4.1.0/fonts/fontawesome-webfont.woff" >
        <style>
            h1 {
                text-align: center;
                color:#999;
            }
 
            ul li {
                font-size: 1.35em;
                margin-top: 1em;
                margin-bottom: 1em;
            }
 
            ul li.small {
                font-style: italic;
            }
 
            footer {
                margin-top: 25px;
                border-top: 1px solid #eee;
                padding-top: 25px;
            }
 
            i[data-id] {
                cursor: pointer;
                color: #eee;
            }
 
            i[data-id]:hover {
                color: #c75a6d;
            }
 
            .push-down {
                margin-top: 25px;
            }
 
            #save-button {
                margin-left: 10px;
            }
        </style>
        <script src="//cdnjs.cloudflare.com/ajax/libs/modernizr
/2.8.2/modernizr.min.js" ></script>
    </head>
    <body class="container">
        <h1>Tasks</h1>
        <div id="unsupported-message"
             class="alert alert-warning"
             style="display:none;">
            <b>Aww snap!</b> Your browser does not support indexedDB.
        </div>
        <div id="ui-container" class="row">
            <div class="col-sm-3">
 
                <a href="#" id="delete-all-btn" class="btn-xs">
                    <i class="fa fa-trash-o"></i> Delete All</a>
 
                <hr/>
 
                <ul id="list-container" class="list-unstyled"></ul>
 
            </div>
            <div class="col-sm-8 push-down">
 
                <input type="hidden" id="id-hidden" />
 
                <input
                       id="title-text"
                       type="text"
                       class="form-control"
                       tabindex="1"
                       placeholder="title"
                       autofocus /><br />
 
                <textarea
                          id="notes-text"
                          class="form-control"
                          tabindex="2"
                          placeholder="text"></textarea>
 
                <div class="pull-right push-down">
 
                    <a href="#" id="clear-button" tabindex="4">Clear</a>
 
                    <button id="save-button"
                            tabindex="3"
                            class="btn btn-default btn-primary">
                                <i class="fa fa-save"></i> Save</button>
                </div>
            </div>
        </div>
        <footer class="small text-muted text-center">by
            <a href="http://craigshoemaker.net" target="_blank">Craig Shoemaker</a>
            <a href="http://twitter.com/craigshoemaker" target="_blank">
                <i class="fa fa-twitter"></i></a>
        </footer>
        <script id="note-template" type="text/template">
            <li>
                <i data-id="{ID}" class="fa fa-minus-circle"></i>
                <a href="#" data-id="{ID}">{TITLE}</a>
            </li>
        </script>
        <script id="empty-note" type="text/template">
            <li class="text-muted small">No tasks</li>
        </script>
        <script src="//ajax.googleapis.com/ajax/libs
/jquery/1.11.1/jquery.min.js"></script>
        <script src="index.db.js" type="text/javascript"></script>
        <script src="index.ui.js" type="text/javascript"></script>
    </body>
</html>

赞 1 收藏
评论

                img{width:80px}

至于作者:cucr

亚洲城ca88 8

果壳网今日头条:@hop_ping
个人主页 ·
小编的篇章 ·
17

亚洲城ca88 9

        </style>

    </head>

    <body>

        <h1>笔者的率先个页面</h1>

        <p>我的率先个段落</p>

        <h2>一点点</h2>

        <ul>

                <li>茉香绿茶</li>

                <li>抹茶拿铁</li>

                <li>

                        今日特殊供应

                        <ul>

网站地图xml地图