写点什么

WEB 本地存储:localStorage、Web SQL Database、IndexedDB

作者:devpoint
  • 2021 年 11 月 09 日
  • 本文字数:7899 字

    阅读完需:约 26 分钟

WEB本地存储:localStorage、Web SQL Database、IndexedDB

在 HTML5 之前,应用程序数据只能存储在 cookie 中,并且会包含在每个服务器请求中。与 cookie 不同,浏览器本地存储限制要大得多(至少 5MB),并且信息不会被传输到服务器。本文将要介绍的本地存储包括:localStorageWeb SQL DatabaseIndexedDB

localStorage

localStorage 在一般浏览器支持的是 5M 大小,在不同的浏览器中 localStorage 会有所不同。


localstorage 的 API 有两个:localStoragesessionStorage,存在于window对象中:localStorage对应window.localStoragesessionStorage对应window.sessionStorage


localStoragesessionStorage的区别主要是在于其生存期,localStorage属于永久性存储,而sessionStorage当会话结束的时候,sessionStorage中的键值对会被清空。


localstorage为标准的键值对(Key-Value,简称 KV)数据类型,简单也易扩展,只要以某种编码方式把想要存储进localstorage的对象给转化成字符串,就能轻松支持。


由于浏览器的安全策略,localstorage 是无法跨域的,也无法让子域名继承父域名的 localstorage 数据。


在使用方面,sessionStoragelocalStorage方法是一样的。

初始化

在浏览器中使用需要判断兼容性,判断浏览器是否支持。


if(!window.localStorage){    alert("浏览器支持localstorage");    return false;}
复制代码

数据存储操作

数据写入的方法就是直接给window.localStorage添加一个属性,例如:window.localStorage.a 或者 window.localStorage["b"]。它的读取、写、删除操作方法很简单,是以键值对的方式存在的,如下:


const storage=window.localStorage;//写入a字段storage["a"]=1;//写入b字段storage.b=1;//写入c字段storage.setItem("c",3);console.log(typeof storage["a"]);console.log(typeof storage["b"]);console.log(typeof storage["c"]);
storage.getItem("a");
复制代码


运行后再开发工具下可以看到:



写入数据推荐使用setItem(),读取数据推荐使用getItem(),清除键值对使用removeItem()。如果希望一次性清除所有的键值对,可以使用clear()。另外,HTML5 还提供了一个key()方法,可以在不知道有哪些键值的时候使用,如下:


const storage = window.localStorage;function showStorage(){    for(var i=0;i<storage.length;i++){        //key(i)获得相应的键,再用getItem()方法获得对应的值        console.log(storage.key(i)+ " : " + storage.getItem(storage.key(i)) + "<br>");    }}
复制代码


需要注意的是,HTML5 本地存储只能存字符串,任何格式存储的时候都会被自动转为字符串,所以读取的时候,需要自己进行类型的转换。


对于json数据侧存储需要调用JSON.stringify()将其转为字符串后才能存储,读取出来后调用JSON.parse()将字符串转为 json 格式。

storage 事件

事件可以对键值对的改变进行监听,使用方法如下:


if(window.addEventListener){    window.addEventListener("storage",handle_storage,false);}else if(window.attachEvent){    window.attachEvent("onstorage",handle_storage);}function handle_storage(e){    if(!e){e=window.event;}    //逻辑}
复制代码


对于事件变量 e,是一个StorageEvent对象,提供了一些实用的属性,可以很好的观察键值对的变化,如下表:




Web SQL Database

Web SQL Database 实际上已经被废弃


虽然 Html5 已经提供了功能强大的localStoragesessionStorage,但是他们两个都只能提供存储简单数据结构的数据,对于复杂的 Web 应用的数据却无能为力。逆天的是 Html5 提供了一个浏览器端的数据库支持,允许直接通 JavaScript 的 API 在浏览器端创建一个本地的数据库,而且支持标准的SQLCRUD操作,让离线的 Web 应用更加方便的存储结构化的数据。接下里介绍一下本地数据的相关 API 和用法。


  • 第一步:openDatabase方法:创建一个访问数据库的对象。

  • 第二步:使用第一步创建的数据库访问对象来执行transaction方法,通过此方法可以设置一个开启事务成功的事件响应方法,在事件响应方法中可以执行 SQL.

  • 第三步:通过executeSql方法执行查询,当然查询可以是:CRUD

openDatabase 方法


初次调用时创建数据库,以后就是建立连接了。


//Demo:获取或者创建一个数据库,如果数据库不存在那么创建之const dataBase = openDatabase("student", "1.0", "学生表", 1024 * 1024, function () { });
复制代码


openDatabase方法打开一个已经存在的数据库,如果数据库不存在,它还可以创建数据库。几个参数意义分别是:


  1. 数据库名称。

  2. 数据库的版本号,目前来说传个 1.0 就可以了,当然可以不填;

  3. 对数据库的描述。

  4. 设置分配的数据库的大小(单位是 kb)。

  5. 回调函数(可省略)。


db.transaction方法可以设置一个回调函数,此函数可以接受一个参数就是我们开启的事务的对象。然后通过此对象可以进行执行 Sql 脚本,跟下面的步骤可以结合起来。

通过 executeSql 方法执行查询

ts.executeSql(sqlQuery,[value1,value2..],dataHandler,errorHandler)
复制代码


参数说明:


  • qlQuery:需要具体执行的 sql 语句,可以是createselectupdatedelete

  • [value1,value2..]:sql 语句中所有使用到的参数的数组,在executeSql方法中,将s>语句中所要使用的参数先用“?”代替,然后依次将这些参数组成数组放在第二个参数中

  • dataHandler:执行成功是调用的回调函数,通过该函数可以获得查询结果集;

  • errorHandler:执行失败时调用的回调函数;

IndexedDB

Web Storage(Local Storage 和 Session Storage)与IndexedDBWeb Storage使用简单字符串键值对在本地存储数据,方便灵活,但是对于大量结构化数据存储力不从心,IndexedDB是为了能够在客户端存储大量的结构化数据,并且使用索引高效检索的 API。

异步 API

IndexedDB大部分操作并不是我们常用的调用方法,返回结果的模式,而是请求——响应的模式,比如打开数据库的操作


const request=window.indexedDB.open('testDB');
复制代码


这条指令并不会返回一个 DB 对象的句柄,我们得到的是一个IDBOpenDBRequest对象,而我们希望得到的DB对象在其result属性中



这条指令请求的响应是一个 IDBDatabase 对象,这就是IndexedDB对象



除了resultIDBOpenDBRequest接口定义了几个重要属性


  • onerror: 请求失败的回调函数句柄

  • onsuccess:请求成功的回调函数句柄

  • onupgradeneeded:请求数据库版本变化句柄


所谓异步 API 是指并不是这条指令执行完毕,我们就可以使用 request.result 来获取 indexedDB 对象了,就像使用 ajax 一样,语句执行完并不代表已经获取到了对象,所以我们一般在其回调函数中处理。

创建数据库

刚才的语句已经展示了如何打开一个indexedDB数据库,调用indexedDB.open方法就可以创建或者打开一个indexedDB。看一个完整的处理


function openDB(name) {    const request = window.indexedDB.open(name);    request.onerror = function (e) {        console.log("OPen Error!");    };    request.onsuccess = function (e) {        myDB.db = e.target.result;    };}
const myDB = { name: "test", version: 1, db: null,};openDB(myDB.name);
复制代码


代码中定义了一个myDB对象,在创建indexedDB request的成功毁掉函数中,把request获取的DB对象赋值给了myDBdb属性,这样就可以使用myDB.db来访问创建的indexedDB了。

version

我们注意到除了onerroronsuccessIDBOpenDBRequest还有一个类似回调函数句柄——onupgradeneeded。这个句柄在我们请求打开的数据库的版本号和已经存在的数据库版本号不一致的时候调用。


indexedDB.open()方法还有第二个可选参数,数据库版本号,数据库创建的时候默认版本号为1,当传入的版本号和数据库当前版本号不一致的时候onupgradeneeded就会被调用,当然不能试图打开比当前数据库版本低的version,否则调用的就是onerror了,修改一下刚才例子


function openDB (name,version) {    const version=version || 1;    const request=window.indexedDB.open(name,version);    request.onerror=function(e){        console.log(e.currentTarget.error.message);    };    request.onsuccess=function(e){        myDB.db=e.target.result;    };    request.onupgradeneeded=function(e){        console.log('DB version changed to '+version);    };} const myDB={    name:'test',    version:3,    db:null};openDB(myDB.name,myDB.version);
复制代码


由于刚才已经创建了版本为1的数据库,打开版本为3的时候,会在控制台输出:DB version changed to 3

关闭与删除数据库

关闭数据库可以直接调用数据库对象的 close 方法


function closeDB(db){    db.close();}
复制代码


删除数据库使用indexedDB对象的deleteDatabase方法


function deleteDB(name){    indexedDB.deleteDatabase(name);}
复制代码


简单调用


const myDB={    name:'test',    version:3,    db:null};openDB(myDB.name,myDB.version);setTimeout(function(){    closeDB(myDB.db);    deleteDB(myDB.name);},500);
复制代码


由于异步 API 愿意,不能保证能够在closeDB方法调用前获取db对象(实际上获取 db 对象也比执行一条语句慢得多),所以用了setTimeout延迟了一下。当然我们注意到每个indexedDB实例都有onclose回调函数句柄,用以数据库关闭的时候处理。

object store

有了数据库后我们自然希望创建一个表用来存储数据,但indexedDB中没有表的概念,而是objectStore,一个数据库中可以包含多个objectStoreobjectStore是一个灵活的数据结构,可以存放多种类型数据。也就是说一个objectStore相当于一张表,里面存储的每条数据和一个键相关联。


可以使用每条记录中的某个指定字段作为键值(keyPath),也可以使用自动生成的递增数字作为键值(keyGenerator),也可以不指定。选择键的类型不同,objectStore可以存储的数据结构也有差异



事务

在对新数据库做任何事情之前,需要开始一个事务。事务中需要指定该事务跨越哪些 object store。


事务具有三种模式


  • 只读:read,不能修改数据库数据,可以并发执行

  • 读写:readwrite,可以进行读写操作

  • 版本变更:verionchange


const transaction=db.transaction([students','taecher']);  //打开一个事务,使用students 和teacher object storeconst objectStore=transaction.objectStore('students'); //获取students object store
复制代码
给 object store 添加数据

调用数据库实例的createObjectStore方法可以创建object store,方法有两个参数:store name和键类型。调用storeadd方法添加数据。有了上面知识,可以向object store内添加数据了

keyPath

因为对新数据的操作都需要在transaction中进行,而transaction又要求指定object store,所以只能在创建数据库的时候初始化object store以供后面使用,这正是onupgradeneeded的一个重要作用,修改一下之前代码


function openDB (name,version) {    const version=version || 1;    const request=window.indexedDB.open(name,version);    request.onerror=function(e){        console.log(e.currentTarget.error.message);    };    request.onsuccess=function(e){        myDB.db=e.target.result;    };    request.onupgradeneeded=function(e){        const db=e.target.result;        if(!db.objectStoreNames.contains('students')){            db.createObjectStore('students',{keyPath:"id"});        }        console.log('DB version changed to '+version);    };}
复制代码


这样在创建数据库的时候就为其添加了一个名为studentsobject store,准备一些数据以供添加


const students=[{     id:1001,     name:"Byron",     age:24 },{     id:1002,     name:"Frank",     age:30 },{    id:1003,     name:"Aaron",     age:26 }];
复制代码


function addData(db,storeName){    const transaction=db.transaction(storeName,'readwrite');     const store=transaction.objectStore(storeName); 
for(var i=0;i<students.length;i++){ store.add(students[i]); }}openDB(myDB.name,myDB.version);setTimeout(function(){ addData(myDB.db,'students');},1000);
复制代码


这样就在students object store里添加了三条记录,以id为键,在 chrome 控制台看看效果


keyGenerate
function openDB (name,version) {    const version=version || 1;    const request=window.indexedDB.open(name,version);    request.onerror=function(e){        console.log(e.currentTarget.error.message);    };    request.onsuccess=function(e){        myDB.db=e.target.result;    };    request.onupgradeneeded=function(e){        const db=e.target.result;        if(!db.objectStoreNames.contains('students')){            db.createObjectStore('students',{autoIncrement: true});        }        console.log('DB version changed to '+version);    };}
复制代码


查找数据

可以调用object store的 get 方法通过键获取数据,以使用keyPath做键为例:


function getDataByKey(db,storeName,value){    const transaction=db.transaction(storeName,'readwrite');     const store=transaction.objectStore(storeName);     const request=store.get(value);     request.onsuccess=function(e){         var student=e.target.result;         console.log(student.name);     };}
复制代码
更新数据

可以调用object storeput方法更新数据,会自动替换键值相同的记录,达到更新目的,没有相同的则添加,以使用 keyPath 做键为例


function updateDataByKey(db, storeName, value) {    const transaction = db.transaction(storeName, "readwrite");    const store = transaction.objectStore(storeName);    const request = store.get(value);    request.onsuccess = function (e) {        const student = e.target.result;        student.age = 35;        store.put(student);    };}
复制代码
删除数据及 object store

调用object storedelete方法根据键值删除记录


function deleteDataByKey(db, storeName, value) {    const transaction = db.transaction(storeName, "readwrite");    const store = transaction.objectStore(storeName);    store.delete(value);}
复制代码


调用object storeclear方法可以清空object store


function clearObjectStore(db, storeName) {    const transaction = db.transaction(storeName, "readwrite");    const store = transaction.objectStore(storeName);    store.clear();}
复制代码


调用数据库实例的deleteObjectStore方法可以删除一个object store,这个就得在onupgradeneeded里面调用了


if (db.objectStoreNames.contains("students")) {    db.deleteObjectStore("students");}
复制代码
创建索引

可以在创建object store的时候指明索引,使用object storecreateIndex创建索引,方法有三个参数:


  • 索引名称

  • 索引属性字段名

  • 索引属性值是否唯一


function openDB(name, version) {    const version = version || 1;    const request = window.indexedDB.open(name, version);    request.onerror = function (e) {        console.log(e.currentTarget.error.message);    };    request.onsuccess = function (e) {        myDB.db = e.target.result;    };    request.onupgradeneeded = function (e) {        const db = e.target.result;        if (!db.objectStoreNames.contains("students")) {            const store = db.createObjectStore("students", { keyPath: "id" });            store.createIndex("nameIndex", "name", { unique: true });            store.createIndex("ageIndex", "age", { unique: false });        }        console.log("DB version changed to " + version);    };}
复制代码


利用索引获取数据


function getDataByIndex(db, storeName) {    const transaction = db.transaction(storeName);    const store = transaction.objectStore(storeName);    const index = store.index("nameIndex");    index.get("Byron").onsuccess = function (e) {        const student = e.target.result;        console.log(student.id);    };}
复制代码


这样可以利用索引快速获取数据,name的索引是唯一的没问题,但是对于age索引只会取到第一个匹配值,要想得到所有 age 符合条件的值就需要使用游标了

游标

indexedDB中使用索引和游标是分不开的,对数据库熟悉的同学很好理解游标是什么东东,有了数据库object store的游标,就可以利用游标遍历 object store 了。


使用 object store 的openCursor()方法打开游标


function fetchStoreByCursor(db, storeName) {    const transaction = db.transaction(storeName);    const store = transaction.objectStore(storeName);    const request = store.openCursor();    request.onsuccess = function (e) {        const cursor = e.target.result;        if (cursor) {            console.log(cursor.key);            const currentStudent = cursor.value;            console.log(currentStudent.name);            cursor.continue();        }    };}
复制代码


curson.contine()会使游标下移,直到没有数据返回undefined

index 与游标结合

要想获取age26student,可以结合游标使用索引


function getMultipleData(db, storeName) {    const transaction = db.transaction(storeName);    const store = transaction.objectStore(storeName);    const index = store.index("ageIndex");    const request = index.openCursor(IDBKeyRange.only(26));    request.onsuccess = function (e) {        const cursor = e.target.result;        if (cursor) {            const student = cursor.value;            console.log(student.id);            cursor.continue();        }    };}
复制代码


这样可是使用索引打开一个游标,参数下面会讲到,在成功的句柄内获得游标便利age为 26 的student,也可以通过index.openKeyCursor()方法只获取每个对象的key值。

指定游标范围

index.openCursor()/index.openKeyCursor()方法在不传递参数的时候会获取object store所有记录,像上面例子一样我们可以对搜索进行筛选。


可以使用key range 限制游标中值的范围,把它作为第一个参数传给 openCursor() 或是 openKeyCursor()


  • IDBKeyRange.only(value):只获取指定数据

  • IDBKeyRange.lowerBound(value,isOpen):获取最小是value的数据,第二个参数用来指示是否排除value值本身,也就是数学中的是否是开区间

  • IDBKeyRange.upperBound(value,isOpen):和上面类似,用于获取最大值是 value 的数据

  • IDBKeyRange.bound(value1,value2,isOpen1,isOpen2):不用解释了吧


获取名字首字母在 B-E 的 student


function getMultipleData(db, storeName) {    const transaction = db.transaction(storeName);    const store = transaction.objectStore(storeName);    const index = store.index("nameIndex");    const request = index.openCursor(IDBKeyRange.bound("B", "F", false, true));    request.onsuccess = function (e) {        const cursor = e.target.result;        if (cursor) {            const student = cursor.value;            console.log(student.name);            cursor.continue();        }    };}
复制代码

发布于: 2021 年 11 月 09 日阅读数: 11
用户头像

devpoint

关注

细节的追求者 2011.11.12 加入

专注前端开发,用技术创造价值!

评论

发布
暂无评论
WEB本地存储:localStorage、Web SQL Database、IndexedDB