颠覆认知的前端存储技术

741 阅读11分钟

背景

随着浏览器功能不断的增强,前端技术也是发生了飞速的发展。越来越多的网站开始考虑,将大量数据存储在客户端。记录用户的所有状态信息,增加用户体验。比如记录用户的登录状态,以及涉及用户的个人信息等。

前端实现本地存储的方式

  1. Cookie

    Cookie的本职工作并非本地存储,而是 “维持状态”

    在早期Web开发时候,HTTP是无状态协议,无法维护历史状态,换句话说就是,不断的HTTP请求,并且做一些事,下一次访问时,web站点也不认识你。这带来了很多麻烦也使某些服务很难运行。这样Cookie诞生了。

    1. 好处

      1. cookie头部行在响应信息中
      2. cookie头部行在请求信息中
      3. cookie文件保存在用户主机中并不被用户浏览器管理。
      4. cookie也保存在web站点后端数据库
    2. 坏处

      1. Cookie不够大(一般为4KB)
      2. Cookie是紧跟域名的。
      3. 同一个域名下的所有请求,都会携带Cookie。
      4. Cookie的存储的有效时间是有限制的。
    因此为了弥补Cookie的局限性,WebStorage出现了。
  2. Web Storage

    Web Storage是HTML5专门为浏览器存储而提供的数据存储机制,容量有5M的大小,具体的由浏览器厂家决定。另外它只存在于客户端,不会与服务端进行通讯。Web Storage又分为Localstorage和Sessionstorage。

    1. Localstorage

      持久化存储方式,存储在这里面的数据是不会过期的,只能手动进行删除。

      LocalSorage核心Api

    2. Sessionstorage

      临时性存储方式,当浏览器关闭的时候存储信息进行销毁。

      SessionStorage核心Api

      Web Storage是有同源策略限制,但是Sessionstorage特别之处在于即使在一个域名下,不在同一个窗口打开那么数据也是无法共享的。

    缺点:Web Storage是一个从定义到使用都非常简单的东西。它使用键值对的形式进程存储,只能存储string,object需要对字符串进行解析才能使用。并且只能存储少量简单的数据,当遇到大规模结构数据时,就爱莫能助。因此为了弥补Web Storage的缺陷,出现了indexDB浏览器本地数据库

  3. indexDB

    IndexDB是一种使用浏览器存储大量数据的方法,它创造的数据可查询,并且可以离线使用,它 是一个运行在浏览器的非关系型数据库,理论时来说IndexDB存储数据量没有上限一般来说不少于250M(真香)。

    特点:

    1. 键值对存储

      IndexDB内部采用对象仓库(object store)存放数据。所有类型的数据都可以存入。包括js对象。对象仓库中,数据以“键值对”的形式保存,每一个数据记录都有对应的主键,主键不能重复只能是唯一的。

    2. 异步

      IndexDB的操作不会锁死浏览器,用户依然可以进行其他的操作,这与Web Storage形成对比,后者是同步的。异步设计是为了防止大量数据写入,拖慢网页的表现。

    3. 支持事务

      IndexDB支持事物(transaction),这就意味一系列操作步骤中,只要有一步失败,整个事务就都取消,数据库回滚到事务发生的状态,不存在只改写一部分数据的情况。比如,在修改多条数据时,前几条已经成功了,在中间的某一条是失败了,那么这时,如果是基于事物的数据库操作,那么这时数据库就应该重置前面数据的修改,放弃后面的数据修改,直接返回错误,一条数据也不修改。

    4. 同源限制

      IndexDB受到同源限制,每一个数据库对应创建它的域名。网页只能访问自身域名下的数据库,而不能访问跨域的数据库。

    5. 支持二进制存储

      IndexDB不仅能存储字符串,还可以存储二进制数据对象和Blob对象。

    6. 存储空间大

      IndexDB存储空间比Web Storage大得多,一般来说不少于250M,甚至没有上限。

    操作:

    1. 打开数据库
      let db = null
      const request = window.indexedDB.open(name)
      ​
      request.onsuccess = function (event) {
        db = event.target.result
        cb(db)
        console.log('数据库打开成功', event)
      }
      ​
      request.onerror = function (event) {
        console.log('数据库打开失败', event)
      }
      
    2. 新建数据库和更新数据库版本号

      onupgradeneeded只会在版本更新的时候触发,这时因为IndexDB API中不允许数据库中的数据仓库在同一版本种发生变化。

      request.onupgradeneeded = function (event) {
        db = event.target.resultlet objectStore = null
        // 判断库是否存在。不存在,在创建
        if (!db.objectStoreNames.contains('persion')) {
          objectStore = db.createObjectStore('persion', { keyPath: 'id' })
      ​
          objectStore.createIndex('user', 'name', { unique: false })
        }
      }
      
    3. 添加数据
      // 新建事务
      const store = db.transaction(['persion'], 'readwrite').objectStore('persion')
      // add方法新增数据
      store.add({
        id: 1,
        name: '熊猫小弟',
        age: 24
      })
      

      readonly 只读 readwrite读写

    4. 读取数据
      // 新建事务
      const store = db.transaction(['persion'], 'readwrite').objectStore('persion')
      // get获取数据
      const request = store.get(1)
      ​
      request.onerror = function (event) {
        console.log('读取失败', event.target.result)
      }
      ​
      request.onsuccess = function (event) {
        if (request.result) {
          console.log(request.result)
        } else {
          console.log('未获得数据记录')
        }
      }
      
    5. 遍历数据
      // 新建事务
      const store = db.transaction(['persion'], 'readwrite').objectStore('persion')
      // openCursor是一个异步方式,所以监听onsuccess
      store.openCursor().onsuccess = function (evet) {
        const cursor = event.target.resultif (cursor) {
          console.log(cursor.value)
          cursor.continue()
        } else {
          console.log('没有更多数据了')
        }
      }
      
    6. 更新数据
      // 新建事务
      const store = db.transaction(['persion'], 'readwrite').objectStore('persion')
      // put方法更新数据
      const request = store.put({
        id: 1,
        name: '李四',
        age: 24
      })
      request.onerror = function (event) {
        console.log('更新数据失败');
      }
      request.onsuccess = function (event) {
        console.log('更新数据成功');
      }
      
    7. 删除数据
      // 新建事务
      const store = db.transaction(['persion'], 'readwrite').objectStore('persion')
      // delete方法用于删除记录
      const request = store.delete(1)
      request.onsuccess = function (event) {
        console.log('删除成功');
      }
      
    8. 创建和使用索引
      // 新建事务
      const store = db.transaction(['persion']).objectStore('persion')
      // index方法获取索引对象,get方法获取数据
      const index = store.index('user')
      const request = index.get('熊猫小弟')
      

    使用场景:随着前端功能的复杂度提升,用户需要多元化,前端IndexDB应用越来越多。桌面应用,PWA等,用户同时会获取/操作更多信息,桌面留存大量数据,那么IndexDB就上线了。

    文章借鉴阮一峰老师的浏览器数据库 IndexedDB 入门教程

  4. WebSQL

    WebSQL是浏览器端的关系行数据库,引入了一组使用SQL操作客户端数据库的API。HTML5支持没有对IndexDB那么好

    • 三个核心方法

      • openDatabase:使用数据库或新建数据库来创建数据库对象
      • transaction:允许我们根据情况控制事务提交和回滚。
      • executeSql:这个方法用于执行真实的SQL查询
      const web_db = openDatabase('web_db', '1.0', 'Test WebSQL DB', 1024 * 1024)
      ​
      // 在web_db数据库中创建一张日志信息表
      web_db.transaction(tx => {
        tx.executeSql('CREATE TABLE IF NOT EXISTS LOGS (id unique, log, time)')
        tx.executeSql('INSERT INTO LOGS (id, log, time) VALUES (1, "hello world", 1628087366562)')
      })
      ​
      // 向STU表格中添加一条数据
      web_db.transaction(tx => {
        tx.executeSql('INSERT INTO LOGS (id, log) VALUES (?, ?)',
          ['2', 'Jack'],
          () => console.log('添加数据成功'),
          (tx, err) => console.error('添加数据失败', err.message)
        )
      })
      
  5. Appliction Cache

    WebSQL Applicaton Cache是HTML5中定义的一种离线存储技术标准。这种技术可以让开发者明确指定页面中那些静态资源可以在第一次访问网页的同时缓存到本地,并且咋下次访问该网页时向服务器询问本地缓存的资源是否需要更新。

    • 优势

      • 离线浏览 - 用户可在应用离线时使用它们。
      • 速度 - 已缓存资源加载得更快。
      • 减少服务器负载 - 浏览器只从服务器下载更新过或更改过的资源。
    • 原理

      • HTML5的离线存储是基于一个manifest文件(缓存清单文件,后缀为.appcache)的缓存机制(不是存储技术),通过这个文件上的清单解析离线存储资源,这些资源就会像Cookie一样被存储下来。之后当网络在处于离线状态时,浏览器会通过被离线存储的数据进行页面展示。
    • 使用

      • 首先在文档的html标签中设置manifest属性,引用manifest文件,然后配置manifest文件,在manifest文件中编写离线存储的资源。最后操作window.applicationCache进行需求实现。此外,必须要在服务端正确的配置MIME-type。

      • demo.html

        <!DOCTYPE html>
        <html lang="en" manifest="demo.appcache">
        <head>
          <meta charset="UTF-8">
          <title>demo</title>
        </head>
        <body>
          <img src="img.jpg" height="500" width="900" alt="">
          其它内容...
        </body>
        </html>
        
      • demo.appcache

        CACHE - 在此标题下列出的文件将在首次下载后进行缓存。

        NETWORK - 在此标题下列出的文件需要与服务器的连接,且不会被缓存。可以使用*,表示除CACHE 外的所有其他资源/文件都需要因特网连接。

        FALLBACK - 在此标题下列出的文件规定当页面无法访问时的替代页面。

        注意:CACHE MANIFEST写在第一行

        CACHE MANIFEST
        #version 1.0CACHE:
        img.jpgNETWORK:
        *FALLBACK: /demo/ /404.html
        

        demo.appcache中的配置意为:

        demo.html中的img在首次下载后进行缓存;其他文件内容都需要因特网连接; 如果无法建立因特网连接,则用 "404.html" 替代 /demo/ 目录中的所有文件。
        
      • 操作window.applicationCache进行需求实现

        window.applicationCache 对象是对浏览器的应用缓存的编程访问方式。其 status 属性可用于查看缓存的当前状态。

        status 属性值:

        0(UNCACHED) :
        无缓存, 即没有与页面相关的应用缓存。
        
        1(IDLE) :
        闲置,即应用缓存未得到更新。
        
        2 (CHECKING) :
        检查中,即正在下载描述文件并检查更新。
        
        3 (DOWNLOADING) :
        下载中,即应用缓存正在下载描述文件中指定的资源。
        
        4 (UPDATEREADY) :
        更新完成,所有资源都已下载完毕。
        
        var oAppCache = window.applicationCache;
        var sCacheStatus = "Not supported";
        switch (oAppCache.status) {
          case 0: // UNCACHED == 0
            sCacheStatus ='(UNCACHED) : 无缓存, 即没有与页面相关的应用缓存';     
            break;
          case 1: // IDLE == 1
            sCacheStatus = '(IDLE) : 闲置,即应用缓存未得到更新';     
            break;
          case 2: // CHECKING == 2
            sCacheStatus = '(CHECKING) : 检查中,即正在下载描述文件并检查更新';     
            break;     
          case 3: // DOWNLOADING == 3         
            sCacheStatus ='(DOWNLOADING) : 下载中,即应用缓存正在下载描述文件';     
            break;     
          case 4: // UPDATEREADY == 4         
            sCacheStatus ='(UPDATEREADY) : 更新完成,所有资源都已下载完毕';    
            break;     
          case 5: // OBSOLETE == 5         
            sCacheStatus ='(IDLE) : 废弃,即应用缓存的描述文件已经不存在了,因此页面无法再访问应用缓存');     
            break;     
          default:         
            console.log( 'UKNOWN CACHE STATUS');    
            break; 
        }; 
        

        浏览器会对下载进度、应用缓存更新和错误状态等情况触发相应事件。

        APPCACHE 事件 :

        checking :
        每当应用程序载入的时候,都会检查该清单文件,也总会首先触发“checking”事件。
        
        noupdate :
        如果没有改动,同时应用程序也已经缓存了“noupdate”事件被触发,整个过程结束 
        
        downloading :
        如果还未缓存应用程序,或者清单文件有改动,那么浏览器会下载并缓存清单中的所有资源 ,触发"downloading"事件,同时意味着下载过程开始。
        
        progress:
        在下载过程中会间断性触发“progress”事件,通常是在每个文件下载完毕的时候 
        
        cached :
        下载完成并且首次将应用程序下载到缓存中时,浏览器会触发“cached“事件 
        
        updateready :
        当下载完成并将缓存中的应用程序更新后,浏览器会触发”updaterady”事件。
        
        error :
        如果浏览器处于离线状态,检查清单列表失败,则会触发“error“事件,当一个未缓存的应用程序引用一个不存在的清单文件,也会触发此事件
        
        obsolete :
        如果一个缓存的应用程序引用一个不存在的清单文件,会触发“obsolete“,同时将应用从缓存中移除之后不会从缓存而是通过网络加载资源
        
        var oAppCache = window.applicationCache;
        
        function handleCacheEvent(e) {         对应操作...    }
        
        function handleCacheError(e) {        
          alert('Error: Cache failed to update!');     
        }; 
        
        //在浏览器为应用缓存查找更新时触发    
        oAppCache.addEventListener('checking', handleCacheEvent, false); 
        
        //在检查描述文件发现文件无变化时触发    
        oAppCache.addEventListener('noupdate', handleCacheEvent, false);
        
        // 在开始下载应用缓存资源时触发
        oAppCache.addEventListener('downloading', handleCacheEvent, false); 
        
        //在文件下载应用缓存的过程中持续不断地下载地触发    
        oAppCache.addEventListener('progress', handleCacheEvent, false);     
        
        //在应用缓存完整可用时触发     
        oAppCache.addEventListener('cached', handleCacheEvent, false);  
        
        //在页面新的应用缓存下载完毕触发    
        oAppCache.addEventListener('updateready', function(){          
          oAppCache.swapCache(); // 更新本地缓存    
          location.reload(); //重新加载页面页面        
        }, false);   
        
        //在检查更新或下载资源期间发送错误时触发    
        oAppCache.addEventListener('error', handleCacheError, false); 
        
        //缓存清单不存在时触发    
        oAppCache.addEventListener('obsolete', handleCacheEvent, false); 
        
      • 在服务器端正确的配置MIME-type

        若遇到如此报错“Application Cache Error event: Manifest fetch failed (404) ”,其原因是manifest文件需要正确的配置MIME-type(描述该消息的媒体类型),即“text/cache-manifest”,必须在服务器端进行配置。不同服务器配置方式不一样,举在tomcat服务器配置的例子。

      • 更新缓存

        一旦应用被缓存,它就会保持缓存直到发生下列情况:

        1. 用户清空浏览器缓存。

        2. manifest 文件被修改。

        3. 由程序来更新应用缓存。

          oAppCache.addEventListener('updateready', function(){  
            oAppCache.swapCache();  // 更新本地缓存
            location.reload();  //重新加载页面页面
          }, false); 
          

结语

前端技术快速不断的进行迭代,浏览器也不断升级,通过新的技术体系结合浏览器新的性能能完成很多之前不能完成的事。前端存储技术是前端星球上一个必不可少的环节,大家一起学起来吧!!!

文章最后如果你喜欢本文,请点赞,关注加评论。作者会持续不断进行技术文章分享,希望大家能多多支持。