美文网首页
前端性能优化之浏览器存储

前端性能优化之浏览器存储

作者: zxhnext | 来源:发表于2019-05-15 12:10 被阅读0次

    一、cookie

    因为HTTP请求无状态,所以需要cookie去维持客户端状态
    过期时间 expire
    cookie的生成方式

    1. http response header中的set-cookie
    2. js中可以通过document.cookie可以读写cookie
      仅仅作为浏览器存储(大小4KB左右,能力被localstorage替代)
      cookie中在相关域名下面 —— cdn的流量损耗,所以我们需要将cdn与主域名独立起来

    种cookie

    document.cookie = 'username=hello'
    

    二、LocalStorage

    HTML5设计出来专门用于浏览器存储的
    大小为5M左右
    仅在客户端使用,不和服务端进行通信
    接口封装较好
    浏览器本地缓存方案
    存储js文件

    var testJsContent = localStorage.getItem('test')
        if(testJsContent) {
          eval(testJsContent)
        } else {
          axios.get('./test.js').then(res => {
            var responseText = res
            eval(responseText)
            localStorage.setItem('test', responseText)
          })
        }
    

    三、SessionStorage

    会话级别的浏览器存储
    大小为5M左右
    仅在客户端使用,不和服务端进行通信
    接口封装较好
    对于表单信息的维护

    四、IndexedDB

    IndexedDB 是一种低级API,用于客户端存储大量结构化数据。该API使用索引来实现对该数据的高性能搜索。虽然 Web Storage 对于存储较少量的数据很有用,但对于存储更大量的结构化数据来说,这种方法不太有用。IndexedDB提供了一个解决方案。
    为应用创建离线版本

    使用方法参考:http://www.ruanyifeng.com/blog/2018/07/indexeddb.html

    五、PWA

    PWA (Progressive Web Apps) 是一种 Web App 新模型,并不是具体指某一种前沿的技术或者某一个单一的知识点,我们从英文缩写来看就能看出来,这是一个渐进式的 Web App,是通过一系列新的 Web 特性,配合优秀的 UI 交互设计,逐步的增强 Web App 的用户体验。
    PWA优点:

    1. 可靠:在没有网络的环境中也能提供基本的页面访问,而不会出现“未连接到互联网”的页面。
    2. 快速:针对网页渲染及网络数据访问有较好优化。
    3. 融入(Engaging):应用可以被增加到手机桌面,并且和普通应用一样有全屏、推送等特性。

    Manifest实现添加至主屏幕
    index.html

    <head>
      <title>Minimal PWA</title>
      <meta name="viewport" content="width=device-width, user-scalable=no" />
      <link rel="manifest" href="manifest.json" />
      <link rel="stylesheet" type="text/css" href="main.css">
      <link rel="icon" href="/e.png" type="image/png" />
    </head>
    

    manifest.json

    {
      "name": "Minimal PWA", // 必填 显示的插件名称
      "short_name": "PWA Demo", // 可选  在APP launcher和新的tab页显示,如果没有设置,则使用name
      "description": "The app that helps you understand PWA", //用于描述应用
      "display": "standalone", // 定义开发人员对Web应用程序的首选显示模式。standalone模式会有单独的
      "start_url": "/", // 应用启动时的url
      "theme_color": "#313131", // 桌面图标的背景色
      "background_color": "#313131", // 为web应用程序预定义的背景颜色。在启动web应用程序和加载应用程序的内容之间创建了一个平滑的过渡。
      "icons": [ // 桌面图标,是一个数组
        {
        "src": "icon/lowres.webp",
        "sizes": "48x48",  // 以空格分隔的图片尺寸
        "type": "image/webp"  // 帮助userAgent快速排除不支持的类型
      },
      {
        "src": "icon/lowres",
        "sizes": "48x48"
      },
      {
        "src": "icon/hd_hi.ico",
        "sizes": "72x72 96x96 128x128 256x256"
      },
      {
        "src": "icon/hd_hi.svg",
        "sizes": "72x72"
      }
      ]
    }
    

    Manifest参考文档:https://developer.mozilla.org/zh-CN/docs/Web/Manifest

    可以打开网站https://developers.google.cn/web/showcase/2015/chrome-dev-summit查看添加至主屏幕的动图。

    如果用的是安卓手机,可以下载chrome浏览器自己操作看看

    六、Service Worker

    Service Worker 是一个脚本,浏览器独立于当前网页,将其在后台运行,为实现一些不依赖页面或者用户交互的特性打开了一扇大门。在未来这些特性将包括推送消息,背景后台同步, geofencing(地理围栏定位),但它将推出的第一个首要特性,就是拦截和处理网络请求的能力,包括以编程方式来管理被缓存的响应。

    参见文档:https://segmentfault.com/a/1190000012353473?utm_source=tag-newest#articleHeader4

    查看浏览器Service Worker使用情况
    chrome://serviceworker-internals/ 已安装Service Worker的详细情况
    chrome://inspect/#service-workers

    Service Workers 就像介于服务器和网页之间的拦截器,能够拦截进出的HTTP 请求,从而完全控制你的网站。
    最主要的特点:

    1. 在页面中注册并安装成功后,运行于浏览器后台,不受页面刷新的影响,可以监听和截拦作用域范围内所有页面的 HTTP 请求。
    2. 网站必须使用 HTTPS。除了使用本地开发环境调试时(如域名使用 localhost)
      运行于浏览器后台,可以控制打开的作用域范围下所有的页面请求
    3. 单独的作用域范围,单独的运行环境和执行线程
    4. 不能操作页面 DOM。但可以通过事件机制来处理
    5. 事件驱动型服务线程

    为什么要求网站必须是HTTPS的,大概是因为service worker权限太大能拦截所有页面的请求吧,如果http的网站安装service worker很容易被攻击

    浏览器支持情况详见: https://caniuse.com/#feat=serviceworkers

    Service Worker生命周期如下图:


    image.png

    当用户首次导航至 URL 时,服务器会返回响应的网页。

    1. 当你调用 register() 函数时, Service Worker 开始下载。
    2. 在注册过程中,浏览器会下载、解析并执行 Service Worker ()。如果在此步骤中出现任何错误,register() 返回的 promise 都会执行 reject 操作,并且 Service Worker 会被废弃。
    3. 一旦 Service Worker 成功执行了,install 事件就会激活
    4. 安装完成,Service Worker 便会激活,并控制在其范围内的一切。如果生命周期中的所有事件都成功了,Service Worker 便已准备就绪,随时可以使用。

    离线缓存
    index.html

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="UTF-8">
        <title>Hello Caching World!</title>
      </head>
      <body>
        <!-- Image -->
        <img src="/images/hello.png" />                 
        <!-- JavaScript -->
        <script async src="/js/script.js"></script>     
        <script>
          // 注册 service worker
          if ('serviceWorker' in navigator) {           
            navigator.serviceWorker.register('/service-worker.js', {scope: '/'}).then(function (registration) {
              // 注册成功
              console.log('ServiceWorker registration successful with scope: ', registration.scope);
            }).catch(function (err) {                   
              // 注册失败 :(
              console.log('ServiceWorker registration failed: ', err);
            });
          }
        </script>
      </body>
    </html>
    

    注:Service Worker 的注册路径决定了其 scope 默认作用页面的范围。
    如果 service-worker.js 是在 /sw/ 页面路径下,这使得该 Service Worker 默认只会收到 页面/sw/ 路径下的 fetch 事件。
    如果存放在网站的根路径下,则将会收到该网站的所有 fetch 事件。
    如果希望改变它的作用域,可在第二个参数设置 scope 范围。示例中将其改为了根目录,即对整个站点生效。

    service-worker.js

    var cacheName = 'helloWorld';     // 缓存的名称  
    // install 事件,它发生在浏览器安装并注册 Service Worker 时        
    self.addEventListener('install', event => { 
    /* event.waitUtil 用于在安装成功之前执行一些预装逻辑
     但是建议只做一些轻量级和非常重要资源的缓存,减少安装失败的概率
     安装成功后 ServiceWorker 状态会从 installing 变为 installed */
      event.waitUntil(
        caches.open(cacheName)                  
        .then(cache => cache.addAll([    // 如果所有的文件都成功缓存了,便会安装完成。如果任何文件下载失败了,那么安装过程也会随之失败。        
          '/js/script.js',
          '/images/hello.png'
        ]))
      );
    });
      
    /**
    为 fetch 事件添加一个事件监听器。接下来,使用 caches.match() 函数来检查传入的请求 URL 是否匹配当前缓存中存在的任何内容。如果存在的话,返回缓存的资源。
    如果资源并不存在于缓存当中,通过网络来获取资源,并将获取到的资源添加到缓存中。
    */
    self.addEventListener('fetch', function (event) {
      event.respondWith(
        caches.match(event.request)                  
        .then(function (response) {
          if (response) {                            
            return response;                         
          }
          var requestToCache = event.request.clone();  //          
          return fetch(requestToCache).then(                   
            function (response) {
              if (!response || response.status !== 200) {      
                return response;
              }
              var responseToCache = response.clone();          
              caches.open(cacheName)                           
                .then(function (cache) {
                  cache.put(requestToCache, responseToCache);  
                });
              return response;             
        })
      );
    });
    

    注:为什么用request.clone()和response.clone()
    需要这么做是因为request和response是一个流,它只能消耗一次。因为我们已经通过缓存消耗了一次,然后发起 HTTP 请求还要再消耗一次,所以我们需要在此时克隆请求
    Clone the request—a request is a stream and can only be consumed once.

    serice worker实现消息推送

    1. 提示用户并获得他们的订阅详细信息
    2. 将这些详细信息保存在服务器上
    3. 在需要时发送任何消息

    步骤一和步骤二
    index.html

    <!DOCTYPE html>
    <html>
      <head>
        <meta charset="UTF-8">
        <title>Progressive Times</title>
        <link rel="manifest" href="/manifest.json">                                      
      </head>
      <body>
        <script>
          var endpoint;
          var key;
          var authSecret;
          var vapidPublicKey = 'BAyb_WgaR0L0pODaR7wWkxJi__tWbM1MPBymyRDFEGjtDCWeRYS9EF7yGoCHLdHJi6hikYdg4MuYaK0XoD0qnoY';
          // 方法很复杂,但是可以不用具体看,知识用来转化vapidPublicKey用
          function urlBase64ToUint8Array(base64String) {                                  
            const padding = '='.repeat((4 - base64String.length % 4) % 4);
            const base64 = (base64String + padding)
              .replace(/\-/g, '+')
              .replace(/_/g, '/');
            const rawData = window.atob(base64);
            const outputArray = new Uint8Array(rawData.length);
            for (let i = 0; i < rawData.length; ++i) {
              outputArray[i] = rawData.charCodeAt(i);
            }
            return outputArray;
          }
          if ('serviceWorker' in navigator) {
            navigator.serviceWorker.register('sw.js').then(function (registration) {
              return registration.pushManager.getSubscription()                            
                .then(function (subscription) {
                  if (subscription) {                                                      
                    return;
                  }
                  return registration.pushManager.subscribe({                              
                      userVisibleOnly: true,
                      applicationServerKey: urlBase64ToUint8Array(vapidPublicKey)
                    })
                    .then(function (subscription) {
                      var rawKey = subscription.getKey ? subscription.getKey('p256dh') : '';
                      key = rawKey ? btoa(String.fromCharCode.apply(null, new Uint8Array(rawKey))) : '';
                      var rawAuthSecret = subscription.getKey ? subscription.getKey('auth') : '';
                      authSecret = rawAuthSecret ?
                        btoa(String.fromCharCode.apply(null, new Uint8Array(rawAuthSecret))) : '';
                      endpoint = subscription.endpoint;
                      return fetch('./register', {                                         
                        method: 'post',
                        headers: new Headers({
                          'content-type': 'application/json'
                        }),
                        body: JSON.stringify({
                          endpoint: subscription.endpoint,
                          key: key,
                          authSecret: authSecret,
                        }),
                      });
                    });
                });
            }).catch(function (err) {
              // 注册失败 :(
              console.log('ServiceWorker registration failed: ', err);
            });
          }
        </script>
      </body>
    </html>
    

    步骤三 服务器发送消息给service worker
    app.js

    const webpush = require('web-push');                 
    const express = require('express');
    var bodyParser = require('body-parser');
    const app = express();
    webpush.setVapidDetails(                             
      'mailto:contact@deanhume.com',
      'BAyb_WgaR0L0pODaR7wWkxJi__tWbM1MPBymyRDFEGjtDCWeRYS9EF7yGoCHLdHJi6hikYdg4MuYaK0XoD0qnoY',
      'p6YVD7t8HkABoez1CvVJ5bl7BnEdKUu5bSyVjyxMBh0'
    );
    app.post('/register', function (req, res) {           
      var endpoint = req.body.endpoint;
      saveRegistrationDetails(endpoint, key, authSecret); 
      const pushSubscription = {                          
        endpoint: req.body.endpoint,
        keys: {
          auth: req.body.authSecret,
          p256dh: req.body.key
        }
      };
      var body = 'Thank you for registering';
      var iconUrl = 'https://example.com/images/homescreen.png';
      // 发送 Web 推送消息
      webpush.sendNotification(pushSubscription,          
          JSON.stringify({
            msg: body,
            url: 'http://localhost:3111/',
            icon: iconUrl
          }))
        .then(result => res.sendStatus(201))
        .catch(err => {
          console.log(err);
        });
    });
    app.listen(3111, function () {
      console.log('Web push app listening on port 3111!')
    });
    

    service worker监听push事件,将通知详情推送给用户
    service-worker.js

    self.addEventListener('push', function (event) {
     // 检查服务端是否发来了任何有效载荷数据
      var payload = event.data ? JSON.parse(event.data.text()) : 'no payload';
      var title = 'Progressive Times';
      event.waitUntil(
        // 使用提供的信息来显示 Web 推送通知
        self.registration.showNotification(title, {                           
          body: payload.msg,
          url: payload.url,
          icon: payload.icon
        })
      );
    });
    

    七、workbox3

    相关文章

      网友评论

          本文标题:前端性能优化之浏览器存储

          本文链接:https://www.haomeiwen.com/subject/gpdpwqtx.html