初探HTML5的本地存储

本文深入探讨HTML5的本地存储技术,包括localStorage、sessionStorage、WebSQL及IndexedDB的使用方法与应用场景,提供了丰富的示例代码。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >


======================================================
注:本文源代码点此下载
======================================================

传统的html使用的是众所周知的cookie,各种浏览器都支持,直接用js就可以调用,很方便。但cookie也有它本身的缺陷与不足。比如存储空间小,每个站点大小限制在4 kb左右,又有时间期限,而且在请求网页的时候cookie会被附加在每个http请求的header中,所以无形中增加了流量,在http请求中的cookie是明文传递的,所以安全性成问题,当然你可以用ssl通道,这另当别论。cookie还很容易受到跨站脚本的攻击,在一个链接地址后面加上“?cookie=document.cookie”就可以很容易地获得用户的cookie信息。当然, html5的本地存储可能也会有跨站脚本攻击xss的问题,这个不是本文讨论的主要内容,以后我们一起慢慢研究。

长久以来本地存储能力一直是桌面应用有别于web应用的一个主要优势,前者可以自由的操作本地文件系统,存储和读取都很方便,比如ini、xml等本地配置文件,存于本地,不需要在每次用的时候都要去服务器上取配置数据。web应用就不同,为了安全,浏览器都不会让网页脚本去调用本地的文件系统,除了cookie,自然也不能存储或使用本地配置文件,用户的个性化设置一般都是存在浏览器上。再比如,用户在网页上编辑一篇很长的文章,编辑的过程中网页要自动帮用户保存。存哪?本地cookie很可能不够用,空间太小,而且还有时候期限,所以只能保存到服务器上去。

html5的新标准可以很好地解决上面的一些问题。当然,本地存储还可以解决更多我暂时没提到的问题,留给读者自己去探索。本地存储可以存储5m大小的数据,甚至还可以更多。 它主要有四种:localstorage , sessionstorage, websql , indexeddb。

1、localstorage

用window.localstorage,但浏览器可能不支持,它就有可能是空,所以我们先做个判断。

1 function getlocalstorage() {

2try {

3if( !! window.localstorage ) return window.localstorage;

4} catch(e) {

5return undefined;

6}

7 }

(两个感叹号用于判断变量是否为null、undefined、''、0,个人理解,这是书写习惯的问题,把它去掉也没错。这里是为了规范if()里面一般是放bool类型的,而js比较灵活,上面提到的那四种语法上当作false来处理,在它们前面加上一个感叹号,则变成真正的bool类型,但是true,所以再加个感叹号,变回false,不是上面四种类型的都变true)

存数据。

1 var db = getlocalstorage();

2 if(db) {

3db.setitem('author', 'jasonling');

4db.setitem('company', 'tencent');

5db.setitem('introduction', 'a code lover !');

6 }

其实,localstorage就是键值对,它的值可以存大小多达5m的字符串。事实上,它是存到一个sqlite的文件中去,用sqlite打开可以看到里面自己建了一个表,里面有我们刚刚存的数据。

chrome浏览器的这个sqlite文件存在:c:/users/你的用户名/appdata/local/google/chrome/user data/default/local storage 里面,如上图,用sqlite打开,用.schema可以看到一个表,这是chrome帮我们建的用来存储键值对的表itemtable,查询表里的数据可以发现我们刚刚用程序存进去的值。

其它浏览器中,sqlite文件的存储位置,读者可以自己去探索探索。

localstorage的其它操作:

view code

1 db.setitem('author', 'jasonling');

2 db.setitem('company', 'tencent');

3 db.setitem('introduction', 'a code lover !');

4 //取值

5 alert(localstorage['author']);

6 alert(db.getitem('company'));

7 //删除

8 db.removeitem('company');

9 alert(db.getitem('company'));

10 //当然也可以用db.setitem('company', '');来删除一个值,但这样删不彻底。

11 //清除

12 db.clear();

13 alert(localstorage['author']); //undefined

在浏览器上同时打开两个页面(lynda.com上的一个示例),如图:

这两个页面都是一样的,当我用在一个页面中进行修改,另一个页面的数据会不会自动修改呢?

显然不会,为了达到自动更新的效果,我们得用事件监听器。

一样的道理,先要判断浏览器支不支持:

1 //get the addeventlistener

2 function getaddeventhandler() {

3try {

4if( !! window.addeventlistener ) return window.addeventlistener;

5} catch(e) {

6return undefined;

7}

8 }

写一个handler函数.

1 //eventstatus

2 function eventstatus(s) {

3if(s) element('eventresult').innerhtml = s;

4else element('eventresult').innerhtml = 'event status';

5 }

6

7 //event handler

8 function eventhandler(e) {

9eventstatus('event triggered: ' + e.url + ' ' +

10e.storagearea.traveler + ' ' + e.storagearea.destination + ' ' + e.storagearea.transportation);

11dispresults();

12 }

eventstatus用来作判断,当字符串s有问题时,就是else,当它没问题时就if。为了进一步说明,我写了下面一个例子:

1 function test(s) {

2if(a) {

3alert('if');

4} else {

5alert('else');

6}

7 }

8 var a = window.abc; //window.abc不存在,是有问题的,所以,可以想像,下面的结果是else

9 test(a);

这个其实是js的一个技巧,有问题的字符串,在if里面当false处理。这要和未定义的变量放if里面区别开来,如果一个变量未定义,那么不管放哪,js都会停止执行。如,如果上面没有定义b,即没有var b; 这句,下面突然出现了个if(b)那么程序到这里就会中断。大部分浏览器是这样的。回到正题上来:

1 var addel = getaddeventhandler(); //这是上面写的一个函数

2 if(addel) {

3addel('storage',eventhandler,false);//给storage事件加上监听器

4 } else {

5element('eventresult').innerhtml = 'this browser does not support event listeners';

6 }

7 dispresults(); //这是自定义的一个函数,读者不用管,作用是显示结果

这样,每次local storage里面的数据变化时,浏览器里面的数据就会自动变化了,因为storage事件被监听后,浏览器就会时时监视local storage里的数据,如果变化,就会触发事件,修改页面。不管几个页面都会做出相应的修改。

要注意一点,这个事件只有在同一个浏览器程序里面才有效(比如你不能在chrome和firefox各打开一个页面,然后等着事件生效),因为不同浏览器的sqlite文件不一样,各自修改自己的数据,当然不会对其它浏览器的数据造成影响。

经过上面的介绍,大家可以看出,忽略事件的内容外,localstorage的操作其实也很简单,和cookie一样很容易操作。下面介绍的几个会一个比一个难,这里先做好心里准备。

2、sessionstorage

localstorage的数据是页面共享的,但有些情况,我们需要一个浏览器中的不同页面可以单独操作自己的数据。这时我们就可以用sessionstorage了,它存储的数据只有当前页面可以访问。

第一步还是一样,判断浏览器支不支持:

1 function getsessionstorage() {

2try {

3if( !! window.sessionstorage ) return window.sessionstorage;

4} catch(e) {

5return undefined;

6}

7 }

然后:

1 var db = getsessionstorage();

2 if(db) {

3db.setitem('author','jasonling');

4db.setitem('company', 'tencent');

5db.setitem('introduction', 'a code lover!');

6 }

7 //和localstorage一样的操作

然后打开浏览器,同时打开两个网页做测试,我们可以看到,这两个页面中的数据不会相互影响。还是用lynda.com的一个例子来展示:

再怎么刷新这两个页面,他们之间的数据还是不会相互影响。

那么,sessionstorage把数据存哪了?经过测试发现,它保存在

c:\users\你的用户名\appdata\local\google\chrome\user data\default\current session 这个文件里面。但它不是一个sqlite文件,因为用sqlite打不开。具体怎么存的,作者本人也没研究出来,如有高手知道,请留言告之。

3、websql

其实就是sqlite数据库,在js中可以像java在本地调用mysql数据库一样的方便,可以自己创建数据库,自己建表,自己往里面增删改数据。

第一步还是要判断一下浏览器是否支持:

1 function getopendatabase() {

2try {

3if( !! window.opendatabase ) return window.opendatabase;

4else return undefined;

5} catch(e) {

6return undefined;

7}

8 }

如果支持,就试着打开数据库连接:

1 function preparedatabase() {

2var odb = getopendatabase();

3if(!odb) {

4disperror('web sql not supported');

5return undefined;

6} else {

7var db = odb( 'testdatabase', '1.0', 'a test database', 10 * 1024 * 1024 );

8db.transaction(function (t) {

9t.executesql( createsql, [], function(t, r) {}, function(t, e) {

10alert('create table: ' + e.message);

11});

12});

13return db;

14}

15 }

其中,odb('testdatabase', '1.0','a test database',10*1024*1024);试着打开(新建)一个数据库数据库,四个参数分别表示:数据库名,数据库版本,数据库描述,和它预定的大小。

db.transaction那一句,是websql中最常用的语法。createsql是一个用来创建数据库的字符串:

var createsql = 'create table if not exists ttravel (' +

'id integer primary key,' +

'traveler text,' +

'destination text,' +

'transportation text' +

')';

transaction这个函数,我在网上找了很多资料,都说是后面只能带一个参数,但我测试之后,发现是可以带三个参数,第二个是错误处理函数,两参(t,e),分别表示transaction和error,第三个成功回调函数,无参。。重点是第一个参数,是一个方程,四个参数:

function(t) {

t.executesql("要执行的sql语句_需要参数的地主用?代替",[参数_用逗号隔开],function(t,r){成功回调函数_t表示transaction_r是result},function(t,e){出错回调函数_t表示transaction_e是error});

}

比如用用户名ling和密码mypwd来登陆,可以这样写:

1 function(t) {

2t.executesql('select * from tuser where name=? and pwd=?',['ling','mypwd'],

3function(t,r) {

4alert('登陆成功');

5},

6function(t,e) {

7alert(e.message);

8}

9 }

很方便,完全可以使用sql语句来操作。

回到原来的话题,var db = preparedatabase(); 来得到我们在数据库中建的表ttravel。接下去我们就可以用它来操作这个表。下面举一些例子:(其中有些这是lynda.com提供的一些例子,bwtable是它自己实现的一个漂亮的表格,用来显示数据,读者不用深究这个,我会把最后的代码一起传上来)

(1)计算表里面有几行记录:

1 //get the count of rows

2 function countrows(){

3if(!db) return ;

4db.readtransaction(function(t) {

5t.executesql('select count(*) as c from ttravel',[],function(t,r) {

6var c = r.rows.item(0).c; //result 结果的第一行的c那一列

7element('rowcount').innerhtml = c? c:0;

8},function(t,e) {

9alert('countrows:' + e.message);

10});

11});

12 }

语法和上面提到的一样,只是这边用的是readtransaction,这是为了保证不对表进行写操作,这是一种安全的举措,当然也可以用transaction。

(2)取出表中的数据:

1 if(db) {

2db.readtransaction(function(t){

3t.executesql('select * from ttravel order by lower(traveler)',[],function(t,r) {

4var mytab = new bwtable();

5mytab.setheader(['traveler','destination','transportation','']);

6

7for(var i = 0; ir.rows.length; i ++){

8var row = r.rows.item(i);

9mytab.addrow([row.traveler,row.destination,row.transportation,rowbtn(row.id,row.traveler)]);

10}

11element('results').innerhtml = mytab.gettablehtml();

12element("travelform").elements['traveler'].focus();

13},function(t,e) {

14alert("can't get the data");

15});

16});

17 }

(3)往表里添加数据:

1 if(db) {

2db.transaction(function(t) {

3t.executesql('insert into ttravel values(null,?,?,?)',

4[traveler,destination,transportation],

5function(t,r) {resetform();},//执行成功时把表格清空,自己实现的一个函数

6function(t,e){

7alert("insert rows:"+e.message);

8});

9});

10 }

(4)更新数据:

1 db.transaction(function(t){

2t.executesql('update ttravel set traveler=?,destination=?,transportation=? where id=?',

3[traveler, destination, transportation,inputkey]);

4 },function(t,e){

5alert('update row:'+e.message);

6 },function(){resetform();});

(5)删除数据:

1 if(db) {

2db.transaction(function(t){

3t.executesql('delete from ttravel where id=?',[id],

4function(t,r){ alert('successfully!');});

5});

6 }

其实很容易,就是简单地用sql来操作数据库。

chrome的websql本地数据存在这个目录下:c:\users\你的用户名\appdata\local\google\chrome\user data\default\databases文件夹,里面一般会有file__0等类似的文件夹,可以用sqlite打开来看看。

4、indexeddb

有一篇文章《indexed db:未来一切 web 应用的基石》在总体上介绍了一下indexeddb,读者可以去网搜一搜,读一读,大概了解一下它的前世今生!我摘录了《html5之indexeddb使用详解》里面的一些内容来简单的介绍一下:它是html5-webstorage的重要一环,是一种轻量级nosql数据库,w3c为indexeddb定义了很多接口,其中database对象被定义为idbdatabase。而得到idbdatabase用的是工厂方法,即从idbfactory中取得。浏览器对象中,实现了idbfactory的只有indexeddb这个实例。indexeddb中,几乎所有的操作都是采用了command->request->result的方式。比如查询一条记录,返回一个request,在request的result中得到查询结果。又比如打开数据库,返回一个request,在request的result中得到返回的数据库引用。(摘录结束)

indexeddb的使用比前三种复杂了些,下面我们一部分一部分来了解。

首先要注意的一点是,indexeddb的页面只有放到服务器上才能正常访问,单独双击页面很可能不会成功。但即使这样,indexeddb的数据还是存在用户本地的。目前测试了一下,chrome上delete操作有点问题,不过读者可以在firefox上测试,可以正常跑通。

第一步也是判断并获得indexeddb对象:

function getindexdb() {

try {

//由于浏览器不同内核中,indexeddb的对象名不同,所以比较麻烦:

if(! window.indexeddb ) window.indexeddb = window.mozindexeddb || window.webkitindexeddb;

//webkitindexeddb内核的浏览器中,idbtransaction的名字有些差异

if('webkitindexeddb' in window) window.idbtransaction = window.webkitidbtransaction;

if( !! window.indexeddb) {

return window.indexeddb;

} else {

return undefined;

}

} catch (e) {

return undefined;

}

}

然后打开indexeddb,比较复杂:

1 function opendb(){

2var idb = getindexdb();

3if(!idb) {

4disperror('indexdb not supported!');

5return ;

6} else {

7try {

8//打开或创建一个indexeddb,名traveldb,后面那参数是描述。

9var request = idb.open('traveldb','demo traveldb');

10request.onerror = function(event) { //request.onerror是打开失败时的处理函数

11disperror('failed to open indexdb database');

12}

13request.onsuccess = function(event) { //request.onerror是打开成功时的回调函数

14db = request.result;

15//不要以为到这里很复杂,其实到上一步就是:

16//window.indexeddb.open('traveldb','demo traveldb').result

17//只是我们在一步步的做安全处理 ,下面是request在获取结果失败时的处理

18db.onerror = function(event){disperror( 'database error: ' + event.target.errorcode );}

19//indexeddb比较麻烦的一点是还要设置版本,如果不版本不统一,

20//在之后某些步骤调用时,会有问题。一般都把它设成1.0

21if(db.version != '1.0') {

22var req = db.setversion('1.0'); //set the version

23//设置了version之后 还要进行错误处理

24req.onerror = function(event){alert('version error!');}

25req.onsuccess = function(event) {

26//alert('creating the object store');

27//创建一个objectstore,id是主键,自动增长

28var objstore = db.createobjectstore('otravel',

29{keypath:'id',autoincrement:true});

30//创建一个index:traveler,不唯一

31objstore.createindex('traveler','citraveler',{unique:false});

32}

33}

34}

35} catch(e) {

36disperror('indexdb supported, but can\'t open the database\n'+e.message);

37}

38}

39 }

其中db是在函数外定义的一个变量:var db;这样就可以在全局使用。

下面再举几个查询、插入、修改、删除的例子:

(1)查询

1 if(db){

2//先得到我们刚刚创建的objectstore,transaction里面的可以有两个参数,

3//第一个参数为要关联的数据库名称数组,第二个为打开此数据库的方式(如只读),

4//下面会讲,它缺省时表示 打开的方式为只读

5//然后由transaction得到名为otravel的objectstore

6var objstore = db.transaction(['otravel']).objectstore('otravel');

7//可以用objstore来打开游标,也可以用index来打开游标,它们的区别下面会讲

8//var indextraveler = objstore.index('traveler');

9objstore.opencursor().onsuccess = function(event) { //open the cursor of the index

10var cursor = event.target.result; //由event来获得游标

11if(cursor) {

12var v = cursor.value;

13alert(v.traveler + '乘坐' + v.transportation + '去' + v.destination);

14cursor.continue();

15//continue其实就是让游标往下移,并试着open,成功时还是执行这个函数,

16//所以这个其实是一个隐藏的循环,直到cursor读完为止

17}

18}

19 }

transaction的第二个参数表示打开的方式,主要有以下几种:

idbtransaction.read_only只读

idbtransaction.read_write可读可写

idbtransaction.version_change版本升级

用的最多的是前两种。不设置时则默认为read_only

(2)插入

由上面例子可以体验到一些nosql数据库的优点,直接对对象进行操作,取出的是对象,写入的也是对象,如下:

1 currec = {traveler:traveler, destination:destination,

2transportation:transportation,citraveler:traveler.tolowercase()};

3 db.transaction(['otravel'],idbtransaction.read_write).objectstore('otravel').add(currec);

很简单,就是把对象插入数据库中。

(3)修改

其实就是先把原来相应的那条数据给删除,然后加入这条修改后的数据

1 currec = {traveler:traveler, destination:destination,

2transportation:transportation,citraveler:traveler.tolowercase()};

3 var objstore = db.transaction('otravel',idbtransaction.read_write).objectstore('otravel');

4 //alert(key);

5 var request;

6 if (objstore.delete) {

7request = objstore.delete(key);

8 } else {

9// ff4 not up to spect

10request = objstore.remove(key);

11 }

12 request.onerror = function(e) {

13disperror('remove error !');

14 }

15 request.onsuccess = function(e) {

16objstore.add(currec);

17 };

(4)删除

删除时要先得到id,把id传入数据库引擎就知道删除哪一条数据了,步骤和上面差不多。

1 if(confirm('are you sure to delete traveler:'+traveler)) {

2var objstore = db.transaction('otravel',idbtransaction.read_write).objectstore('otravel');

3//alert(id);

4var request;

5if (objstore.delete) {

6request = objstore.delete(id);

7} else {

8// ff4 not up to spect

9request = objstore.remove(id);

10}

11request.onerror = function(e) {

12disperror('remove error !');

13}

14 }

上面讲到的,用objstore来打开游标,也可以用index来打开游标。有一篇文章讲得很清楚,我把它摘过来供读者参考。

数据本地化存储》这文章比较长,可以搜 opencursor 来找到你想要的内容

使用游标

使用get()方法需要知道存储对象的key值,但若不知道key值,要看存储对象,就可以使用游标,如下是使用游标对数据库进行遍历:

var objectstore = db.transaction("customers").objectstore("customers");

objectstore.opencursor().onsuccess = function(event) {

var cursor = event.target.result;

if (cursor) {

alert("name for ssn " + cursor.key + " is " + cursor.value.name);

cursor.continue();

}

else {

alert("no more entries!");

}

};

opencursor()方法有许多参数,首先你可设置遍历的key的范围,其次可以设置游标遍历的方向,可参见后面关于indexeddb异步api的介绍。continue();表示继续遍历。

使用索引

在数据库中,所有的数据都是以key值来存储的,若要通过name等其他属性查看存储对象,需要遍历每个ssn并将它的name提取出判断是否为要查看的对象,但可以通过index而更为简单的实现,如:

var index = objectstore.index("name"); index.get("donna").onsuccess = function(event) {

alert("donna's ssn is " + event.target.result.ssn);

};

我们在index中使用cursor来遍历存储的数据,并根据不同的cursor打开方式,返回不同的遍历结果,如下两种方式:

//方式一

index.opencursor().onsuccess = function(event) {

var cursor = event.target.result;

if (cursor) {

// cursor.key is a name, like "bill", and cursor.value is the whole object.

alert("name: " + cursor.key + ", ssn: " + cursor.value.ssn + ", email: " + cursor.value.email);

cursor.continue();

}

};

//方式二

index.openkeycursor().onsuccess = function(event) {

var cursor = event.target.result;

if (cursor) {

// cursor.key is a name, like "bill", and cursor.value is the ssn.

// no way to directly get the rest of the stored object.

alert("name: " + cursor.key + ", "ssn: " + cursor.value);

cursor.continue();

}

};

关于游标遍历的范围和方向

如果想要限制游标的遍历范围,可以使用“key range”的对象,并将它做为opencursor()和openkeycursor()的第一个参数,这样的范围可以是单个键值、或是一个最低边界和最高边界的范围,并规定是否包括范围,如下:

// only match "donna"

var singlekeyrange = idbkeyrange.only("donna");

// match anything past "bill", including "bill"

var lowerboundkeyrange = idbkeyrange.lowerbound("bill");

// match anything past "bill", but don't include "bill"

var lowerboundopenkeyrange = idbkeyrange.lowerbound("bill", true);

// match anything up to, but not including, "donna"

var upperboundopenkeyrange = idbkeyrange.upperbound("donna", true);

//match anything between "bill" and "donna", but not including "donna"

var boundkeyrange = idbkeyrange.bound("bill", "donna", false, true);

index.opencursor(boundkeyrange).onsuccess = function(event) {

var cursor = event.target.result;

if (cursor) {

// do something with the matches.

cursor.continue();

}

};

另外,还可以规定游标遍历的方向,默认的是上升的方向,若使用相反的方向,可以将prev作为opencursor()或是openkeycursor()的第二个参数,如下:

objectstore.opencursor(null, idbcursor.prev).onsuccess = function(event) {

var cursor = event.target.result;

if (cursor) {

// do something with the entries.

cursor.continue();

}

};

需要注意的是,在索引中使用游标时,由于可能有多个键值是相同的,如果想过滤多余的相同键值,将next_no_duplicate 或是prev_no_duplicate做为它的第二个参数,这时候总是返回最低边界的那一个对象,如下:

index.openkeycursor(null, idbcursor.next_no_duplicate).onsuccess = function(event) {

var cursor = event.target.result;

if (cursor) {

// do something with the entries.

cursor.continue();

}

};

chrome的indexeddb本地数据存在:c:\users\你的用户名\appdata\local\google\chrome\user data\default\indexeddb这个目录下,它会根据站点的名字来命名,其实上面三种也是用站点名字来命名,只是在本地用双击的方式访问时,会写成file之类的名字。

如果读者对indexeddb还是不太了解的话,可以参考上面提到的这篇文章:《html5之indexeddb使用详解》,它写得比较详细。

(最后,我会把本文中所讲到的示例文件都上传到我的博客中了,可以下载运行)


======================================================
在最后,我邀请大家参加新浪APP,就是新浪免费送大家的一个空间,支持PHP+MySql,免费二级域名,免费域名绑定 这个是我邀请的地址,您通过这个链接注册即为我的好友,并获赠云豆500个,价值5元哦!短网址是http://t.cn/SXOiLh我创建的小站每天访客已经达到2000+了,每天挂广告赚50+元哦,呵呵,饭钱不愁了,\(^o^)/
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值