离线应用与客户端存储(23)

-进行离线检测
-使用离线缓存
-在浏览器中保存数据

离线web应用,就是设备在不能上网的情况下仍然可以运行的应用。

离线检测

navigator.onLine
这个属性的关键是浏览器必须知道设备能否访问网络
true ==> 表示设备能上网
false ==> 表示设备离线

IE6和Safari 5+ 能够正确检测到网络已经断开...略有兼容问题。但手机端的话,还ok

        if(navigator.onLine){
            console.log('能够连接到网络')
        }else{
            console.log('断网啦')
        }

online,offline

        var EventUtil = {

            addHandler: function (element, type, handler) {
                if (element.addEventListener) {        //DOM2级
                    element.addEventListener(type, handler, false);

                } else if (element.attachEvent) {      //DOM1级
                    element.attachEvent("on" + type, handler);

                } else {
                    element["on" + type] = handler;    //DOM0级
                }
            },
        }
        
        EventUtil.addHandler(window,'online',function(){
            alert('online')
        })

        EventUtil.addHandler(window,'offline',function(){
            alert('offline')
        })

当关闭网络或开启网络时可以得到校验结果

navigator.onLine + online,offline
在页面加载后,最好先通过navigator.onLine取得初始的状态。再通过online,offline来确定状态是否变化。


应用缓存

appcache
Appcache是专门从浏览器的缓存中跟出来的一块缓存区,使用描述文件manifest file

<html manifest="offline.appcache">

描述文件的扩展名以前推荐用manifest.现在推荐用appcache

CACHE MANIFEST
#Comment
index.js //描述文件中列出的都是需要下载的资源,已备离线时使用

applicationCache
虽然应用缓存的意思是确保离线时资源可用,但也有相应的JavaScript API 让你知道它在作什么
状态

  • 0
    无缓存
  • 1
    闲置。未更新
  • 2
    检查中。正在下载描述文件并检查更新
  • 3
    下载中,即应用混存正在下载描述文件
  • 4
    更新完成。可以通过swapCache()来使用
  • 5
    废弃。即应用已经不存在
    事件
  • checking
    在浏览器为应用缓存查找更新时触发
  • error
    在检查更新或下载资源期间发生错误时触发
  • noupdate
    在检查描述文件发现文件无变化时触发
  • downloading
    在开始下载应用缓存资源时触发
  • progress
    在文件下载应用缓存的过程中持续不断的触发
  • updateready
    在页面新的应用缓存下载完毕且可以通过swapCache()使用时触发
  • cached
    在应用缓存完整可用时触发

数据存储

Cookie
最初是在客户端用于存储会话信息的,服务器对任意HTTP请求发送Set-Cookie头部,发送回服务器的额外信息可以用于唯一验证客户来自于发送的那个请求。

  1. 限制
    cookie在性质上是绑定在特定的域名下的(浏览器限制),每个域的cookie总数是有限的,当超过数量时,浏览器会清除以前设置的cookie,且会影响传输速度
  2. cookie的构成
    名称:不区分大小写
    :存储在cookie中的字符串值,值必须被URL编码
    :cookie对于哪个域是有效的。所有向该域发送的请求都会包含这个cookie信息。这个只可以包含子域,也可以不包含
    路径:对于指定域中的路径,应该向服务器发送cookie,例如:
    http://www.xxxx.com/demo/中才能访问,那么http://www.xxxx.com的页面就不会发送cookie信息,即使请求都是来自同一个域的。
    失效时间:是GMT格式的日期,用于指定应该删除的准确时间
    安全标志:指定后cookie只有在使用SSL连接的时候才发动到服务器。
HTTP/1.1 200 OK 
Content-type: text/html 
Set-Cookie: name=value; 
domain=.wrox.com; path=/; secure 
Other-header: other-header-value
//以上表示一个叫做name的cookie,
//在格林威治2007年1月22日7:10:24失效,
//同时对于www.wrow.com和wrow.com的任何子域(如:p2p.wrow.com)都有效
//secure 是cookie中唯一一个非名值对的部分,直接包含一个secure单词。

域、路径、失效时间、和secure标志都是读武器给浏览器的知识,以指定何时应该发送cookie,这些参数并不会作为发送到服务器的cookie信息的一部分,只有名值对才会被发送

3.JavaScript的cookie

name1=value1;name2=value2;name3=value3

所有名字和值都是经过URL编码的,所以必须使用decodeURIComponent()来解码
4.子cookie
为了绕开浏览器的但域名下的cookie数限制,一些开发人员使用了一种称为子cookie。例如:

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

这昂网站或者web应用可以无需达到单域名cookie上限也可以存储更加结构化的数据。
5.关于cookie的思考

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

不要在cookie中存储敏感信息,因为都可以被访问到

cookie信息越大。完成对服务的请求时间也就越长。


Web存储机制

Web Storage

  • 提供一种在cookie之外存储会话数据的途径
  • 提供一种存储大量可以跨会话存在的数据的机制
  1. Storage类型

Storage类型提供最大的存储空间,storage的实例于其他对象类似

clear():删除所有值,Firefox中没有实现,只能清除值,不建议使用这个方法。
getItem(name): 根据指定的名字name获取对应的值
key(index):获得index位置处的值的名字
removeItem(name):删除由name指定的名值对
setItem(name,value):为指定的name设置一个对应的值

Storage类型只能存储字符串,非字符串的数据在存储之前会被转换成字符串


2.sessionStorage对象
sessionStorage对象存储特定于某个会话的数据,也就是该数据值保持到浏览器关闭。这个对象就像会话cookie,也会在浏览器关闭后消失,刷新仍然保留。
3.globalStorage对象(已被localStorage取代)
4.localStorage对象
不能给localStorage指定任何访问规则,页面必须来自同一个域名(子域名无效)使用同一种协议。
5.限制
localStorage:大多数是5MB,Chrome和Safari限制是2.5MB
sessionStorage:大多数是5MB,Chrome和Safari限制是2.5MB


IndexedDB

Indexed Database API,或者简称为IndexedDB,是在浏览器中保存结构化数据的一种数据库。
IndexedDB的思想是创建一套API,方便保存和读取JavaScript对象,同时还支持查询及搜索。
我们使用时还是应该要注意浏览器的兼容

var indexedDB = window.indexedDB ||
window.msIndexedDB ||
window.mozIndexedDB ||
window.webkitIndexedBD;

数据库
IndexedDB就是一个数据库,可以被网页脚本创建和操作,IndexDB允许存储大量数据,提供查找接口,还能建立索引,与MySQL或Web SQL Database等这些你以前可能用过的数据库类似。IndexedDB最大的特色是使用对象保存数据,而不是用表来保存。(IndexDB不属于关系型数据库,不支持SQL查询)
特性
1.键值对存储
IndexDB内部采用对象仓库(object store)存放数据,所有类型的数据都可以直接存入,包括JavaScript对象,数据以“键值对”形式保存,每一额数据记录都有对应的主键,主键独一无二,重复会报错。
2.异步
IndexedDB操作时不会锁死浏览器,用户依然可以进行其他操作(LocalStorage是同步操作)
3.支持事务
IndexedDB支持事务(transaction 游标),这意味着一系列操作步骤中,只要有一步失败,整个事务就都取消,数据库回滚到事务发生之前的状态,不存在只改写一部分数据的情况。
4.同源限制
IndexDB受到同源限制,每一个数据库对象创建它的域名,网页只能访问自身域名下的数据库,而不能访问跨域的数据库。
5.存储空间大
一般不少于250MB,甚至没有上限
6.支持二进制存储
不仅可以存储字符串,还可以存储二进制数据(ArrayBuffer对象和Blob对象)


操作

  1. 打开数据库
    indexDB.open()
    如果存在则打开,如果不存在就创建一个并打开
    调用indexDB.open()会返回一个IDBRequest对象,这个对象上可以添加onerror和onsuccess事件处理程序
        var request, database
        request = indexedDB.open('demo'); // window.indexedDB.open(databaseName,version)
        request.onerror = function(event){
            console.log('Something bad happened',event)
        }
        request.onsuccess = function (event){
            console.log(event)
        }
        request.onupgradeneeded = function (event) {
             console.log(event)
        } //upgradeneeded事件,如果指定版本号大于数据库的实际版本号,会发生数据库升级事件

方法接受2个参数,第一个参数是数据库的名字,如果不存在新建,第二个参数是整数,表示版本,如果省略,打开已有的数据库时,默认为当前版本,新建数据库时,默认为1

错误码
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()方法,传入以字符串形式表示的版本号。同样,调用这个方法也会返回一个请求对象。

        function openIndexedDB(){
            return new Promise(
                (resolve,reject) => {
                    var request;
                    request = indexedDB.open('demo');
                    request.onerror = function(event){
                        reject(event.target.errorCode)
                    }
                    request.onsuccess = function (event){
                        resolve(event.target.result)
                    }
                }
            )
        }
        openIndexedDB().then(
            (res) => {
                console.log('IndexedDB',res)
            }
        )
  1. 对象存储空间
    在建立了与数据库的连接之后,下一步就是使用对象存储空间,如果数据库的版本与你传入的版本不匹配,需要创建一个新的对象存储空间,在创建存储空间之前,必须要想清楚你想要什么数据类型。
var user = {
  username: '007',
  firstName:'James',
  lastName:'Bond',
  password: 'foo',
}

在这里贴一篇详细的IndexDB的文章
https://wangdoc.com/javascript/bom/indexeddb.html#indexeddb-%E5%AF%B9%E8%B1%A1
3.新建数据库
新建数据库与打开数据库是同一个操作,不存在就新建,不同的在于,后续的操作主要在upgradeneeded事件的监听函数完成,因为版本从无到有,就是触发。

  • 新建仓库(同MySql里的新建表)
request.onupgradeneeded = function (event){
    db = event.target.result;
    var objectStore = db.createObjectStore('person',{ keyPath: 'id' });
}
  • 新增表
    数据库新建成功后,新增一张叫做 person 的表格,主键是 id
    更好的写法是先判断一下,这张表格是否存在,不存在再新建
         var db = null;
         var request = window.indexedDB.open('demo')
         request.onerror = function (event) {
             db = request.result
             console.log( '数据库打开报错' )
         }
         request.onsuccess = function (event) {
             db = request.result
             console.log( '数据库打开成功' )
         }
         request.onupgradeneeded = function (event) {
             db = event.target.result
             var objectStore ;
             if(!db.objectStoreNames.contains('person')){
                 objectStore = db.createObjectStore('person', {keyPath: 'id'})
             }
             console.log('创建了仓库',objectStore);
         }

主键(keyPath)是默认建立索引的属性,比如:数据记录是{ id: 1, name: '张三' },那么id属性可以作为主键。主键也可以指定为下一层对象的属性,比如:{ foo, { bar: 'baz' } }的foo.bar也可以指定为主键。

如果 数据记录里面没有合适作为主键的属性,那么可以让IndexedDB自动生成主键

var objectStore = db.createObjectStore(
  'person',
  { autoIncrement: true }
);
//指定主键为一个递增的整数
  • 新建索引
    新建仓库后,下一步可以新建索引
request.onupgradeneeded = function (event) {
    db = event.target.result
    var objectStore ;
    if(!db.objectStoreNames.contains('person')){
        objectStore = db.createObjectStore('person', {autoIncrement: true})
        objectStore.createIndex( 'name', 'name', {unique: false} )
        objectStore.createIndex( 'email', 'email', {unique: true} )
        //createIndex的三个参数分别为索引名称、索引所在的属性、配置对象(说明该属性是否包含重复的值)
    }
    console.log('创建了仓库',objectStore);
}

事务
接下来的一系列我们都叫做事务。
在数据库对象上调用transaction()方法可以创建事务,任何时候,只要想读取或修改数据,都要通过事务来组织所有操作。

var transaction = db.transaction(name);
//name为需要传入的参数
var transaction = db.transaction(['name','email']);
//如果需要访问多个对象存储空间,也可以在第一个参数的位置传字符串数组

以上都是以只读方式访问数据,要修改访问方式,必须在创建事务时传入第二个参数,这个参数表示访问模式
READ_ONLY:只读
READ_WRITE:读写
VERSION_CHANGE:改变

IE10+和Firefox 4+实现的是IDBTransaction,但在Chrome中则叫webkitIDBTransaction,使用以下代码可以统一:

var IDBTransaction = window.IDBTransaction || window.webkitIDBTransaction

var transaction = db.transaction('users',IDBTransaction.READ_WRITE)
  • 新增数据
    新增数据指的是向对象仓库写入数据记录。通过事务完成
function createStore (){
    return new Promise(
        (resolve,reject) => {
            var db = null;
            var request = window.indexedDB.open('demo')
            request.onerror = function (event) {
                db = request.result
                console.log( '数据库打开报错',db )
                reject (db)
            }
            request.onsuccess = function (event) {
                db = request.result
                console.log( '数据库打开成功',db )
                resolve(db)
            }
            request.onupgradeneeded = function (event) {
                db = event.target.result
                var objectStore;
                if (!db.objectStoreNames.contains('person')) {
                    objectStore = db.createObjectStore('person', {autoIncrement: true})
                    objectStore.createIndex('name', 'name', {unique: false})
                    objectStore.createIndex('email', 'email', {unique: true})
                    //createIndex的三个参数分别为索引名称、索引所在的属性、配置对象(说明该属性是否包含重复的值)
                }
                console.log('创建了仓库', objectStore);
                // return db
            }
        }
    )
}
async function add() {
    let db = await createStore()
    var request = db.transaction(['person'], 'readwrite')
        .objectStore('person')
        .add({ id: 1, name: '张三', age: 24, email: 'zhangsan@example.com' });

    request.onsuccess = function (event) {
        console.log('数据写入成功');
    };

    request.onerror = function (event) {
        console.log('数据写入失败', event);
    }
}
add()
  • 读取数据
  • 遍历数据
  • 更新数据
  • 删除数据
  • 使用索引
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 211,639评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,277评论 3 385
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 157,221评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,474评论 1 283
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,570评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,816评论 1 290
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,957评论 3 408
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,718评论 0 266
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,176评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,511评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,646评论 1 340
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,322评论 4 330
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,934评论 3 313
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,755评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,987评论 1 266
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,358评论 2 360
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,514评论 2 348

推荐阅读更多精彩内容