高程3总结#第23章离线应用与客户端存储

离线应用与客户端存储

离线检测

  • HTML5定义了navigator.onLine属性来检测设备是在线还是离线。这个属性为true表示设备能上网,值为false表示设备离线。这个属性的关键是浏览器必须知道设备能否访问网络,从而返回正确的值
  • 不同浏览器之间有小差异

    • IE6+和Safari5+能够正确检测到网络已经断开,并将navigator.onLine的值转换为false
    • Firefox3+和Opera10.6+支持navigator.onLine属性,但必须手工选中菜单项脱机工作才能让浏览器正常工作
    • Chrome11以及之前版本始终将navigator.onLine属性设置为true
  • 为了更好确定网络是否可用,HTML5还定义了两个事件onLine和offLine。当网络从离线变为在线或者从在线变为离线时,分别触发这两个事件

    EventUtil.addHandler(window, "online", function(){
      alert("Online");
    });
    EventUtil.addHandler(window, "offline", function(){
      alert("Offline");
    });

应用缓存

  • applicationCache 对象,status属性,属性的值是常量,表示应用缓存的如下当前状态。

    • 0,无缓存,即没有与页面相关的应用缓存。
    • 1,闲置,即应用缓存未得到更新。
    • 2,检查中,即正在下载描述文件并检查更新。
    • 3,下载中,即应用缓存正在下载描述文件中指定的资源。
    • 4,更新完成,即应用缓存已经更新了资源,而且所有资源都已下载完毕,可以通过 swapCache()来使用了。
    • 5,废弃,即应用缓存的描述文件已经不存在了,因此页面无法再访问应用缓存。
  • 应用缓存还有很多相关的事件,表示其状态的改变。以下是这些事件。

    • checking,在浏览器为应用缓存查找更新时触发。
    • error,在检查更新或下载资源期间发生错误时触发。
    • noupdate,在检查描述文件发现文件无变化时触发。
    • downloading,在开始下载应用缓存资源时触发。
    • progress,在文件下载应用缓存的过程中持续不断地触发。
    • updateready,在页面新的应用缓存下载完毕且可以通过 swapCache() 使用时触发。
    • cached,在应用缓存完整可用时触发。

数据存储

Cookie

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

    • 名称,一个唯一确定 cookie 的名称。cookie 名称是不区分大小写的,所以 myCookie 和 MyCookie被认为是同一个 cookie。然而,实践中最好将 cookie 名称看作是区分大小写的,因为某些服务器会这样处理 cookie。cookie 的名称必须是经过 URL 编码的。
    • 值,储存在 cookie 中的字符串值。值必须被 URL 编码。
    • 域,cookie 对于哪个域是有效的。所有向该域发送的请求中都会包含这个 cookie 信息。这个值可以包含子域(subdomain,如 www.wrox.com ),也可以不包含它(如. wrox.com ,则对于wrox.com的所有子域都有效)。如果没有明确设定,那么这个域会被认作来自设置 cookie 的那个域。
    • 路径,对于指定域中的那个路径,应该向服务器发送 cookie。例如,你可以指定 cookie 只有从http://www.wrox.com/books/ 中才能访问,那么 http://www.wrox.com 的页面就不会发
      送 cookie 信息,即使请求都是来自同一个域的。
    • 失效时间,表示 cookie 何时应该被删除的时间戳(也就是,何时应该停止向服务器发送这个cookie)。默认情况下,浏览器会话结束时即将所有 cookie 删除;不过也可以自己设置删除时间。这个值是个 GMT 格式的日期(Wdy, DD-Mon-YYYY HH:MM:SS GMT),用于指定应该删除cookie 的准确时间。因此,cookie 可在浏览器关闭后依然保存在用户的机器上。如果你设置的失效日期是个以前的时间,则 cookie 会被立刻删除。
    • 安全标志,指定后,cookie 只有在使用 SSL 连接的时候才发送到服务器。例如,cookie 信息只能发送给https://www.wrox.com ,而 http://www.wrox.com 的请求则不能发送 cookie。
  • 基本的cookie操作有3种:读取、写入和删除

    //设置 cookie
    CookieUtil.set("name", "Nicholas");
    CookieUtil.set("book", "Professional JavaScript");
    //读取 cookie 的值
    alert(CookieUtil.get("name")); //"Nicholas"
    alert(CookieUtil.get("book")); //"Professional JavaScript"
    //删除 cookie
    CookieUtil.unset("name");
    CookieUtil.unset("book")
    //设置 cookie,包括它的路径、域、失效日期
    CookieUtil.set("name", "Nicholas", "/books/projs/", "www.wrox.com",
                   new Date("January 1, 2010"));
    //删除刚刚设置的 cookie
    CookieUtil.unset("name", "/books/projs/", "www.wrox.com");
    //设置安全的 cookie
    CookieUtil.set("name", "Nicholas", null, null, null, true);
  • 确保删除cookie

    //设置 cookie
    CookieUtil.set("name", "Nicholas");
    CookieUtil.set("book", "Professional JavaScript");
    //读取 cookie 的值
    alert(CookieUtil.get("name")); //"Nicholas"
    alert(CookieUtil.get("book")); //"Professional JavaScript"
    //删除 cookie
    CookieUtil.unset("name");
    CookieUtil.unset("book")
    //设置 cookie,包括它的路径、域、失效日期
    CookieUtil.set("name", "Nicholas", "/books/projs/", "www.wrox.com",
                   new Date("January 1, 2010"));
    //删除刚刚设置的 cookie
    CookieUtil.unset("name", "/books/projs/", "www.wrox.com");
    //设置安全的 cookie
    CookieUtil.set("name", "Nicholas", null, null, null, true);
  • 子cookie一般以查询字符串的格式进行格式化。然后这些值可以使用单个cookie进行存储和访问,而非对每个名称-值对儿使用不同的cookie存储
  • 要获得一个子cookie,首先要遵循与获得cookie一样的基本步骤,但是在解码cookie值之前,需要按下面方法找出子cookie信息

    var SubCookieUtil = {
      get: function (name, subName){
        var subCookies = this.getAll(name);
        if (subCookies){
          return subCookies[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(";", cookieStart);
          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, len=subCookies.length; i < len; i++){
              parts = subCookies[i].split("=");
              result[decodeURIComponent(parts[0])] =
                decodeURIComponent(parts[1]);
            }
            return result;
          }
        }
        return null;
      },
      //省略了更多代码
    };
  • 要设置子cookie,也有两种方法set()和setAll()

    var SubCookieUtil = {
      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 += "; expires=" + (new Date(0)).toGMTString();
        }
        document.cookie = cookieText;
      },
      //省略了更多代码
    };
  • 为了删除一个子cookie,首先必须获得包含在某个cookie中的所有子cookie,然后仅删除需要删除的那个子cookie,然后再将余下的子cookie的值保存为cookie的值

    var SubCookieUtil = {
      //这里省略了更多代码
      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);
      }
    };

IE用户数据

  • 一旦元素使用了userData行为,那么就可以使用setAttribute()方法在上面保存数据了,为了将数据提交到浏览器缓存中,还必须调用save()方法,并告诉它要保存的数据空间的名字

    var dataStore = document.getElementById("dataStore");
    dataStore.setAttribute("name", "Nicholas");
    dataStore.setAttribute("book", "Professional JavaScript");
    dataStore.save("BookInfo")

Web存储机制

Storage类型

  • Storage的实例与其他对象类似

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

sessionStorage对象

  • 可以使用setItem()或者直接设置新的属性类存储数据

    //使用方法存储数据
    sessionStorage.setItem("name", "Nicholas");
    //使用属性存储数据
    sessionStorage.book = "Professional JavaScript";
  • sessionStroage中有数据时,可以使用getItem()或者通过直接访问属性名来获取数据

    //使用方法读取数据
    var name = sessionStorage.getItem("name");
    //使用属性读取数据
    var book = sessionStorage.book;

globalStorage对象

  • 可以通过方括号标记使用属性来实现

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

localStorage对象

  • 可以像使用sessionStorage一样来使用

    //使用方法存储数据
    localStorage.setItem("name", "Nicholas");
    //使用属性存储数据
    localStorage.book = "Professional JavaScript";
    //使用方法读取数据
    var name = localStorage.getItem("name");
    //使用属性读取数据
    var book = localStorage.book;

storage事件

  • 对Storage对象进行任何修改,都会在文档上触发storage事件,当通过属性或setItem()方法保存数据,使用delete操作符或removeItem()删除数据,或者调用clear()方法时,都会发生这个事件,这个事件的event对象有以下属性

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

IndexedDB

数据库

  • IndexedDB最大的特色就是使用对象保存数据,而不是使用表来保存数据
  • 使用IndexedDB第一步是打开它,将要打开的数据库名传给indexedDB.open()。如果传入的数据库已经存在,就会发送一个打开它的请求,如果传入的数据库不存在,就发送一个创建并打开它的请求

    var request, database;
    request = indexedDB.open("admin");
    request.onerror = function(event){
      alert("Something bad happened while trying to open: " +
            event.target.errorCode);
    };
    request.onsuccess = function(event){
      database = event.target.result;
    }
  • 可能的错误码

    • IDBDatabaseException.UNKNOWN_ERR (1),意外错误,无法归类。
    • IDBDatabaseException.NON_TRANSIENT_ERR (2),操作不合法。
    • IDBDatabaseException.NOT_FOUND_ERR (3),未发现要操作的数据库。
    • IDBDatabaseException.CONSTRAINT_ERR (4),违反了数据库约束。
    • IDBDatabaseException.DATA_ERR (5),提供给事务的数据不能满足要求。
    • IDBDatabaseException.NOT_ALLOWED_ERR (6),操作不合法。
    • IDBDatabaseException.TRANSACTION_INACTIVE_ERR (7),试图重用已完成的事务。
    • IDBDatabaseException.ABORT_ERR (8),请求中断,未成功。
    • IDBDatabaseException.READ_ONLY_ERR (9),试图在只读模式下写入或修改数据。
    • IDBDatabaseException.TIMEOUT_ERR (10),在有效时间内未完成操作。
    • IDBDatabaseException.QUOTA_ERR (11),磁盘空间不足
  • IndexedDB没有版本号,一开始为数据库指定一个版本号,可以调用setVersion()方法,传入以字符串形式表示的版本号

    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);
    }

对象存储空间

  • 如果想验证请求是否成功完成,可以把返回的请求对象保存在一个变量中,然后再指定onerror或onsuccess事件处理程序

    //users 中保存着一批用户对象
    var i=0,
        request,
        requests = [],
        len = users.length;
    while(i < len){
      request = store.add(users[i++]);
      request.onerror = function(){
        // 处理错误
      };
      request.onsuccess = function(){
        // 处理成功
      };
      requests.push(request);
    }

事务

  • 事务对象本身也有事件处理程序:onerror和oncomplete

    transaction.onerror = function(event){
      //整个事务都被取消了
    };
    transaction.oncomplete = function(event){
      //整个事务都成功完成了
    };

使用游标查询

  • 实例有几个属性

    • direction,数值,表示游标移动的方向。默认值为 IDBCursor.NEXT (0),表示下一项。IDBCursor.NEXT_NO_DUPLICATE (1)表示下一个不重复的项, IDBCursor.PREV (2)表示前一项,而 IDBCursor.PREV_NO_DUPLICATE 表示前一个不重复的项。
    • key,对象的键。
    • value,实际的对象。
    • primaryKey,游标使用的键。可能是对象键,也可能是索引键
  • 调用update()方法可以指定的对象更新当前游标的value,与其他操作一样,调用update()方法也会创建一个新的请求

    request.onsuccess = function(event){
      var cursor = event.target.result,
          value,
          updateRequest;
      if (cursor){ //必须要检查
        if (cursor.key == "foo"){
          value = cursor.value; // 取得当前的值
          value.password = "magic!"; // 更新密码
          updateRequest = cursor.update(value); // 请求保存更新
          updateRequest.onsuccess = function(){
            // 处理成功
          };
          updateReqeust.onerror = function(){
            // 处理失败
          };
        }
      }
    };
  • 调用delete()方法,会删除相应的记录

    request.onsuccess = function(event){
      var cursor = event.target.result,
          value,
          deleteRequest;
      if (cursor){ //必须要检查
        if (cursor.key == "foo"){
          deleteRequest = cursor.delete(); // 请求删除当前项
          deleteRequest.onsuccess = function(){
            // 处理成功
          };
          deleteRequest.onerror = function(){
            // 处理失败
          };
        }
      }
    };
  • 默认情况下,每个游标只发起一次请求,要想发起另一次请求,必须调用下面的方法

    • continue(key),移动到结果集中的下一项。参数 key 是可选的,不指定这个参数,游标移动到下一项;指定这个参数,游标会移动到指定键的位置。
    • advance(count),向前移动 count 指定的项数。

键范围

  • 先声明一个本地的类型

    var IDBKeyRange = window.IDBKeyRange || window.webkitIDBKeyRange;
  • 四种方式

    • 使用only()方法

      var onlyRange = IDBKeyRange.only("007");
    • 指定结果集的下界

      //从键为"007"的对象开始,然后可以移动到最后
      var lowerRange = IDBKeyRange.lowerBound("007");
      //如果你想忽略键为 "007" 的对象,从它的下一个对象开始,那么可以传入第二个参数 true
      //从键为"007"的对象的下一个对象开始,然后可以移动到最后
      var lowerRange = IDBKeyRange.lowerBound("007", true);
    • 指定结果集的上界

      //从头开始,到键为"ace"的对象为止
      var upperRange = IDBKeyRange.upperBound("ace");
      //如果你不想包含键为指定值的对象,同样,传入第二个参数 true
      //从头开始,到键为"ace"的对象的上一个对象为止
      var upperRange = IDBKeyRange.upperBound("ace", true);
    • 同时指定结果集的上界下界。这个方法接收4个参数:表示下界的键、表示上界的键、可选的表示是否跳过下界的布尔值、可选的表示是否跳过上界的布尔值

      //从键为"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);
      //从键为"007"的对象开始,到键为"ace"的对象的上一个对象为止
      var boundRange = IDBKeyRange.bound("007", "ace", false, true)

索引

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

    var store = db.transaction("users").objectStore("users"),
        index = store.createIndex("username", "username", { unique:  false });
  • 索引上调用openCursor()方法可以创建新的游标,除了将来会把索引键而非主键保存在event.result.key属性中之外,这个游标与在对象存储空间上调用openCursor()返回的游标完全一样

    var store = db.transaction("users").objectStore("users"),
        index = store.index("username"),
        request = index.openCursor();
    request.onsuccess = function(event){
      //处理成功
    };
  • 索引上也能创建一个特殊的只返回每条记录主键的游标,调用openKeyCursor()方法,这个方法接收的参数与openCursor()相同

    var store = db.transaction("users").objectStore("users"),
        index = store.index("username"),
        request = index.openKeyCursor();
    request.onsuccess = function(event){
      //处理成功
      // event.result.key 中保存索引键,而 event.result.value 中保存主键
    };
  • 使用get()方法能够从索引中取得一个对象

    var store = db.transaction("users").objectStore("users"),
        index = store.index("username"),
        request = index.get("007");
    request.onsuccess = function(event){
      //处理成功
    };
    request.onerror = function(event){
      //处理失败
    };
  • 要根据给定的索引键取得主键,可以使用getKey()方法

    var store = db.transaction("users").objectStore("users"),
        index = store.index("username"),
        request = index.getKey("007");
    request.onsuccess = function(event){
      //处理成功
      //event.result.key 中保存索引键,而 event.result.value 中保存主键
    }
  • 通过IDBIndex对象的属性可以获得有关索引的相关信息

    • name,索引的名字
    • keyPath,传入createIndex()中的属性路径
    • objectStore,索引的对象存储空间
    • unique,表示索引键是否唯一的布尔值
  • 下面代码可以知道根据存储的对象建立了哪些索引

    var store = db.transaction("users").objectStore("users"),
        indexNames = store.indexNames,
        index,
        i = 0,
        len = indexNames.length;
    while(i < len){
      index = store.index(indexNames[i++]);
      console.log("Index name: " + index.name + ", KeyPath: " + index.keyPath +
                  ", Unique: " + index.unique);
    }

并发问题

  • 刚打开数据库时,要记着指定 onversionchange 事件处理程序。当同一个来源的另一个标签页调
    用 setVersion() 时,就会执行这个回调函数。处理这个事件的最佳方式是立即关闭数据库,从而保证
    版本更新顺利完成

    var request, database;
    request = indexedDB.open("admin");
    request.onsuccess = function(event){
      database = event.target.result;
      database.onversionchange = function(){
        database.close();
      };
    }

限制

  • IndexedDB数据库只能由同源页面操作,因此不能跨域共享信息
  • 每个来源的数据库占用的磁盘空间有限制
  • 不允许本地文件访问IndexedDB

相关推荐