《JavaScript高级程序设计》读书笔记23.3数据存储

数据存储:能够直接在客户端上存储用户信息。

  • HTTP Cookie,通常直接叫做cookie,是一套标准,最初是在客户端用于存储会话信息的。

  • 该标准要求服务器对任意HTTP请求发送 Set-Cookie HTTP头作为响应的一部分,其中包含会话信息,如下

    1
    2
    3
    4
    HTTP/1.1 200 OK 
    Content-type: text/html
    Set-Cookie: name=value
    Other-header: other-header-value
  • 浏览器会存储这样的会话信息,并在这之后,通过为每个请求添加 Cookie HTTP 头将信
    息发送回服务器。

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

1.1 限制

  • cookie在性质上是绑定在特定的域名下的,储存在cookie中的信息只能让批准的接受者访问,而无法被其他域访问。
  • 每个域的 cookie总数是有限的,不过浏览器之间各有不同。
  • 尺寸限制,最好将整个cookie长度限制在4095B(含4095)以内。尺寸限制影响到一个域下所有的cookie,而并非每个 cookie单独限制。

1.2 构成

  • 名称:一个唯一确定 cookie 的名称。cookie 的名称必须是经过 URL 编码的。
  • :储存在 cookie 中的字符串值。值必须被 URL 编码。
  • :cookie 对于哪个域是有效的。
  • 路径:对于指定域中的那个路径,应该向服务器发送 cookie。
  • 失效时间:表示cookie何时应该被删除的时间戳。默认情况下,浏览器会话结束时即将所有 cookie 删除;不过也可以自己设置删除时间。
  • 安全标志:指定后, cookie 只有在使用 SSL 连接的时候才发送到服务器。
    1
    2
    3
    4
    HTTP/1.1 200 OK
    Content-type: text/html
    Set-Cookie: name=value; expires=Mon, 22-Jan-07 07:10:24 GMT; domain=.wrox.com
    Other-header: other-header-value

每一段信息都作为 Set-Cookie 头的一部分,使用分号加空格分隔每一段

1.3 JavaScript中的cookie

  • 获取属性值时,document.cookie返回当前页面可用的(根据cookie的域、路径、失效时间和安全设置)所有cookie的字符串,一系列由分号隔开的名值对儿。
  • 设置值的时候,document.cookie属性可以设置为一个新的cookie字符串。这个cookie字符串会被解释并添加到现有的 cookie 集合中,不会覆盖。
  • 基本的cookie操作有三种:读取、写入和删除。它们在 CookieUtil 对象中如下表示
    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
    var CookieUtil = {
    //读取
    get: function (name){
    var cookieName = encodeURIComponent(name) + "=",
    cookieStart = document.cookie.indexOf(cookieName),
    cookieValue = null;

    if (cookieStart > -1){
    var 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) {
    var 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;
    },
    //变相删除
    unset: function (name, path, domain, secure){
    this.set(name, "", new Date(0), path, domain, secure);
    }
    };

    //用法----设置 cookie,包括它的路径、域、失效日期
    CookieUtil.set("name", "Nicholas",new Date("January 1, 2010"), "/books/projs/", "www.wrox.com",
    );

1.4 子cookie

子cookie是存放在单个cookie中的更小段数据,也就是使用cookie值来存储多个名称值对儿。如

1
name=name1=value&name2=value2&name3=value3&name4=value4&name5=value5

子cookie的方法对象

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
var SubCookieUtil={

get:function(name,subName){
var subCookies=this.getAll(name);
if(subCookies){
return subCookie[subName];
}else{
return null;
}
},

getAll:function(name){
var cookieName=encodeURIComponent("name")+"=",
cookieStart=document.cookie.indexOf(cookieName),
cookieValue=null,
cookieEnd,
subCookies,
i,
parts,
result={};

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

if(cookieValue.length>0){
subCookies=cookieValue.split("&");
for(i=0;i<subCookies.length;i++){
parts=subCookies[i].spilt("=");
result[decodeURIComponent(parts[0])]=decodeURIComponent(parts[1]);
}
return result;
}
}
return null;
},

set:function(name,subName,value,expires,path,domain,secure){
var subCookies=this.getAll(name)||{};
subCookies[subName]=value;
this.setAll(name,subCookies,expires,path,domain,secure);
},

setAll:function(name,subCookies,expires,path,domain,secure){
var cookieText=encodeURIComponent("name")+"=",
subCookieParts=new Array(),
subName;

for(subName in subCookies){
if(subName.length>0&&subCookies.hasOwnProperty(subName)){
subCookieParts.push(encodeURIComponent(subName)+"="+encodeURIComponent(subCookies[subName]));
}
}

if(cookieParts.length>0){
cookieText+=subCookieParts.join("&");

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

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

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

if(secure){
cookieText+="; secure";
}else{
cookieText+="; expiress="+(new Date(0).toGMTString();
}

document.cookie=cookieText;
}
},

unset:function(name,subName,path,domain,secure){
var subCookies=this.geTAll(name);
if(subCookies){
delete subCookies[subName];
this.setAll(name,subCookies,null,path,domain,secure);
}
},

unsetAll:function(name,path,domain,secure){
this.setAll(name,null,new Date(0),path,domain,secure);
}
};

2.IE用户数据

利用CSS指定userData行为

3.Web存储机制–Web Storage

  • 目的:克服由cookie带来的一些限制,当数据需要被严格控制在客户端上时,无须持续地将数据发回服务器。
  • 目标
    • 提供一种在cookie之外存储会话数据的途径
    • 提供一种存储大量可以跨会话存在的数据机制。
  • 包含两种对象定义:sessionStorageglobalStorage

3.1 Storage类型

Stroage类型提供最大的存储空间来存储名值对儿。

  • 有如下方法:
    • clear():删除所有值;Firefox 中没有实现。
    • getItem(name):根据指定的名字 name 获取对应的值。
    • key(index):获得 index 位置处的值的名字。
    • removeItem(name):删除由 name 指定的名值对儿。
    • setItem(name, value):为指定的 name 设置一个对应的值。

3.2 sessionStorage对象

  • 仅针对小段数据的存储。不适用于跨会话存储数据。
  • 存储特定于某个会话的数据,也就是该数据只保持到浏览器关闭。
  • sessionStorage对象绑定于某个服务器会话,则当文件在本地运行的时候是不可用的。存储在seesionStorage中的数据只能由最初给对象存储数据的页面访问。
    1
    2
    3
    4
    5
    迭代sessionStorage中的值:
    for(var key in sessionStorage){
    var value=sessionStorage.getItem(key);
    alert(key+"="+value);
    }

3.3 globalStorage对象

  • 要使用globalStorage,先要指定哪些域可以访问该数据。如下:

    1
    2
    3
    4
    5
    6
    7
    //保存数据
    globalStorage["wrox.com"].name="libo";

    //获取数据
    var name=globalStorage["wrox.com"].name;

    这里,访问的是针对域名wrox.com的存储空间。
  • gloabalStorage对象不是Storage的实例,而具体的globalStorage[“wrox.com”]才是。

    1
    2
    3
    4
    5
    6
    7
    //保存数据
    globalStorage["www.wrox.com"].name = "Nicholas";

    //获取数据
    var name = globalStorage["www.wrox.com"].name;

    这里所指定的存储空间只能由来自www.wrox.com的页面访问,其他子域名都不行。
  • 对globalStorage空间的访问,是依据发起请求的页面的域名、协议和端口来限制的。三者必须一样。

  • 非常适合在客户端存储文档或长期保存用户偏好设置。

3.4 localStorage对象

  • 修订过的HTML5规范中,取代globalStorage,作为持久客户端数据方案。
  • 不能给localStorage指定任何访问规则,事先已经设定好。
  • 数据保留到通过javascript删除或者用户清除浏览器缓存,和globalStorage一样。
  • 兼容globalStorage和localStorage
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    function getLocalStorage(){
    if(typeof localStorage=="object"){
    return localStorage;
    }else if(typeof globalStorage=="object"){
    return globalStorage[location.host];
    }else{
    throw new Error("Local storage not available.");
    }
    }

    var storage=getLocalStorage();

3.5 storage事件

  • 对Storage对象进行任何修改,都会在文档上触发storage事件。
  • 该事件的event对象有以下属性:
    • domain:发生变化的存储空间的域名。
    • key:设置或者删除的键名。
    • newValue:如果是设置值,则是新值;如果是删除键,则是null。
    • oldValue:键被更改之前的值。
  • 浏览器支持性不好。

3.6 限制

每个来源(协议、域、端口)都有固定大小的空间用于保存自己的数据。

4. IndexedDB

Indexed Database API,简称IndexedDB是在浏览器中保存结构化数据的一种数据库。

  • 思想宗旨:创建一套API,方便保存和读取JavaScript对象,同时还支持查询及搜索。
  • IndexDB操作是异步。
  • 每一次IndexDB操作,需要注册onerror或onsuccess事件处理程序,来确保适当处理结果。
  • 浏览器间有差异,需做如下处理:
    1
    var indexedDB=window.indexedDB||window.msIndexedDB||window.mozIndexedDB||window.webkitIndexedDB;

4.1 数据库

特色:使用对象保存数据,而不是使用表来保存数据。

  • 操作步骤:
  1. 打开它:调用indexDB.open(),传入数据库名。
  2. 调用indexDB.open()后会返回一个IDBRequest对象,在这个对象上添加onerror或onsuccess事件处理程序。错误码见书P644。
1
2
3
4
5
6
7
8
9
var request,database;

request=indexedDB.open("admin");
request.onerror=function(event){
alert("Something bad happend while trying to open:"+event.target.onerrorCode);
}
request.onsuccess=function(event){
database=event.target.result;
};
  1. 设置版本号:调用setVersion(),传入以字符串形式表示的版本号。同样,也会返回一个请求对象,需要指定事件处理程序。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
if (database.version != "1.0"){
request = database.setVersion("1.0");
request.onerror = function(event){
alert("Something bad happened while trying to set version: "+event.target.errorCode);
};
request.onsuccess = function(event){
alert("Database initialization complete. Database name: " + database.name +
", Version: " + database.version);
};
//真实项目开发中,应该在这里建立对象存储空间。
} else {
alert("Database already initialized. Database name: " + database.name +
", Version: " + database.version);
}

4.2 对象存储空间

建立与数据库连接后,下一步是使用对象存储空间。

  1. 创建对象存储空间。必须指定一个键,这个键要保存对象的一个属性。

    1
    2
    3
    4
    5
    6
    7
    8
    var user = {
    username: "007",
    firstName: "James",
    lastName: "Bond",
    password: "foo"
    };

    var store=db.createObjectStore("users",{keyPath:"username"});
  2. 使用add()put()方法来添加数据。都接受要保存的对象作为参数。add()–插入,put()–更新。

4.3 创建对象存储空间后,接下来所有操作都通过事务完成。

  1. 读取数据:默认只读。在数据库对象(前面代码中的database)上调用transaction()

    1
    2
    3
    var transaction=db.transaction("");
    var transaction = db.transaction("users");
    var transaction = db.transaction(["users", "anotherStore"]);
  2. 读写数据:传入第二个参数,更改访问模式。

    1
    2
    3
    var IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction;

    var transaction = db.transaction("users", IDBTransaction.READ_WRITE);
  3. 取得事务索引后,使用objectStore()方法并传入存储空间的名称,可以访问特定的存储空间。然后可以使用add()put()get()delete()clear()。5个方法都会返回一个新的请求对象。

    1
    2
    3
    4
    5
    6
    7
    8
    var request = db.transaction("users").objectStore("users").get("007");
    request.onerror = function(event){
    alert("Did not get the object!");
    };
    request.onsuccess = function(event){
    var result = event.target.result;
    alert(result.firstName); //"James"
    };

4.4 使用游标查询。

游标是指向结果集的指针,先指向结果中的第一项,在接到查找下一项的指令时,才会指向下一项。

  • 调用openCursor()能创建游标,返回一个请求对象,必须指定onsuccess和onerror事件处理程序。

    1
    2
    3
    4
    5
    6
    7
    8
    var store = db.transaction("users").objectStore("users"),
    request = store.openCursor();
    request.onsuccess = function(event){
    //处理成功
    };
    request.onerror = function(event){
    //处理失败
    };
  • onsuccess事件处理程序执行时,可以通过event.target.result取得存储空间中的下一个对象。结果集中有下一项时,这个属性中保存一个IDBCursor的实例,在没有下一项时,这个属性值为null。IDBCursor的实例属性:

    • direction:数值,表示游标移动的方向。
    • key:对象的键
    • value:实际的对象
    • primaryKey:游标使用的键。
      1
      2
      3
      4
      5
      6
      7
      8
      要检索某一个结果:
      //接上一段代码
      request.onsuccess=function(event){
      var cursor=event.target.result;
      if(cursor){//必须要检查
      console.log("Key:"+cursor.key+",Value:"+JSON.stringify(cursor.value));
      }
      };
  • 使用游标可以更新个别的记录。调用update()方法可以用指定的对象更新当前游标的value。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    //接上上一段代码
    request.onsuccess=function(event){
    var cursor=event.target.result,
    value,
    updateRequest;

    if(cursor){
    if(cuesor.key=="foo"){
    value=cursor.value;
    value.password="magic!";

    updateRequest=cursor.update(value);
    updateRequest.onsuccess=function(){
    //处理成功
    };
    updateRequest.onerror=function(){
    //处理失败
    };
    }
    }
    };
  • 默认情况下,每个游标只发起一次请求。要想发起另一次请求,则需下面的方法:

    • continue(key):移动到结果集中的下一项。参数key为可选。
    • advance(count):向前移动count项数。
    • 2个方法都会导致游标使用相同的请求,因此相同的onsucccess和onerror也会得到重用。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      //遍历对象存储空间所有项
      request.onsuccess=function(event){
      var cursor=event.target.result;
      if(cursor){
      console.log("Key:"+cursor.key+",Value:"+JSON.stringify(cursor.value));
      cursor.continue();//移动到下一项
      }else{
      console.log("Done!");
      }
      };

4.5 键范围

键范围由IDBKeyRange的实例表示。使用前先声明一个本地类型。

1
var IDBKeyRange=window.IDBKeyRange||window.webkitIDBKeyRange;
  • 有四种定义键范围的方式
  1. only(),传入你想要取得的对象的键。

  2. lowerBound(),指定结果集的下界。下界表示游标开始的位置。

    1
    2
    3
    4
    //从键为"007"的对象开始,然后可以移动到最后
    var lowerRange = IDBKeyRange.lowerBound("007");
    //从键为"007"的对象的下一个对象开始,然后可以移动到最后
    var lowerRange = IDBKeyRange.lowerBound("007", true);

    3.upperBound(),指定结果集的上界。指定游标不能超越哪个键。

1
2
3
4
//从头开始,到键为"ace"的对象为止
var upperRange = IDBKeyRange.upperBound("ace");
//从头开始,到键为"ace"的对象的上一个对象为止
var upperRange = IDBKeyRange.upperBound("ace", true);
  1. bound(),同时指定上、下界。
1
2
3
4
5
6
//从键为"007"的对象开始,到键为"ace"的对象为止
var boundRange = IDBKeyRange.bound("007", "ace");
//从键为"007"的对象的下一个对象开始,到键为"ace"的对象为止
var boundRange = IDBKeyRange.bound("007", "ace", true);
//从键为"007"的对象的下一个对象开始,到键为"ace"的对象的上一个对象为止
var boundRange = IDBKeyRange.bound("007", "ace", true, true);
  • 定义键范围后,把它传给openCursor()方法,能得到一个符合相应约束条件的游标。
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    var store=db.transaction('users').objectStore('users'),
    range=IDBKeyRange.bound('007','ace'),
    request=store.openCursor(range);

    request.onsuccess=function(event){
    var cursor=event.target.result;
    if(cursor){
    console.log("Key:"+cursor.key+",Value:"+JSON.stringify(cursor.value));
    cursor.continue();//移动到下一项
    }else{
    console.log("Done!");
    }
    };

4.6 设定游标方向

openCursor()可以接受两个参数:

  • 第一个参数是IDBKeyRange实例。
  • 第二个参数是表示方向的数值常量,即IDBCursor中的常量。也需要消除浏览器差异
    1
    2
    3
    4
    var IDBCursor=window.IDBCursor||window.webkitIDBCursor;

    var store=db.transaction('users').objectSrore('users'),
    request=store.opebCursor(null,IDBCursor.NEXT_NO_DUPLICATE);

4.7 索引

对于某些数据,可能需要为一个对象储存空间指定多个键。那么其中一个作为主键,其他键作为索引。

  • 要创建索引,首先引用对象存储空间,然后调用createIndex().

    1
    2
    var store=db.tansaction('users'),objectStore('users'),
    index=store.createIndex('username','username',{unique:false});
  • 使用索引,则是调用index()createIndex()index()方法都返回IDBIndex实例。

  • IDBIndex对象的属性:

    • name:索引的名字
    • keyPath:传入createIndex()中的属性路径
    • objectStore:索引的对象存储空间
    • unique:表示索引键是否唯一的布尔值。
  • 索引和对象存储空间很相似,也能在索引上调用openCursor()、get()方法。

  • 在对象存储空间上调用deleteIndex()方法并传入索引的名字即可删除。删除索引不会影响对象存储空间的数据,所以这个操作无回调函数。

4.8 并发问题

  • 浏览器两个标签打开同一个页面,当一个页面试图更新另一个页面没有准备就绪的数据库时可能发生并发问题。解决方案是:
  1. 打开数据库后,需指定onversionchange事件处理程序(当同一个来源的另一个标签页调用setVersion()时执行),来关闭数据库。

    1
    2
    3
    4
    5
    6
    7
    8
    var request,database;
    request=indexedDB.open('admin');
    request.onsuccess=function(event){
    database=event.target.result;
    database.onversionchange=function(){
    database.close();
    }
    }
  2. 调用setVersion()时,指定请求的onblocked事件处理程序(在你更新数据库版本但另一个标签页已经打开数据库时执行)。

    1
    2
    3
    4
    5
    6
    7
    var request=database.setVersion('2.0');
    request.onblocked=function(){
    alert('Please close all other tabs and try again.');
    };
    request.onsuccess=function(){
    //处理成功,继续
    }

4.9 限制

  1. 同源限制。IndexedDB数据库只能同源(相同的协议、域名、端口)页面操作。
  2. 占用磁盘空间大小限制。
  3. Firefox不允许本地访问IndexedDB。
liborn wechat
欢迎您扫一扫上面的微信二维码,订阅我的公众号!