前端缓存 -- 本地缓存

1,465 阅读16分钟

每一个网络请求都包含三个阶段:请求=>处理=>响应.如果在短时间内重复发起同一个请求,那么服务端将会重复经历这三个阶段,我们知道每一次的请求、响应都依赖于网络的速度,每一次的处理都需要依赖服务器的资源,都会造成时间的消耗、服务端的负担, 甚至会影响用户的体验.针对上面的三个阶段,后端可以在“处理”的阶段作文章,包括 保留数据库连接、存储处理结果等方式缩短处理时间,尽快进入“响应”阶段;前端则可以在“请求”和“响应”两个阶段尽情施展,在发起请求时,浏览器可以通过存储结果的方式直接使用资源,从而省去请求,如果不能省去请求,那么可以通过浏览器和服务器相互配合,减少响应内容,缩短传输时间.

前端这种手段就是前端缓存,那么前端缓存都有哪些好处呢:

  • 减少冗余的数据传输,降低距离时延,提升性能,提高访问速度
  • 减少网络I/O消耗,减少宽带消耗,缓解网络瓶颈问题,
  • 缓解服务器压力,降低原始服务器的要求,服务器可以更快的响应

前端方向的缓存包括:本地缓存、浏览器缓存(http缓存)、DNS缓存、CDN缓存等

本地缓存

本地缓存是可以通过各大浏览器商提供的API使用JS在本地设置的缓存,包括:WebStorage(localstorage、sessionstorage)、Cookies、WebSQL、indexDB、应用缓存、PWA等.具体可以 进入Chrome开发者模式=> application

WebStorage(html5)

  • localstorage:用于存储一个域名下需要永久存在本地的数据,这些数据可以一直被访问,直到被删除.
  • sessionstorage:用于本地存储一个会话中的数据,这些数据只有在同一个会话中的页面才能访问,会话结束数据随之销毁.
  • 区别:它们存储数据的生命周期:localstorage只要不删除,就可以一直在本地保存(超过存储上限会报错),sessionstorage只在当前会话存储数据,如果当前会话结束(浏览器窗口关闭),数据就会销毁;它们之间共享的限制:localstorage在所有同源的窗口都是共享的,sessionstorage只在当前窗口,即使是同一页面不同窗口也不能共享(页面刷新不清除数据、同一个页面打开的链接可以访问sessionstorage数据--window.open/location.href)
  • 共同点: 都存储字符串类型数据(如果不是会调用toString方法自动转换成字符串);具有相同的API:setItem(key,val)--添加键值对、getItem(key)--获取键值、removeItem(key)--删除键值对、clear()--删除键值对、key(index)--获取键名,注意先存储的键值大、len--获取键值对数量
  • 当存储的数据发生变化时,将会触发一个storage事件,这个事件不可取消,它将会通知同域下其他窗口的storage事件,但是当前窗口下storage事件不会被触发.
  • 场景:存储不经常变动的数据、存储不重要的数据、窗口间的通信

cookie

cookie用于存储web页面的用户信息.http协议是无状态的,即服务器无法判断用户身份.当客户端向服务器发送请求时,在连接关闭后,服务器不会记录用户的信息,当下一次请求时就又需要携带用户的信息,但是如果浏览器自动帮我们做了这一步操作,就带来大大的便利,因此浏览器提供了存储cookie功能,并且在每次请求时自动帮我们加入request header中.

  • 每个域名下cookie的大小为4KB(存储在浏览器中的纯文本),每个域名下的cookie数量最多为20个(视浏览器厂商不同),自动发送到服务端
  • cookie获取方法:document.cookie,cookie设置key=val;expires=过期时间;Domain=域名;Path=路径;Secure;HttpOnly
  • 过期时间:expires--http/1.0协议,设置一个时间点表示过期时间点;max-age--http/1.1 替换 expires,设置表示有效时间,过期时间是创建时刻 + max-age.max-age有三种值:正数、0、负数,正数表示在max-age秒后cookie失效,浏览器会将max-age写入对应cookie文件中,无论用户是否关闭浏览器,只要没过期,登录网站时仍然有效(持久化);0表示立即删除cookie;负数表示这是一个临时cookie,仅在本浏览器窗口或者子窗口有效,关闭浏览器后该cookie失效(当前session有效).
  • domain:生成cookie的域名,默认为设置该cookie网页的域名,path:生成cookie的路径,默认为设置该cookie网页所在目录.domain + path 限制cookie可以被哪些url访问.注意:子域和子目录会自动携带父域和父目录下的cookie(非跨域).
  • secure:当请求时https或者其他安全协议时,包含secure属性的cookie才会被发送到服务器中.默认为空.如果想再网页中通过js设置secure的cookie,请确保当前是https协议.
  • httpOnly:默认为空,当为空时,可以通过JS访问cookie,否则无法访问.(避免xss攻击,网络安全)
  • cookie设置步骤: 客户端发起请求 => 浏览器查询不到cookie => 服务端响应(响应头Set-Cookie) => 客户端保存cookie => 下一次请求 => 浏览器读取cookie加入到(请求头cookie) => 服务器响应
  • 修改cookie: docuemnt.cookie = 新cookie;删除cookie: expires=过去的时间点/max-age=0
  • cookie中 逗号、分号、空格会被当作特殊符号,当包含时需要使用escape进行处理,读取时使用unescape(encodeURIComponent/decodeURIComponent、encodeURI/decodeURI)

WebStorage和cookie的区别:

  • cookie:数据大小不超过4K,过期之前一直有效,会被浏览器自动读取传递至服务器.cookie受域名和路径限制,没有特定的API
  • webstorage:存储大(5M或更大),localstorage除非删除否则一直保存,sessionstorage当前会话有效,不会被浏览器自动传递到服务器,webstorage只受域名影响,有特定API

WebSQL(没用过)

WebSQL 一个独立的规范,引入一组使用SQL操作客户端数据库的api来存储数据.它不是html5的规范

all interested implementors have used the same SQL backend (Sqlite), but we need multiple independent implementations to proceed along a standardisation path.

API
  • openDatabase(name,version,desc,size,callback):使用现有的数据库或者新建的数据库创建一个数据库对象,即 数据库存在打开数据库,不存在创建一个数据库.name:数据库名称,version:版本号,desc:数据库描述,size:数据库大小,callback:创建完成的回调.
  • transaction:控制事务,并基于执行提交或者回滚操作,
  • executeSql:执行传入的sql语句,进行sql操作.
const db = openDatabase('student','1.0','学生库',2 * 1024 * 1024)
db.transaction(tx => {
  tx.executeSql('CREATE TABLE IF NOT EXISTS SUBJECT (id unique, name, teacher)'); // 创建表
  tx.executeSql('INSERT INTO SUBJECT (id, name, teacher) VALUES (1, "数学", "xxx")'); //插入数据
  tx.executeSql('SELECT * FROM SUBJECT',[参数],(tx,res) => {}); //查询
  tx.executeSql('DELETE FROM SUBJECT WHERE id=?',[id]) //删除数据
  tx.executeSql('UPDATE SUBJECT SET name=\'语文\' WHERE id=1') // 更新数据
})

IndexDB

随着浏览器的功能不断增强,越来越多的网站开始考虑将大量数据存储在客户端,这样可以减少和服务器的交互.但是现有的浏览器存储方案都不太适合:cookie大小不超过4K,每次会自动发送到服务器;webStorage在2.5M到10M间,而且不提供搜索功能,不能建立自定义索引.因此需要一种新的解决方案 IndexDB.

IndexDB就是浏览器提供给本地的数据库,可以被JS创建和操作.就数据库类型而言,IndexDB不属于关系型数据库(不支持SQL查询语句),更接近NOSQL数据库.

特点
  • 键值对存储,IndexDB内部采用对象仓库存储数据,可以存储任何类型的数据,在对象仓库中,数据以 键值对 形式保存,每一个数据都对应一个主键,主键保持唯一,不能重复,否则就会报错.
  • 异步,IndexDB操作不会锁死浏览器,用户依然可以进行其他操作,好处是防止大量数据读写,拖慢网页.而localstorage的操作会影响浏览器的执行(JS单线程)
  • 支持事务,IndexDB支持事务(transaction),这意味着一系列的操作步骤中,只要有一步失败,整个事务取消,数据库回滚到事务发生之前的状态,不存在只更改一部分数据的情况.
  • 同源限制,每一个数据库对应创建它的域名,网页只能访问自身域名下的数据库,不能跨域访问.
  • 储存空间大,不少于250M
  • 支持二进制存储,IndexDB不仅可以储存字符串,也可以储存二进制数据(ArrayBuffer、Blob)
概念
  • 数据库:每个域名下可以创建多个数据库,但是同一时刻,只能有一个版本的数据库,修改数据库的结构(增删表、索引、主键)只能通过升级数据库版本完成
  • 对象仓库:每个数据库包含若干个对象仓库,类似与关系型数据库中的表
  • 数据记录:对象仓库保存的是数据记录,每条记录只有主键和数据体两部分
    • 主键用来建立默认索引,必须是不同的,主键可以是数据记录里面的一个属性或者指定为一个递增的整数编号
    • 数据题可以是任意类型数据
  • 索引:为了加速数据检索,可以在对象仓库为不同属性建立不同索引
  • 事务:数据记录的操作通过事务完成,事务对象提供 error、abort、complete 三个事件,监听操作结果.
API
  • open(name,version): 打开数据库,如果数据库不存在,创建数据库.version省略,已有数据库,打开当前数据库,新建数据库时默认为1.返回一个数据库对象,包含error、success、upgradeneeded事件
    • error: 打开数据库失败
    • success: 成功打开数据库
    • upgradeneeded: 如果指定版本号大于当前版本,就会触发数据库升级,如果是新建数据库或者升级,后续操作在该事件进行
  • createObjectStore(对象仓库,{keyPath:主键,autoIncrement:boolean}), 指定主键/自增主键 二选一
const req = indexDB.open('user',1); // 创建数据库user
req.onupgrdeneeded = (evt) => {
  const db = evt.target.result 获取数据库对象
  const table = db.createObjectStore('person',{keyPath:'id'}); // 创建对象仓库
  table.createIndex('name','name',{unique:true}) // 创建索引
  const writeEvt =  db.transaction(['person'],'readwrite').objectStore('person').add({id:1,name:'李雷',age:24,}) // 添加数据
  const readEvt = db.transaction(['person']).objectStore('person').get(1) // 读取数据
  // 遍历数据
  db.transaction(['person']).objectStore('person').openCursor().onSuccess = (evt) => {
    const cursor = evt.target.result
    if(cursor){
      cursor.continue()
    }
  }
  const updateEvt = db.transaction(['person'],'readwrite').objectStore('person').put({id:1,name:'韩梅梅',age:25}) // 更新数据
  const deleteEvt = db.transaction(['person']).objectStore('person').delete(1) // 删除数据
  const indexEvt = db.transaction(['person'],'readonly').objectStore('person').index('name').get('韩梅梅')
}

cacheStorage

cache对象的存储,它提供一个serviceworker、其他worker或者window范围内可以访问到的所有命名为cache的主目录,并维护一份字符串名称到相应cache对象的映射. CacheStorage对不受信任的源使用 SecurityError reject.

API
  • match: 检查给定的Request是否是CacheStorage对象跟踪的任何Cache对象的键,并返回一个resolve为cache对象的Promise
  • has:如果存在与cacheName匹配的cache对象,返回一个resolve为true的Promise
  • open:如果当前对象存在,返回resolve为cache对象的promise,不存在则创建一个返回
  • delete:找到cache对象删除掉,并返回 resolve为true的Promise,否则返回false
  • keys:查找cacheStorage跟踪的所有cache对象,并返回一个resolve为这些对象的promise

Application Cache

Application Cache 是html5的新特性,浏览器可以在本地存储页面上所需的资源,使得页面离线也可以访问.(离线浏览、提升加载速度、减少与服务器交互)

使用步骤
  • 服务端管理维护 mainfest.appcache文件(建议文件扩展名 .appcache),检查mainfest清单中是否有无法访问的文件,并及时更新.这个文件负责告知浏览器哪些资源可以被缓存.其中包含三部分:CACHE MAINFEST、NETWORK、FALLBACK.文件中的任何变动都会导致浏览器重新对mainfest文件读取缓存.
CACHE MAINFEST (必须存在)
# 此标题下的文件在首次加载后缓存,不能使用通配符
/reset.css (文件位置相根据文件在服务器中的实际目录,确保正确.)
/logo.gif

NETWORK (可选)
# 此标题下的文件需要与服务器交互,不能被缓存
nav.html (千万不要把首页放到下面禁止缓存,否则插件无法使用)

FALLBACK (可选)
# 此标题下的文件如果无法联网或访问,那么使用后一个替补显示
/index/ /404.html 
# fallback下的资源需要与mainfest文件同源
  • html需要做的, <html mainfest="mainfest.appcache">.缓存默认包含mainfest.appcache的页面,这些页面被称为主条目,CACHE MAINFEST下的为详细条目,NETWORK下的被称为网络条目.mainfest文件需要与html文件同源
生命销毁
  • 用户清空浏览器的缓存,此时Application cache的本地缓存销毁
  • mainfest文件修改,Application Cache本地缓存将更新,资源请求本身也会触发更新
  • 程序更新缓存

mainfest无视http相关的缓存头域和https的缓存页面限制,在用户代理更新页面之前,缓存不会过期. 各大浏览器对应用缓存的容量限制不同,几乎都是5M 当一个资源被缓存后,即使直接发起对绝对路径下的访问,也会访问缓存中的资源.站点中其他页面即使没有设置mainfest,但是当它访问的资源如果在缓存中也从缓存中获取. 每次网站更新,服务端都要对mainfest.appcache文件进行检查和更新,避免造成损失,但是如果浏览器中缓存mainfest文件时失败(某个文件不能下载到缓存),浏览器继续使用旧的缓存

PWA Mainfest

web应用程序清单,被称为渐进式Web应用程序的web技术的一部分,在一个JSON文件中提供有关应用程序的信息(名称,作者,图标,描述),mainfest的目的是将web应用程序安装到设备的主屏幕,为用户提供更快的访问和更丰富的体验.

部署

<link rel="mainfest" href="/mainfest.json">

示例
{
  "name":"hackerWeb", // 应用程序的名称,
  "short_name":"hackerWeb", // 应用名称缩写
  "lang":"en-US", // 定义语言
  "start_url":"", // 指定设备启东时的url
  "scope":"/myapp", // 定义应用程序可以访问的范围
  "display":"standalone", // 定义开发人员对web应用程序的首选显示模式:fullscreen-全屏显示,standalone-类似独立应用程序,minimal-ui - 有浏览器栏的独立程序,browser-浏览器显示, fullscreen > standalone > minimal-ui > browser
  "background_color":"#fff", // 程序预定义背景色,不能替代程序的真正背景色
  "theme_color":"#fff", // 定义应用程序默认主题色
  "description":"test for app", // 程序的描述
  "dir":"ltr", // 名称,短名称,描述的文本方向,ltr-从左到右,rtl-从右到左,auto-浏览器自动判断
  "orientation":"any", // 定义web程序顶级的默认方向
  "icons":[ // 程序的图标的图像数组
    {
      "src":"icon/lower.png", // 图片路径
      "size":48 x 48, // 图片尺寸
      "type":"image/webp" // 图片类型
    }
  ],
  "prefer_related_applications":false, // 指定一个布尔值,提示用户代理向用户指示指定的相关应用程序可用,并建议通过web应用程序.
  "related_applications":[ // 指定底层可安装或访问的本机应用程序
    {
      "platform":"web" // 找到应用程序的平台,
      "url":"", // 找到应用程序的url
      "id":"", // 指定平台上应用程序ID
    }
  ]
}

Service workers

有一个困扰web用户多年的难题---丢失网络连接,虽然现在有很多技术尝试解决这一难题,如:离线页面,但是仍然没有一个好的统筹机制对资源缓存和自定义网络请求进行控制.因此service workers应运而生,service workers可以使你的应用优先访问本地缓存资源,所以在离线状态下,没有通过网络接受更多的数据前,仍可以提供基本的功能. 它是一个服务器与浏览器的中间人角色,如果网站中注册了service worker,那么可以拦截当前网站的所有请求,进行判断(编写脚本处理).

设置
  • Firefox Nightly:访问 about:config,设置 dom.serviceWorkers.enabled 为true,然后重启浏览器
  • Chrome Canary:访问 chrome://flags,开启 exprimental-web-platform-features, 然后重启浏览器
  • Opera:访问 opera://flags,开启 ServiceWorker 的支持,重启浏览器
基本架构
  1. service worker URL 通过 serviceWorkerContainer.register(处理脚本,{scope:需要控制的目录}) 来获取和注册
  2. 注册成功后,service worker就在ServiceWorkerGlobalScope环境中运行,这是一个特殊类型的worker上下文运行环境,与主运行线程相独立,同时也没有访问dom的能力
  3. service worker 开始处理事件
  4. 受service worker控制的页面打开后会尝试安装service worker.最先发送给service worker的事件是安装事件(让所有资源可离线访问)
  5. 当 oninstall事件的处理程序执行完成后,可以理解为 service worker 安装完成
  6. 下一步激活,当 service worker 安装完成后,会接收到一个激活事件,onactivate事件主要用途是清除先前版本的service worker脚本中使用的资源
  7. Service Worker现在可以控制页面,但只是在 register()成功后打开的页面.也就是说,页面起始于有没有service worker,且在页面接下来的生命周期内维持这个状态,所以页面不得不重新加载以让service worker获得完全的控制
核心工作机制

Promise是一种非常适用于异步操作的机制,一个操作依赖于另一个操作的成功执行.这是service worker的核心工作机制

session

session保存在服务端的一个数据结构,用来跟踪用户的状态,可以保存在集群、数据库、文件中.

同源策略

同源策略是一个重要的安全策略,它用于限制一个origin的文档或者加载的脚本如何能与另一个源的资源进行交互.它能帮助阻隔恶意文档,减少可能被攻击的媒介. 如果两个URL的 protocol(协议)、port(端口)、host(主机/域名)都相同的话,则表示这两个URL同源. 在页面中通过 about:blank 或者 javascript: URL执行的脚本会继承打开该URL文档的源,因为这些类型的url没有包含源服务器的相关信息.