高级程序设计——客户端存储

  • 前言
  • Cookie
    • 限制
    • cookie的构成
    • JavaScript中的cookie
    • 子cookie
    • 关于cookie的思考
  • IE用户数据
  • Web存储机制
    • Storage类型
    • sessionStorage对象
    • globalStorage对象
    • localStorage对象
    • storage事件
    • 限制
  • IndexedDB
    • 数据库
    • 对象存储空间
    • 事务
    • 使用游标查询
    • 键范围
    • 设定游标方向
    • 索引
    • 并发问题

前言

  1. 属于某个特定用户的信息应该存在该用户的机器上。无论是登录信息、偏好设定或其他数据,Web应用提供者发现他们在找各种方式将数据存在客户端上

  2. 一个方案是以cookie的形式出现的。cookie只是在客户端存储数据的其中一种选项

Cookie

  1. 最初是在客户端用于存储会话信息的。要求服务器对任意HTTP请求发送Set-Cookie HTTP头作为响应的一部分,其中包含会话信息。

    这种服务器响应的头可能如下:
    HTTP/1.1 200 OK
    Content-type:text/html
    Set-Cookie:name=value
    Other-header:other-header-value
    • 上述HTTP响应设置以name为名称、以value为值的一个cookie,名称和值在传送时都必须是URL编码的
    • 浏览器会存储这样的会话信息,在这之后,通过为每个请求添加Cookie HTTP头将信息发送回服务器,如下所示

      GET /index.html HTTP/1.1
      Cookie:name=value
      Other-header:other-header-value
      • 发送回服务器的额外信息可以用于唯一验证客户来自于发送的哪个请求

1. 限制

  1. cookie在性质上是绑定在特定的域名下的。当设定一个cookie后,再给创建它的域名发送请求时,都会包含这个cookie。这个限制确保了存储在cookie中的信息只能让批准的接受者访问,,而无法被其他域访问

  2. 确保cookie不会被恶意使用,同时不会占据太多磁盘空间。每个域的cookie总数是有限的。浏览器之间有差异。

  3. 当超过单个域名限制之后还要再设置cookie,浏览器就会清除以前设置的cookie。

  4. 浏览器中对于cookie的尺寸也有+限制。大多数浏览器都有大约4096B(加减1)的长度限制。尺寸限制影响到一个域下所有的cookie,而并非每个cookie单独限制。如果创建超过最大尺寸限制的cookie,那么该cookie就会被丢掉

2. cookie的构成

  1. 名称:一个唯一确定cookie的名称,最好区分大小写。cookie的名称必须是经过URL编码的

  2. 值:储存在cookie中的字符串值。值必须被URL编码

  3. 域:domain cookie对于哪个域是有效的。所有向该域发送的请求中都会包含这个cookie信息。这个值可以包含子域,也可以不包含它。如果没有明确设定,这个域会被认作来自设置cookie的那个域

  4. 路径:path 对于指定域中的那个路径,应该向服务器发送cookie

  5. 失效时间:expires 表示cookie何时应该被删除的时间。默认情况下,浏览器会话结束时即将所有cookie删除;不过也可以自己设置删除时间。这个值是个GMT格式的日期。

  6. 安全标志:secure 指定后,cookie只有在使用SSL连接的时候才发送到服务器。

  7. 上述每一段信息都作为Set-Cookie 头的一部分,使用分号加空格分割每一段

HTTP/1.1 200 OK
Content-type:text/html
Cookie:name=value; expires=Mon, 22-Jan-07 07:10:24 GMT; 
domain=.wrox.com; path=/; secure
Other-header:other-header-value
  1. 域、路径、失效时间和secure标志都是服务器给浏览器的指示,以指定何时应该发送cookie。这些参数并不会作为发送到服务器的cookie信息的一部分,只有名值对才会被发送

3. JavaScript中的cookie

  1. document.cookie属性

    • 当用来获取属性值时,它返回当前页面可用的所有cookie的字符串,一系列由分号隔开的名值对。所有的名字和值都是经过URL编码的,所以必须使用decodeURIComponent()来解码
    • 当用于设置值的时候,可以设置一个新的cookie字符串,会被解释并添加到现有的cookie集合中。如果设置的cookie名称已经存在,则会覆盖。在设置时,只有cookie的名字和值是必需的。要编码。
      document.cookie=encodeURIComponent("name")+"="+encodeURIComponent("mary")+"; domain=.wrox.com; path=/";
  2. 基本的cookie操作:读取、写入、删除

  3. 没有删除已有cookie的直接办法。所以需要使用相同的路径、域和安全选项再次设置cookie。并将失效时间设置为过去的时间

4. 子cookie

  1. 为了绕开浏览器的单域名下的cookie数限制,一些开发人员使用了一种称为子cookie的概念。子cookie是存放在单个cookie中的更小段的数据。也就是使用cookie值来存储多个名称值对
    name=name1=value1&name2=value2&name3=value3

  2. 子cookie一般也以查询字符串的格式进行格式化。然后这些值可以使用单个cookie进行存储和访问,而非对每个名称值对使用不同的cookie存储

  3. set方法时,为了在同一个cookie中存储多个子cookie,路径、域和secure标志必须一致。针对整个cookie的失效日期则可以在任何一个单独的子cookie写入的时候同时设置

  4. 需要关注cookie的长度,以防超过单个cookie的长度限制

5. 关于cookie的思考

  1. 还有一类cookie被称为“HTPP专有cookie”。它可以从浏览器或者服务器设置,但是只能从服务器端读取。因为JavaScript无法获取HTTP专有cookie的值

  2. 在cookie中存储大量信息会影响到特定域的请求性能。cookie信息越大,完成对服务器请求的时间也就越长。所以最好少存储信息,以避免影响性能。

  3. 一定不要再cookie中存储重要和敏感的数据

IE用户数据

  1. 持久化用户数据。用户数据允许每个文档最多128kb数据,每个域名最多1mb数据

  2. 要使用持久化用户数据,必须使用css在某个元素上指定userData行为:

  3. 然后就可以使用setAttribute()方法在上面保存数据了

  4. 为了将数据提交到浏览器缓存中,还必须调用save()方法并告诉它要保存到的数据空间的名字。

var datastore=document.getElementById("dataStore");
datastore.setAttribute("name","mary");
datastore.setAttribute("book","java");
datastore.save("bookinfo");
  1. 下一次页面载入之后,可以使用load()方法指定同样的数据空间名称来获取数据 datastore.load("bookinfo");。然后可以获取到bookinfo数据空间中的所有信息,而且可以访问。只有到载入确切完成之后数据方能使用,如果数据不存在,则返回null

  2. removeAttribute()方法指定属性名称,就可以删除某元素数据。删除之后,必须再次调用save()来提交更改

datastore.getAttribute("name");//mary
datastore.removeAttribute("name");
datastore.save("bookinfo");
  1. 对IE用户数据的访问限制:要访问某个数据空间,脚本运行的页面必须来自于同一个域名,在同一个路径下,并使用与进行存储的脚本同样的协议。但是无法将用户数据访问限制扩展到更多的客户。用户数据默认是可以跨越会话持久存在的,同时也不会过期。

  2. 不安全,不能存放敏感信息。

Web存储机制

  1. Web Storage:当数据需要被严格控制在客户端时,无须持续地将数据发回服务器。它的两个主要目标是:
    • 提供一种在cookie之外存储会话数据的途径
    • 提供一种存储大量可以跨会话存在的数据的机制

1. Storage类型

  1. Storage类型提供最大的存储空间来存储名值对

  2. 有如下方法:

    • clear():删除所有值
    • getItem(name):根据指定的名字获取对应的值
    • key(index):获得index位置处的值的名字
    • removeItem(name):删除由name指定的名值对
    • setItem(name,value):为指定的name设置一个对应的值
    • 其中getItem()、removeItem()、setItem()可以当作属性通过点或者方括号直接调用。但是建议使用方法
  3. length属性:有多少名值对放在Storage对象中。ie8有个remainingSpace属性,用于获取还可以使用的存储空间的字节数

  4. Storage类型只能存储字符串

2. sessionStorage对象

  1. 该对象存储特定于某个会话的数据,也就是该数据只保持到浏览器关闭。存储在该对象中的数据可以跨越页面刷新而存在,同时如果浏览器支持,浏览器崩溃并重启之后依然可用。

  2. 当文件在本地运行的时候是不可用的。存储在该对象中的数据只能由最初给对象存储数据的页面访问到,所以对多页面应用有限制。

  3. 是个Storage实例

    • 可以使用setItem()或者直接设置新的属性来存储数据
    • getItem()或者直接访问属性名来获取数据
    • removeItem()方法删除
  4. 写入数据:

    • Firefox和WebKit实现了同步写入:添加到存储空间中的数据是立刻被提交的
    • ie是异步写入:在设置数据和将数据实际写入磁盘之间可能有一些延迟。对于大量数据,比其他浏览器更快的恢复执行,因为它会跳过实际的磁盘写入过程
  5. 在ie8中可以强制把数据写入磁盘:在设置新数据之前使用begin()方法(确保在这段代码执行的时候不会发生其他磁盘写入操作),并且在所有设置完成之后调用commit()方法。

  6. 该对象主要用于仅针对会话的小段数据的存储。如果需要跨越会话存储数据,那么globalStorage或者localStotage更为合适

3. globalStorage对象

  1. 目的是跨越会话存储数据,但有特定的访问限制

  2. 必须要指定哪些域可以访问该数据。
    globalStorage["wrox.com"].name="mary";

  3. 该对象不是Storage的实例,而具体的globalStorage["wrox.com"]才是。这个存储空间对于wrox.com及其所有子域都是可以访问的。

  4. 避免使用可宽泛访问的数据存储。比如:globalStorage["net"]

  5. 对globalStorage空间的访问,是依据发起请求的页面的域名、协议和端口来限制的。

  6. globalStorage的每个属性都是Storage的实例

  7. 如果事先不能确定域名,那么使用location.host作为属性名比较安全

  8. 如果不适用removeItem()或者delete删除,或者用户未清除浏览器缓存,存储在globalStorage属性中的数据会一直保存在磁盘上。所以非常适合在客户端存储文档或者长期保存用户偏好设置

4. localStorage对象

  1. 取代了globalStorage,它不能指定任何访问规则,规则事先就设定好了。

  2. 要访问同一个localStorage对象,页面必须同域名、同协议、同端口。

  3. localStorage是Storage的实例

  4. 数据保存到通过JavaScript删除或者是用户清除浏览器缓存,同globalStorage。

5. storage事件

  1. 对Storage对象进行任何修改,都会在文档上触发storage事件。

  2. 这个事件的event对象有以下属性

    • domain:发生变化的存储空间的域名
    • key:设置或者删除的键名
    • newValue:如果是设置值,则是新值;如果是删除键,则是null
    • oldValue:键被更改之前的值
  3. 发生在document上

6. 限制

  1. 每个来源都有固定大小的空间用于保存自己的限制。所以要注意分析和控制每个来源中有多少页面需要保存数据

  2. 浏览器差异性

IndexedDB

  1. 是在浏览器中保存结构化数据的一种数据库。它的思想是
    创建一套API,方便保存和读取JavaScript对象,同时还支持查询及搜索

  2. 异步进行。所以大多数操作会以请求方式进行,但这些操作会在后期执行。如果成功则返回结果,如果失败则返回错误。差不多每一次IndexedDB操作,都需要你注册onerror或onsuccess事件处理程序,以确保适当地处理结果

  3. IndexedDB将是一个作为API宿主的全局对象。

  4. 有浏览器差异

var indexedDB = window.indexedDB||window.msIndexedDB||window.mozIndexedDB||window.weblitIndexedDB;
//.|ie10|firefox4|chrome

1. 数据库

  1. 使用对象保存数据。一个IndexedDB数据库,就是一组位于相同命名空间下的对象的集合。

  2. 第一步打开它,即把要打开的数据库名传给indexDB.open()。如果传入的数据库已经存在,就会发送一个打开它的需求;如果传入的数据还不存在,就会发送一个创建并打开它的请求。

  3. 上述方法会返回一个IDBRequest对象,在这个对象上可以添加事件处理程序,event.target都指向返回的这个对象。

    • onerror:event.target.result中将有一个数据库实例对象
    • onsuccess:event.target.errorCode中将保存一个错误码,表示问题的性质
  4. 默认情况下,IndexedDB数据库是没有版本号的,最好一开始就调用setVersion()传入以字符串形式表示的版本号为数据库指定一个版本号。同样也会返回一个请求对象。可以指定事件处理程序

2. 对象存储空间

  1. 在建立了与数据库的连接之后,下一步就是使用对象存储空间。

  2. 如果数据库的版本与你传入的版本不匹配,那可能就需要创建一个新的对象存储空间。

  3. 在创建对象存储空间之前,必须要想清除你想要保存什么数据类型。可以对象中的一个属性作为这个对象存储空间的键。这个属性必须全局唯一,而且大多数时候都要通过这个键来访问数据。必须要指定这个键。

    var user={
        username:"007",
        firstname:"James",
        lastname:"Bond",
        password:"foo"
    };
    var store=db.createObject("users",{keyPath:"username"});
  4. 可以使用add()或put()方法来向其中添加数据。都接收要保存的对象。然后这个对象就会被保存到存储空间中。每次调用它们就会创建一个新的针对这个对象存储空间的更新请求。也可以指定事件处理程序。区别在于:add()是添加新值,如果已经存在则返回错误。put()是更新原有的值。

3. 事务

  1. 在数据库对象上调用transaction()方法可以创建事务。可以传入要访问的一或多个对象存储空间。如果是多个,用数组。只要想读取或修改数据,都要通过事务来组织所有操作。

  2. 默认是只读,如果要修改访问方式,必须在创建事务时传入第二个参数表示访问模式。用IDBTransaction接口定义的如下常量表示:

    • READ_ONLY(0):只读
    • READ_WRITE(1):读写
    • VERSION_CHANGE(2):改变
  3. 浏览器有差异性

    • ie10和firefox4+:IDBTransaction
    • chrome:webkitIDBTransaction
      var IDBTransaction=window.IDBTransaction||window.webkitIDBTransaction;
      var transaction=db.transaction(["users","another"],IDBTransaction.READ_WRITE);
  4. 因为transaction方法得到的是一个或多个存储空间,所以调用objectStore方法并传入存储空间的名字,就可以访问特定的存储空间。然后就可以使用add和put方法。也可以使用get取得值,delete删除对象,clear删除所有对象。
    var request=db.transaction("users").objectStore("users").get("007");

  5. 事务对象本身也有事件处理函数

    • onerror
    • oncomplete:通过这个事件的事件对象(event)访问不到get请求返回的任何数据,必须在响应请求的onsucess事件处理程序中才能访问到。

4. 使用游标查询

  1. 需要检索多个对象的情况下,则需要在事务内部创建游标。游标就是一指向结果集的指针。游标并不提前收集结果。游标指针会先指向结果中的第一项,在接到查找下一项的指令时,才会指向下一项。

  2. 在对象存储空间上调用openCursor()方法可以创建游标。会返回一个请求对象,必须为该对象指定事件处理程序。

    • onsuccess:通过event.target.result取得存储空间中的下一个对象。在结果集中有下一项时,这个属性中保存着一个IDBCursur实例;没有下一项时,为null。IDBCursor的实例的属性:
      • direction:数值,表示游标移动的方向
      • key:对象的键
      • value:实际的对象,显示之前要转换成JSON字符串
      • primaryKey:游标使用的键
    • onerror
  3. 使用update方法可以用指定的对象更新当前游标的value。也可以指定事件处理程序。

  4. 使用delete方法就会删除相应的记录,同上。

  5. 如果当前事务没有修改对象存储空间的权限,上述两个方法就会抛出错误。

  6. 默认情况下,每个游标只发起一次请求。要想发起另一次请求,必须调用下面的一个方法

    • continue(key):key可选。没有则移动到下一项。有则移动到key项的位置
    • advance(count):向前移动count指定的项数
    • 这两个方法都会导致游标使用相同的请求,因此相同的onsuccess和onerror事件处理程序也会得到重用。
var stordb.transaction("users").objectStore("users");
var request=store.openCursor();
request.onsuccess=function(event){
    var cursor=event.target.result;//下一项
    if(cursor){//必须要检查
        console.log("key: "+cursor.key+", Value: "+JSON.stringify(cursor.value))
        cursor.continue();//移动到下一项,会触发另一次请求,进而再次调用onsuccess
    }
    else{
        console.log("DONE");
    }
}

5. 键范围

  1. 键范围由IDBKeyRange的实例表示。ie10和firefox4+支持。chrome叫webkitIDBKeyRange

  2. 四种定义键范围的方式

    • only():传入想要取得的对象的键
    • lowerBound():指定结果集的下界。游标从下界开始到完,可传入第二个参数true,则忽略下界对象
    • upperBound():上界。从头到上界。同上
    • bound():同时指定上界、下界。可传入true、true
  3. 在定义键范围之后,把它传给openCursor方法,就能得到一个符合相应约束条件的游标。

var range=IDBKeyRange.bound("007","ace",true);
var request=store.openCursor(range);

6. 设定游标方向

  1. openCursor方法可以接收第二个参数,表示方向的数值常量。默认方向是下一个。也可以跳过、向前、向前跳过

  2. openCursor的第一个参数是null表示:使用默认的键范围,即包含所有对象

  3. 如果传入向前的参数打开游标时,每次调用continue和advance方法,仍然是向后移动。

7. 索引

  1. 如果要用两种方式来保存数据,可以把一种作为主键,为另一种创建索引。

  2. 要创建索引,首先引用对象存储空间,然后调用createIndex方法

    • 第一个参数:索引的名字
    • 第二个参数:索引的属性的名字
    • 第三个参数:一个包含unique属性的选项(options)对象。必须指定,因为它表示键在所有记录中是否唯一
      var index = store.createIndex("username","username",{unique:false});
  3. 上述方法的返回值是IDBIndex的实例。

  4. 使用的时候直接在对象存储空间上调用index()方法,也可以返回上述用一个实例。
    var index=store.index("username");

  5. 在索引上调用openCursor方法也可以创建新的游标。会把索引键保存在event.result.key属性中

  6. 也可以创建一个特殊的只返回每条记录主键的游标,调用openKeyCursor方法。此时event.result.key中保存着索引键,event.result.value中保存着主键

  7. 使用get方法能够从索引中取得一个对象,传入相应的索引键。也返回一个请求,也可以指定事件处理程序。
    var request=index.get("007");

  8. 使用getKey方法可以根据给定的索引键取得主键
    var request=index.getKey("007");

  9. IDBIndex对象的属性:

    • name:索引的名字
    • keyPath:传入createIndex()中的属性路径
    • objectStore:索引的对象存储空间
    • unique:表示索引键是否唯一的布尔值
  10. 通过对象存储空间的indexName属性可以访问到为该空间建立的所有索引。

  11. 在对象空间上调用deleteIndex方法并传入索引的名字可以删除索引。

8. 并发问题

  1. 刚打开数据库时,要急着指定onversionchange事件处理程序。当同一个来源的另一个标签页调用setVersion()时,就会执行这个回调函数,此时需要关闭数据库

    database.onversionchange=function(){
        database.close();
    };
  2. 在你想要更新数据库的版本但另一个标签页已经打开数据库时,会触发onblocked事件处理程序。这时要通知用户关闭其他标签页。然后再重新调用setVersion()

你可能感兴趣的:(javascript)