初探HTML5的本地存储

传统的HTML使用的是众所周知的cookie,各种浏览器都支持,直接用js就可以调用,很方便。但cookie也有它本身的缺陷与不足。比如存储空间小,每个站点大小限制在4 KB左右,又有时间期限…

  传统的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,但浏览器可能不支持,它就有可能是空,所以我们先做个判断。

JavaScript Code复制内容到剪贴板
  1. function getLocalStorage() {  
  2.      try {  
  3.          if( !! window.localStorage ) return window.localStorage;  
  4.      } catch(e) {  
  5.          return undefined;  
  6.      }  
  7.  }  

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

存数据:

JavaScript Code复制内容到剪贴板
  1. var db = getLocalStorage();  
  2. if(db) {  
  3. 3     db.setItem('author''jasonling');  
  4. 4     db.setItem('company''Tencent');  
  5. 5     db.setItem('introduction''A code lover !');  
  6. 6 }  

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

  chrome浏览器的这个sqlite文件存在:C:/Users/你的用户名/AppData/Local/Google/Chrome/User Data/Default/Local Storage 里面,如上图,用sqlite打开,用.schema可以看到一个表,这是chrome帮我们建的用来存储键值对的表ItemTable,查询表里的数据可以发现我们刚刚用程序存进去的值。

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

localstorage的其它操作:

JavaScript Code复制内容到剪贴板
  1. db.setItem('author''jasonling');  
  2.  2 db.setItem('company''Tencent');  
  3.  3 db.setItem('introduction''A code lover !');  
  4.  4 //取值  
  5.  5 alert(localStorage['author']);  
  6.  6 alert(db.getItem('company'));  
  7.  7 //删除  
  8.  8 db.removeItem('company');  
  9.  9 alert(db.getItem('company'));  
  10. 10 //当然也可以用db.setItem('company', '');来删除一个值,但这样删不彻底。  
  11. 11 //清除  
  12. 12 db.clear();  
  13. 13 alert(localStorage['author']); //undefined  

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

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

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

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

JavaScript Code复制内容到剪贴板
  1. //Get the addEventListener    
  2.  function getAddEventHandler() {    
  3.      try {    
  4.          if( !! window.addEventListener ) return window.addEventListener;    
  5.      } catch(e) {    
  6.          return undefined;    
  7.      }    
  8.  }    

写一个handler函数.

JavaScript Code复制内容到剪贴板
  1. //EventStatus  
  2.   function eventStatus(s) {  
  3.       if(s) element('eventResult').innerHTML = s;  
  4.       else element('eventResult').innerHTML = 'Event status';  
  5.   }  
  6.     
  7.   //Event handler  
  8.   function eventHandler(e) {  
  9.       eventStatus('Event triggered: ' + e.url + ' ' +  
  10.          e.storageArea.traveler + ' ' + e.storageArea.destination + ' ' + e.storageArea.transportation);  
  11.      dispResults();  
  12.  }  

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

JavaScript Code复制内容到剪贴板
  1. function test(s) {  
  2.      if(a) {  
  3.          alert('if');  
  4.      } else {  
  5.          alert('else');  
  6.      }  
  7.  }  
  8.  var a = window.abc; //window.abc不存在,是有问题的,所以,可以想像,下面的结果是else  
  9.  test(a);  

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

JavaScript Code复制内容到剪贴板
  1. var addEL = getAddEventHandler(); //这是上面写的一个函数   
  2.  if(addEL) {  
  3.      addEL('storage',eventHandler,false);  //给storage事件加上监听器  
  4.  } else {  
  5.      element('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了,它存储的数据只有当前页面可以访问。 

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

JavaScript Code复制内容到剪贴板
  1. function getSessionStorage() {  
  2.      try {  
  3.          if( !! window.sessionStorage ) return window.sessionStorage;  
  4.      } catch(e) {  
  5.          return undefined;  
  6.      }  
  7.  }  

然后:

JavaScript Code复制内容到剪贴板
  1. var db = getSessionStorage();  
  2.  if(db) {  
  3.      db.setItem('author','jasonling');  
  4.      db.setItem('company''Tencent');  
  5.      db.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数据库一样的方便,可以自己创建数据库,自己建表,自己往里面增删改数据。

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

Javascript复制内容到剪贴板
  1. function getOpenDatabase() {  
  2.      try {  
  3.          if( !! window.openDatabase ) return window.openDatabase;  
  4.          else return undefined;  
  5.      } catch(e) {  
  6.          return undefined;  
  7.      }  
  8.  }  

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

JavaScript Code复制内容到剪贴板
  1. function prepareDatabase() {  
  2.       var odb = getOpenDatabase();  
  3.       if(!odb) {  
  4.           dispError('Web SQL Not Supported');  
  5.           return undefined;  
  6.       } else {  
  7.           var db = odb( 'testDatabase''1.0''A Test Database', 10 * 1024 * 1024 );  
  8.           db.transaction(function (t) {  
  9.               t.executeSql( createSQL, [], function(t, r) {}, function(t, e) {  
  10.                  alert('create table: ' + e.message);  
  11.              });  
  12.          });  
  13.          return 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来登陆,可以这样写: 

JavaScript Code复制内容到剪贴板
  1. function(t) {  
  2.      t.executeSql('select * from tUser where name=? and pwd=?',['ling','mypwd'],  
  3.          function(t,r) {  
  4.              alert('登陆成功');  
  5.          },  
  6.          function(t,e) {  
  7.              alert(e.message);  
  8.          }  
  9.  }  

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

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

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

JavaScript Code复制内容到剪贴板
  1. //Get the count of rows  
  2.   function countRows(){  
  3.       if(!db) return ;  
  4.       db.readTransaction(function(t) {  
  5.           t.executeSql('SELECT count(*) AS c FROM tTravel',[],function(t,r) {  
  6.               var c = r.rows.item(0).c; //result 结果的第行的c那一列  
  7.               element('rowCount').innerHTML = c? c:0;  
  8.           },function(t,e) {  
  9.               alert('countRows:' + e.message);  
  10.          });  
  11.      });  
  12.  }  

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

(2)取出表中的数据:

JavaScript Code复制内容到剪贴板
  1.  if(db) {  
  2.      db.readTransaction(function(t){  
  3.           t.executeSql('SELECT * FROM tTravel ORDER BY LOWER(traveler)',[],function(t,r) {  
  4.               var mytab = new bwTable();   
  5.               mytab.setHeader(['Traveler','Destination','Transportation','']);  
  6.                 
  7.               for(var i = 0; i < r.rows.length; i ++){  
  8.                   var row = r.rows.item(i);  
  9.                   mytab.addRow([row.traveler,row.destination,row.transportation,rowBtn(row.id,row.traveler)]);  
  10.              }  
  11.              element('results').innerHTML = mytab.getTableHTML();  
  12.              element("travelForm").elements['traveler'].focus();  
  13.          },function(t,e) {  
  14.              alert("Can't get the data");  
  15.          });     });  
  16. }      

(3)往表里添加数据:

JavaScript Code复制内容到剪贴板
  1. if(db) {  
  2.       db.transaction(function(t) {  
  3.           t.executeSql('INSERT INTO tTravel VALUES(NULL,?,?,?)',  
  4.               [traveler,destination,transportation],  
  5.               function(t,r) {resetForm();},//执行成功时把表格清空,自己实现的一个函数  
  6.               function(t,e){  
  7.                   alert("insert rows:"+e.message);  
  8.               });  
  9.       });  
  10.  }  

(4)更新数据:

JavaScript Code复制内容到剪贴板
  1. db.transaction(function(t){    
  2.      t.executeSql('update tTravel set traveler=?,destination=?,transportation=? where id=?',    
  3.          [traveler, destination, transportation,inputKey]);    
  4.  },function(t,e){    
  5.      alert('Update row:'+e.message);    
  6.  },function(){resetForm();});    

(5)删除数据:

JavaScript Code复制内容到剪贴板
  1. if(db) {  
  2.      db.transaction(function(t){  
  3.          t.executeSql('delete from tTravel where id=?',[id],  
  4.              function(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对象:

JavaScript Code复制内容到剪贴板
  1. function getIndexDB() {  
  2.     try {  
  3.         //由于浏览器不同内核中,indexedDB的对象名不同,所以比较麻烦:  
  4.         if(! window.indexedDB ) window.indexedDB = window.mozIndexedDB || window.webkitIndexedDB;  
  5.         //webkitIndexedDB内核的浏览器中,IDBTransaction的名字有些差异  
  6.         if('webkitIndexedDB' in window) window.IDBTransaction = window.webkitIDBTransaction;  
  7.         if( !! window.indexedDB) {  
  8.             return window.indexedDB;  
  9.         } else {  
  10.             return undefined;  
  11.         }  
  12.     } catch (e) {  
  13.         return undefined;  
  14.     }  
  15. }  

  然后打开indexedDB,比较复杂:

JavaScript Code复制内容到剪贴板
  1. function openDB(){  
  2.       var iDB = getIndexDB();  
  3.       if(!iDB) {  
  4.           dispError('IndexDB not supported!');  
  5.           return ;  
  6.       } else {  
  7.           try {  
  8.               //打开或创建一个indexedDB,名travelDB,后面那参数是描述。  
  9.              var request = iDB.open('travelDB','demo travelDB');  
  10.              request.onerror = function(event) { //request.onerror是打开失败时的处理函数  
  11.                  dispError('Failed to open IndexDB database');  
  12.              }  
  13.              request.onsuccess = function(event) { //request.onerror是打开成功时的回调函数  
  14.                  db = request.result;   
  15.                  //不要以为到这里很复杂,其实到上一步就是:  
  16.                  //window.indexedDB.open('travelDB','demo travelDB').result  
  17.                  //只是我们在一步步的做安全处理 ,下面是request在获取结果失败时的处理  
  18.                  db.onerror = function(event){dispError( 'Database error: ' + event.target.errorCode );}  
  19.                  //indexedDB比较麻烦的一点是还要设置版本,如果不版本不统一,  
  20.                  //在之后某些步骤调用时,会有问题。一般都把它设成1.0   
  21.                  if(db.version != '1.0') {   
  22.                      var req = db.setVersion('1.0'); //Set the version  
  23.                      //设置了version之后 还要进行错误处理  
  24.                      req.onerror = function(event){alert('version error!');}  
  25.                      req.onsuccess = function(event) {  
  26.                          //alert('Creating the object store');   
  27.                          //创建一个ObjectStore,id是主键,自动增长  
  28.                          var objStore = db.createObjectStore('oTravel',  
  29.                              {keyPath:'id',autoIncrement:true});   
  30.                          //创建一个index:traveler,不唯一  
  31.                          objStore.createIndex('traveler','ciTraveler',{unique:false});  
  32.                      }  
  33.                  }  
  34.              }  
  35.          } catch(e) {  
  36.              dispError('IndexDB supported, but can\'t open the database\n'+e.message);  
  37.          }  
  38.      }  
  39.  }  

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

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

(1)查询

JavaScript Code复制内容到剪贴板
  1. if(db){      
  2.       //先得到我们刚刚创建的objectStore,transaction里面的可以有两个参数,  
  3.       //第一个参数为要关联的数据库名称数组,第二个为打开此数据库的方式(如只读),  
  4.       //下面会讲,它缺省时表示 打开的方式为只读   
  5.       //然后由transaction得到名为oTravel的objectStore  
  6.       var objStore = db.transaction(['oTravel']).objectStore('oTravel');  
  7.       //可以用objStore来打开游标,也可以用index来打开游标,它们的区别下面会讲  
  8.      //var indexTraveler = objStore.index('traveler');  
  9.       objStore.openCursor().onsuccess = function(event) { //open the cursor of the index  
  10.          var cursor = event.target.result; //由event来获得游标  
  11.          if(cursor) {  
  12.              var v = cursor.value;  
  13.              alert(v.traveler + '乘坐' + v.transportation + '去' + v.destination);  
  14.              cursor.continue();   
  15.              //continue其实就是让游标往下移,并试着open,成功时还是执行这个函数,  
  16.              //所以这个其实是一个隐藏的循环,直到cursor读完为止  
  17.          }  
  18.      }  
  19.  }  

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

 

IDBTransaction.READ_ONLY              只读

IDBTransaction.READ_WRITE            可读可写

IDBTransaction.VERSION_CHANGE    版本升级

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

(2)插入

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

JavaScript Code复制内容到剪贴板
  1. curRec = {traveler:traveler, destination:destination,        transportation:transportation,ciTraveler:traveler.toLowerCase()};  
  2. db.transaction(['oTravel'],IDBTransaction.READ_WRITE).objectStore('oTravel').add(curRec);  

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

(3)修改

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

JavaScript Code复制内容到剪贴板
  1. curRec = {traveler:traveler, destination:destination,         transportation:transportation,ciTraveler:traveler.toLowerCase()};  
  2.   var objStore = db.transaction('oTravel',IDBTransaction.READ_WRITE).objectStore('oTravel');  
  3.   //alert(key);   
  4.   var request;  
  5.   if (objStore.delete) {  
  6.       request = objStore.delete(key);  
  7.   } else {  
  8.       // FF4 not up to spect  
  9.      request = objStore.remove(key);   
  10.  }  
  11.  request.onerror = function(e) {  
  12.      dispError('Remove Error !');  
  13.  }  
  14.  request.onsuccess = function(e) {  
  15.      objStore.add(curRec);  
  16.  };  

(4)删除

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

JavaScript Code复制内容到剪贴板
  1. if(confirm('Are you sure to delete traveler:'+traveler)) {  
  2.       var objStore = db.transaction('oTravel',IDBTransaction.READ_WRITE).objectStore('oTravel');  
  3.       //alert(id);   
  4.       var request;  
  5.       if (objStore.delete) {  
  6.           request = objStore.delete(id);  
  7.       } else {  
  8.           // FF4 not up to spect  
  9.           request = objStore.remove(id);   
  10.      }  
  11.      request.onerror = function(e) {  
  12.          dispError('Remove Error !');  
  13.      }  
  14.  }  

 

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

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

  chrome的indexedDB本地数据存在:C:\Users\你的用户名\AppData\Local\Google\Chrome\User Data\Default\IndexedDB这个目录下,它会根据站点的名字来命名,其实上面三种也是用站点名字来命名,只是在本地用双击的方式访问时,会写成file之类的名字。

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

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


阅读更多
想对作者说点什么? 我来说一句

HTML5本地存储不完全指南

2014年08月06日 41KB 下载

没有更多推荐了,返回首页

加入CSDN,享受更精准的内容推荐,与500万程序员共同成长!
关闭
关闭