H5存储

1.本地存储-Web Storage
2.本地存储-IndexedDB
3.本地存储的扩展介绍
4.离线存储-app cache
5.总结

分析存储需求:

  1. 移动端网络环境因素
  • 数据响应慢,体验下降,2G/3G网速非常慢
  1. 流量因素
  • 客户端存储 = 节省流量 = 节省金钱
  1. 追求原生体验
  • 离线使用应用
  • 存储应用相关资源、数据

Cookie 可行否? 否

因为Cookie 的局限性:

  • 存储大小限制,仅 4kb 左右
  • 单个域名下的数量限制,50个左右
  • 污染请求头,浪费流量

说明:

  • cookie的作用是与服务器进行交互,作为HTTP规范的一部分而存在 ,
    而Web Storage仅仅是为了在本地“存储”数据而生。
  • 每次你请求一个新的页面的时候Cookie都会被发送过去,这样无形中浪费了带宽。

本地存储:localStorage 和 sessionStorage

  1. 相同的使用方法
  • 使用 setItem(key,value) 方法设置存储内容
  • 使用 getItem(key) 方法获取存储内容
  • 使用 removeItem(key) 方法删除存储内容
  • 使用 clear() 方法清除所有内容
  • 使用 length 属性获取存储内容个数
  • 使用 key(index) 方法获取存储字段
  1. 不同的存储时效
  • localStorage 存储会持久化
  • sessionStorage 存储会在网页会话结束(标签页的关闭)后失效,只有在同一个会话中的页面才能访问。
  1. 不同的存储容量
  • localStorage 容量一般在 2 – 5Mb 左右
  • sessionStorage 存储容量不一,部分浏览器不设限
  1. Web Storage Support Test
    http://dev-test.nemikor.com/web-storage/support-test/

sessionStorage和localStorage的存储空间虽然较大,但是存储容量仍有限制,叫做配额。

  1. 使用 Storage 时的注意点:
  1. 存储容量超出限制
  • 抛出 QuotaExceededError 异常 (存储值时应使用 try catch 捕获异常)
<!doctype html>
<html lang="en">

<head>
    <meta charset="utf-8">
    <title>quota exceed test</title>
</head>

<body>
    <button class="start-btn">start</button>
    <script>
    var btn = document.querySelector('.start-btn');


    var data = '01234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890123456789012345678901234567890',
        id = 0,
        len = data.length,
        total = 0,
        key;

    function save() {
        id = (id + 1) % 10;
        key = Date.now() + '' + id;
        // try...catch 主要用于捕获代码运行时的异常,并进行异常处理。
        // try 部分包含运行时,可能出现异常的代码.
        // 而 catch 部分包含错误发生时运行的代码。
        try {
            // try 中写可能产生异常的语句
            localStorage.setItem(key, data);
            save();
        } catch (e) {
            // catch 中写负责异常处理的语句
            console.log(e.name); //QuotaExceededError

        }

    }
    btn.addEventListener('click', save);

    </script>
</body>

</html>
  1. 存储类型的限制
  • 仅能存储字符串
  • 注意类型转换 JSON.stringifyJSON.parse
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>quota exceed test</title>
</head>
<body>
    <button class="str" onclick="save('abc')">字符串 abc</button><br />   
    <button class="bool" onclick="save(false)">布尔值 false</button><br />
    <button class="undef" onclick="save(window.undefined)">未定义 window.undefined</button><br />
    <button class="null" onclick="save(null)">空值 null</button><br />
    <button class="num" onclick="save(0)">数字 0</button><br />
    <button class="arr1" onclick="save([])">数组 []</button><br />
    <button class="arr2" onclick="save([1,2,3])">数组 [1,2,3]</button><br />
    <button class="obj1" onclick="save({})">对象 {}</button><br />
    <button class="obj2" onclick="save({x: 1})">对象 {x: 1}</button><br />
    <button class="obj3" onclick="save({toString: function () {return 'toStringed'}})">对象 {toString: function () {return 'toStringed'}}</button><br />
    <button class="obj4" onclick="save({toString: function () {return 100}})">对象 {toString: function () {return 100}}</button><br />
    
    
<script>
function save (data) {
    localStorage.setItem('key', data)
    show(localStorage.getItem('key'))
}

function show (value) {
    console.log(value, '|', typeof value)
}

</script>
</body>
</html>  

打印结果:

> abc | string
> false | string
> undefined | string
> null | string
> 0 | string
>  | string
> 1,2,3 | string
> [object Object] | string
> [object Object] | string
> toStringed | string
> 100 | string

类型转换:在控制台输入

> localStorage.setItem('key',JSON.stringify({data:1}))
< undefined
> localStorage.getItem('key')
< "{"data":1}"
> JSON.parse(localStorage.getItem('key'))
< {data: 1}
  1. sessionStorage 失效机制
  • 刷新页面并不会失效 location.reload()
  • 相同 URL 不同标签页不能共享 sessionStorage
  1. Web Storage 的优化
    性能与存储容量大小无关,与读取次数有关
  • 减少读取 item 次数
  • 单个 item 中尽可能多的存储数据
  1. 带有过期机制的 localStorage 的功能需求
  1. 可以设置数据的存储时间
  2. 过期数据清理
  3. 自行维护存储空间
<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <title>cache storage</title>
</head>
<body>  
<script>
'use strict'
;(function () {
    var ls = window.localStorage

    function oops () {
        return console.warn('your browser is not supported localStorage API')
    }

    function getItem (key) {
        var data = ls.getItem(key)  //在没有数据项的情况下,data为null
        data = JSON.parse(data)||{} //将字符串类型的data转为对象类型的data

        if (data.time === 0) {// 上句如是data = JSON.parse(data),在data.time时,就会报错。所以调整成data = JSON.parse(data)||{}
            // 持久化数据直接返回
            return data.value
        } else if (Date.now() > data.time) { // 判断是否超时
            //过期
            ls.removeItem(key) 
            return ''
        } else {
            // 没过期
            return typeof data.value !== 'undefined' ? data.value : ''
        }
    }

    function setItem (key, value, time) {
        if (typeof key === 'undefined') {return}
        var data = {
            time: time ? Date.now() + time : 0,
            value: value
        }
        data = JSON.stringify(data) //stringify方法进行对象的字符串序列化
        try {
            ls.setItem(key, data)
        } catch (e) {
            ls.clear()
            ls.setItem(key, data)
        }
        
    }

    function removeItem (key) {
        ls.removeItem(key)
    }

    function clear () {
        ls.clear()
    }

    window.cacheStorage = {//浏览器支持使用localStorage,不支持输出提示语
        getItem: ls ? getItem : oops,
        setItem: ls ? setItem : oops,
        removeItem: ls ? removeItem : oops,
        clear: ls ? clear : clear
    }
})()
</script>
</body>
</html>

IndexedDB 数据库

1.了解indexedDB数据库

1)indexedDB数据库是一种事务型数据库
2)是NoSQL数据库
3)使用JS对象存储数据

2.如何创建数据库和表

  1. 如何创建数据库和"表"?
  1. indexedDB.open('dbName',dbVersinNumber) 创建数据库,返回 IDBOpenDBRequest 对象
  2. indexedDB.createObjectStore 创建“表”
  3. indexedDB.deleteDatabase('dbName') 删除数据库
    function createDB() {
        // request 是 IDBOpenDBRequest对象。 
        request = db.open(dbName, version)
        //请求有三种状态,如下:
        request.onsuccess = function() { // 打开数据库成功
            db = request.result;
            console.log('open success');

        }

        request.onerror = function(e) { // 打开数据库失败
            console.log(e.currentTarget.errormessage)
        }

        request.onupgradeneeded = function(e) { //请求数据库版本变化时
            var store = null;
            db = e.target.result;
            console.log('upgradeneeded');

            /*
            if (!db.objectStoreNames.contains(osName)) {
                db.createObjectStore(osName, {autoIncrement: true}) // 创建的表的主建是自增型的
            }
            */

            if (!db.objectStoreNames.contains(osName)) {
                store = db.createObjectStore(osName, { keyPath: 'id' }) // 创建的表以字段为主建
                store.createIndex('idIndex', 'id', { unique: true }); //创建索引字段id唯一
                store.createIndex('categoryIndex', 'category', { multiEntry: true }); //创建索引字段为数组
                store.createIndex('hpIndex', 'hp', { unique: false });
            }                
           
        }

        // onsuccess事件在onupgradeneeded事件之后触发
    }
  1. 设置主键的两种方法
  1. 设置自增主键 - {autoIncrement: true}
db.createObjectStore(osName, {autoIncrement: true})
  1. 取数据中字段作为主键 - {keyPath: 字段名}
store = db.createObjectStore(osName, { keyPath: 'id' })

3. 关于表的增删改查

  1. 如何使用事务获取表
    调用 IDBDatabase.transaction 方法会返回一个 IDBTransaction 对象,
    它含有一个 objectStore 方法, 可以让用户通过指定模式操作数据库中的“表”。

indexedDB -> transaction -> objectStore

//osName 表格名称,
var db = window.indexedDB || window.mozIndexedDB || window.webkitIndexedDB || window.msIndexedDB;
var transaction = db.transaction(osName, 'readwrite'), //打开一个事务,读写模式
store = transaction.objectStore(osName);
  1. 事务的模式
  1. 读写模式 - readwrite
  2. 只读模式(默认) - readonly
  3. 版本变更模式 - versionchange
  1. 关于“表”的增删查改的相关方法
  1. 增加数据 – IDBObjectStore.add
  2. 获取数据 – IDBObjectStore.get
  3. 获取所有数据 – IDBObjectStore.getAll
  4. 修改(或新增)数据 – IDBObjectStore.put
  5. 删除数据 – IDBObjectStore.delete
  6. 清除所有数据 – IDBObjectStore.clear
    这些方法都返回一个 IDBRequest 对象。
    function addData() {
        if (!db) { alert("db"); }

        // 调用 `IDBDatabase.transaction` 方法会返回一个 IDBTransaction 对象,
        // 它含有一个 objectStore 方法, 可以让用户通过指定模式操作数据库中的“表”。
        var transaction, store;
        transaction = db.transaction(osName, 'readwrite'); //打开一个事务,读写模式
        store = transaction.objectStore(osName) ///获取osName指定的 object store

        data.map(function(o) {
            store.add(o)
            // request = store.add(o) //增加数据
            //   if (data.length - 1 === i) {
            //     request.onsuccess = function () {
            //       console.log('alreay add all data to db')
            //       showCurrentData()
            //     }
            //   }
        })
    }

    function getData(id) {
        var transaction = db.transaction(osName, 'readwrite'), //打开一个事务,读写模式
            store = transaction.objectStore(osName);
        var request = store.get(id);
        request.onsuccess = function() {
            console.log(request.result);
        }

    }


    function getAllData() {
        var transaction = db.transaction(osName, 'readwrite'), //打开一个事务,读写模式
            store = transaction.objectStore(osName);
        var request = store.getAll();
        request.onsuccess = function() {
            console.log(request.result);
        }

    }


    function updateData(id) {
        var transaction = db.transaction(osName, 'readwrite'), //打开一个事务,读写模式
            store = transaction.objectStore(osName);
        var request = store.get(id);
        request.onsuccess = function() {
            request = store.put({ //更新即可以更新也可以添加,取决于关键字是否有重复
                name: '小花猫',
                id: id,
                hp: 9
            });
        }

    }


    function deleteData(id) {
        var transaction = db.transaction(osName, 'readwrite'), //打开一个事务,读写模式
            store = transaction.objectStore(osName);
        var request = store.delete(id);

        request.onsuccess = function() {
            console.log('delete success');
        }

    }

    function clear() {
        var transaction = db.transaction(osName, 'readwrite'), //打开一个事务,读写模式
            store = transaction.objectStore(osName);
        var request = store.clear();
        request.onsuccess = function() {
            console.log('clear success');
        }

    }
  1. IDBRequest 对象
  1. 使用 IDBRequest.onsuccess 执行查询完成回调
  2. 使用 IDBRequest.result 获取查询结果
  3. 使用 IDBRequest.onerror 执行查询失败回调

4. 关于索引

  1. 如何创建索引 IDBObjectStore.createIndex
  • indexName: 索引名称
  • keyPath: 索引字段,可以为空或者数组(type array)
  • optionParameters: 索引配置参数
store = db.createObjectStore(osName, { keyPath: 'id' }) // 创建的表以字段为主建
store.createIndex('idIndex', 'id', { unique: true }); //创建索引字段id唯一
store.createIndex('categoryIndex', 'category', { multiEntry: true }); //创建索引字段为数组
store.createIndex('hpIndex', 'hp', { unique: false });
  1. optionParameters: 索引配置参数
  1. unique 表示keyPath字段的数据是否唯一
    2)multiEntry 表示是否为 keyPath 字段的每一项建立一条索引数据
  1. 使用索引的好处
  1. 可以使用存储记录中的值进行检索
  2. 索引自动更新
  3. 索引数据自动排序
  1. 索引的相关方法
    1)查询数据 - IDBIndex.get
  1. 查询所有数据 - IDBIndex.getAll
  2. 打开游标 - IDBIndex.openCursor
function useIndexGetData() { // 索引只能查询数据,并不能操作数据
    var transaction = db.transaction(osName, 'readwrite'), //打开一个事务,读写模式
        store = transaction.objectStore(osName),
        index = store.index('categoryIndex');
        request = index.getAll('飞行');

    request.onsuccess = function() {
        console.log(request.result);
    }
}

5. 关于游标

  1. 如何创建游标?
    IDBObjectStore/IDBIndex.openCursor
  • 接收可选参数 range 和 direction,指明游标遍历的范围和方向
  • 返回一个 IDBRequet 对象,异步方法
  • 该 IDBRequet 对象的结果是一个 IDBCursor 对象
  1. IDBKeyRange 对象
    1)upperBound: 指定游标范围的上限
    2)lowerBound: 指定游标范围的下限
    3)bound: 指定游标范围的区间
    4)only: 指定游标的值

key range 取值表:



  1. direction 参数
  • next: 顺序查询
  • nextunique: 顺序唯一查询
  • prev: 逆序查询
  • prevunique: 逆序唯一查询
function useCursorGetData() {
    var transaction = db.transaction(osName, 'readwrite'), //打开一个事务,读写模式
        store = transaction.objectStore(osName),
        // request=store.openCursor();  //创建游标
        // request=store.openCursor(IDBKeyRange.only('002'));  //指定游标KeyRange,only一个
        // request = store.openCursor(null, 'prev');
        request = store.openCursor(IDBKeyRange.lowerBound('002'), 'prev'); //逆序查询,游标范围下限是"002"
    request.onsuccess = function() {
        var cursor = request.result;
        if (cursor) {
            console.log(cursor.value);
            cursor.continue();
        }
    }
}

6. 索引和游标的结合使用

  1. 索引和游标的优势
    索引:可以按值搜索
    游标:可以选择遍历顺序及操作数据
function useIndexAndCursorOperateData1() { // 索引和游标结合,可以查询和操作数据
    var transaction = db.transaction(osName, 'readwrite'), //打开一个事务,读写模式
        store = transaction.objectStore(osName),
        index=store.index('categoryIndex');
        request=index.openCursor();

    request.onsuccess = function() {
        var cursor = request.result;
        if (cursor) {
            if(cursor.value.id==='002'){

               /* // 更新数据
               cursor.update({  
                      name: '小蝙蝠',
                      id: '002',
                      hp: 10,
                      category:['怪物','飞行']
                })
                */ 

                 // 删除数据
                cursor.delete().onsuccess=function () {  
                    console.log('delete success');         
                }
                        
            }
            // 更新数据和删除数据操作都是异步操作,所以输出可能会混乱
            console.log(cursor.value);
            cursor.continue();
        }
    }
}

function useIndexAndCursorOperateData() { // 索引和游标结合,可以查询和操作数据
    var transaction = db.transaction(osName, 'readwrite'), //打开一个事务,读写模式
        store = transaction.objectStore(osName),
        index = store.index('hpIndex');
        // request = index.openCursor(IDBKeyRange.upperBound(5)); // 5(包含5)以下
        request = index.openCursor(IDBKeyRange.bound(5,10,true,true)); // 范围(5到10)

    request.onsuccess = function() {
        var cursor = request.result,value=null;
        if (cursor) {
            
            value = cursor.value;
            value.hp += 20;
            cursor.update(value);
            
            console.log(cursor.value);
            cursor.continue();
        }
    }
}

IndexedDB 与 Web Storage 比较

  1. indexedDB 的优势:
  • 存储类型更加丰富
  • 可实现高级查询
  • 可在 Web Workers 中使用
  • 存储容量更大
  1. Web Storage 的优势
  • API 较少,更容易掌握
  • 兼容性更好
  1. IndexedDB 的兼容性问题
    1)IOS8 & 9 中 webview 不支持 indexedDB
    2)Firefox 单次存储 Blob 数据超 50Mb 会抛出异常, 这个50M可以在IndexDB的一些API中进行修改
    3)Safari 的 indexedDB 不能用于 web workers
    4)Chrome36 不支持存储类型的数据

其他存储方式介绍

1. WebSQL

  • 关系型数据库
  • 随HTML5规范加入, 在浏览器端运行的轻量级数据库。
  1. WebSQL 的 API 有哪些?
  • openDatabase: 打开数据库
  • transaction: 获取事务,进行数据库操作
  • executeSql: 执行 SQL 进行查询
  1. WebSQL 的现状
  • 兼容性问题严重


  • W3C 已经不再积极处理其相关规范(避免使用WebSQL)

2. Filesystem & FileWriter API

  • 供我们在客户端进行文件的存储
  • 但是和WebSQL有相似的命运,有严重的兼容性问题和规范被废弃(避免使用)。


3. UserData

是IE独有的存储方式:

  • 只在Windows系统的IE中存在
  • 容量不大

4. Cookie

  • 存储大小限制,仅 4kb 左右
  • 单个域名下的数量限制,50个左右
  • 污染请求头,浪费流量
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 202,905评论 5 476
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 85,140评论 2 379
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 149,791评论 0 335
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 54,483评论 1 273
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 63,476评论 5 364
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 48,516评论 1 281
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,905评论 3 395
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,560评论 0 256
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,778评论 1 296
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,557评论 2 319
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,635评论 1 329
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,338评论 4 318
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,925评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,898评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,142评论 1 259
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,818评论 2 349
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,347评论 2 342

推荐阅读更多精彩内容