果我们测试一个版本时遇到Bug,然后等开发改了以后及时验证,开发可能会好心提醒一句,你先清一下浏览器缓存再测,是不是经常碰到这种情况?所以我们在测试中要经常和缓存打交道,既然是老朋友了,那我们干脆来好好认识一下它。
缓存基本上是对网页上经常访问的资源的存储和重用。由于存储是在快速访问的地方完成的,因此导航速度更快,这也提高了网站和应用程序的性能。这就是为什么 HTTP 缓存对于想要优化用户体验并因此增加收入的企业来说是必不可少的工具。
每一毫秒都很重要
如果你被问到一个网站最让你烦恼的是什么,你可能会回答说这是一个网站加载速度很慢。而且有调查显示:70%的消费者表示,网页加载的速度会直接影响他们在线购买的意愿。网页的缓慢加载和呈现会影响用户体验,从而导致失去客户参与和销售的机会。
由于我们生活在即时性的时代,因此在虚拟世界中逃脱的任何事物都是失败的代名词。百度和其他搜索引擎在对搜索进行排名时不会推荐缓慢的网站,想象一下用户在等待查看和浏览网站时的体验和时间消耗。
在访问量大的时候,使网站更快的有效方法是使用缓存。你应该听说过缓存,至于它是什么,以及如何工作的,这就是接下来要谈的内容。
什么是 HTTP 缓存?
为了更好地理解什么是HTTP缓存,首先你得知道HTTP是什么。简而言之HTTP是一种基于文本的应用层传输协议,我们一般称之为“超文本传输协议”,基于该协议可以传输如图像、图形、URL、 HTML 文本和脚本之类,那么这些的存储和重用,那这样留下备份就被称为缓存,从而防止每次访问页面时都下载它们。缓存的主要目的是通过重用以前的响应消息来满足当前的请求来提高通信性能。
HTTP 缓存是如何工作的?
缓存的工作方式如下:
缓存的类型
缓存的类型是根据内容的存储位置定义的。
缓存头
在缓存头中,给出了用于定义缓存特征的指令。例如:
缓存控制
在缓存头中,可以为缓存提供以下标准指令:
内容被视为私有内容,因为只有一个用户有权访问。在这种情况下,私有内容可以由客户端的浏览器存储,但不能由中间缓存存储。
内容被视为公开内容,因为多个用户可以访问。内容可以由浏览器存储,也可以存储在客户端和服务器之间的其他缓存中。
定义在不在源服务器上重新验证的情况下可以缓存内容的最长时间。时间以秒为单位定义,最大值为一年(31,536,000 秒)。
无法缓存内容,因此请求始终发送到源服务器。传输机密数据时会标明此格式。
必须在每个新请求中重新验证缓存的内容,这会使内容立即过时。在这种情况下,缓存会在释放存储的副本之前将请求发送到源服务器进行验证。
指示可以缓存内容的时间量,因此与 max-age 非常相似,但不同之处在于此选项仅适用于中间缓存,而不适用于浏览器。
Expires 标头定义内容何时过期。在指定的时间之后,缓存的内容将被视为过时,因此请求将有权访问源服务器上的最新内容。
Etag(实体标签)标头用于验证浏览器的缓存资源是否与源服务器上的缓存资源相同。也就是说,它验证客户端是否正在接收缓存内容的最新版本。此标头用作与网站上每个资源关联的唯一标识符。为了进行标识,网页服务器使用 Etag 值,该值在每次更改资源时都会修改。ETag 值是上次更新资源的日期和时间。
Last-Modified 标头显示浏览器上次修改资源的时间,以及它是否应使用缓存的副本或下载最新版本。例如,当用户访问您的网站时,浏览器会存储页面的资源,因此当他们下次访问该网站时,服务器会检查文件自上次访问以来是否已更改。如果没有修改,服务器会向浏览器发送“304 not modified”响应,并使用缓存的副本。
Vary 标头可以存储相同内容的不同版本,因此它用于请求缓存以在决定请求哪些内容之前检查其他标头。例如,当与 Accept-Encoding 标头一起使用时,该设置允许区分压缩和未压缩的内容,或者当与User-Agent 标头一起使用时,它会区分移动或桌面网站的版本。
缓存的好处
测试人员在什么场景下要留意缓存
1.及时验证测试
这是最常见的一类需要注意缓存的情况,就是如果遇到一些小bug,需要开发立即改好后验证,那就可以等开发改完,测试这边需要把验证用到的浏览器缓存清除一次,因为很可能页面还用的之前的缓存数据;
2. 页面加载性能测试
缓存可以用于加速页面加载时间。测试人员需要验证页面加载性能,并确保缓存策略不会导致页面内容过时或不一致;
3. 数据一致性测试
当应用程序使用缓存来存储数据时,测试人员需要测试缓存对数据一致性的影响。验证数据在缓存和数据库之间的同步性,以及在数据更新时缓存的更新机制,这点也是很重要的;
4. 用户认证和授权测试
缓存可能包含用户的认证和授权信息。测试人员需要验证用户登录和注销时缓存的正确更新,以及对用户权限变更的处理,虽然这在登录模块中是比较次要的测试,但是对于安全性要求较高的系统还是比较重要的;
5. 并发性能测试
在高并发环境下,缓存的并发读写可能导致性能问题。测试人员需要验证缓存在高负载情况下的表现,确保它能够正确处理并发请求,这对于性能有要求的系统就极为重视。
总结
总之,HTTP缓存是Web性能优化的关键组成部分,也是测试工程师所要牢牢掌握的知识点,理解了http缓存有助于我们对web的架构有更深的认识,这样才能在测试中驾轻就熟。
着我们的应用程序的不断增长并开始进行复杂的计算时,对速度的需求越来越高(️),所以流程的优化变得必不可少。 当我们忽略这个问题时,我们最终的程序需要花费大量时间并在执行期间消耗大量的系统资源。
缓存是一种优化技术,通过存储开销大的函数执行的结果,并在相同的输入再次出现时返回已缓存的结果,从而加快应用程序的速度。
如果这对你没有多大意义,那没关系。 本文深入解释了为什么需要进行缓存,缓存是什么,如何实现以及何时应该使用缓存。
什么是缓存
缓存是一种优化技术,通过存储开销大的函数执行的结果,并在相同的输入再次出现时返回已缓存的结果,从而加快应用程序的速度。
在这一点上,我们很清楚,缓存的目的是减少执行“昂贵的函数调用”所花费的时间和资源。
什么是昂贵的函数调用?别搞混了,我们不是在这里花钱。在计算机程序的上下文中,我们拥有的两种主要资源是时间和内存。因此,一个昂贵的函数调用是指一个函数调用中,由于计算量大,在执行过程中大量占用了计算机的资源和时间。
然而,就像对待金钱一样,我们需要节约。为此,使用缓存来存储函数调用的结果,以便在将来的时间内快速方便地访问。
缓存只是一个临时的数据存储,它保存数据,以便将来对该数据的请求能够更快地得到处理。
因此,当一个昂贵的函数被调用一次时,结果被存储在缓存中,这样,每当在应用程序中再次调用该函数时,结果就会从缓存中非常快速地取出,而不需要重新进行任何计算。
为什么缓存很重要?
下面是一个实例,说明了缓存的重要性:
想象一下,你正在公园里读一本封面很吸引人的新小说。每次一个人经过,他们都会被封面吸引,所以他们会问书名和作者。第一次被问到这个问题的时候,你翻开书,读出书名和作者的名字。现在越来越多的人来这里问同样的问题。你是一个很好的人,所以你回答所有问题。
你会翻开封面,把书名和作者的名字一一告诉他,还是开始凭记忆回答?哪个能节省你更多的时间?
发现其中的相似之处了吗?使用记忆法,当函数提供输入时,它执行所需的计算并在返回值之前将结果存储到缓存中。如果将来接收到相同的输入,它就不必一遍又一遍地重复,它只需要从缓存(内存)中提供答案。
缓存是怎么工作的
JavaScript 中的缓存的概念主要建立在两个概念之上,它们分别是:
闭包
闭包是函数和声明该函数的词法环境的组合。
不是很清楚? 我也这么认为。
为了更好的理解,让我们快速研究一下 JavaScript 中词法作用域的概念,词法作用域只是指程序员在编写代码时指定的变量和块的物理位置。如下代码:
function foo(a) { var b = a + 2; function bar(c) { console.log(a, b, c); } bar(b * 2); } foo(3); // 3, 5, 10
从这段代码中,我们可以确定三个作用域:
仔细查看上面的代码,我们注意到函数 foo 可以访问变量 a 和 b,因为它嵌套在 foo 中。注意,我们成功地存储了函数 bar 及其运行环境。因此,我们说 bar 在 foo 的作用域上有一个闭包。
你可以在遗传的背景下理解这一点,即个体有机会获得并表现出遗传特征,即使是在他们当前的环境之外,这个逻辑突出了闭包的另一个因素,引出了我们的第二个主要概念。
从函数返回函数
通过接受其他函数作为参数或返回其他函数的函数称为高阶函数。
闭包允许我们在封闭函数的外部调用内部函数,同时保持对封闭函数的词法作用域的访问
让我们对前面的示例中的代码进行一些调整,以解释这一点。
function foo(){ var a = 2; function bar() { console.log(a); } return bar; } var baz = foo(); baz();//2
注意函数 foo 如何返回另一个函数 bar。这里我们执行函数 foo 并将返回值赋给baz。但是在本例中,我们有一个返回函数,因此,baz 现在持有对 foo 中定义的bar 函数的引用。
最有趣的是,当我们在 foo 的词法作用域之外执行函数 baz 时,仍然会得到 a 的值,这怎么可能呢?
请记住,由于闭包的存在,bar 总是可以访问 foo 中的变量(继承的特性),即使它是在 foo 的作用域之外执行的。
案例研究:斐波那契数列
斐波那契数列是什么?
斐波那契数列是一组数字,以1 或 0 开头,后面跟着1,然后根据每个数字等于前两个数字之和规则进行。如
0, 1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, …
或者
1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, …
挑战:编写一个函数返回斐波那契数列中的 n 元素,其中的序列是:
[1, 1, 2, 3, 5, 8, 13, 21, 34, 55, 89, 144, …]
知道每个值都是前两个值的和,这个问题的递归解是:
function fibonacci(n) { if (n <= 1) { return 1 } return fibonacci(n - 1) + fibonacci(n - 2) }
确实简洁准确!但是,有一个问题。请注意,当 n 的值到终止递归之前,需要做大量的工作和时间,因为序列中存在对某些值的重复求值。
看看下面的图表,当我们试图计算 fib(5)时,我们注意到我们反复地尝试在不同分支的下标 0,1,2,3 处找到 Fibonacci 数,这就是所谓的冗余计算,而这正是缓存所要消除的。
function fibonacci(n, memo) { memomemo = memo || {} if (memo[n]) { return memo[n] } if (n <= 1) { return 1 } return memo[n] = fibonacci(n-1, memo) + fibonacci(n-2, memo) }
在上面的代码片段中,我们调整函数以接受一个可选参数 memo。我们使用 memo 对象作为缓存来存储斐波那契数列,并将其各自的索引作为键,以便在执行过程中稍后需要时检索它们。
memomemo = memo || {}
在这里,检查是否在调用函数时将 memo 作为参数接收。如果有,则初始化它以供使用;如果没有,则将其设置为空对象。
if (memo[n]) { return memo[n] }
接下来,检查当前键 n 是否有缓存值,如果有,则返回其值。
和之前的解一样,我们指定了 n 小于等于 1 时的终止递归。
最后,我们递归地调用n值较小的函数,同时将缓存值(memo)传递给每个函数,以便在计算期间使用。这确保了在以前计算并缓存值时,我们不会第二次执行如此昂贵的计算。我们只是从 memo 中取回值。
注意,我们在返回缓存之前将最终结果添加到缓存中。
使用 JSPerf 测试性能
可以使用些链接来性能测试。在那里,我们运行一个测试来评估使用这两种方法执行fibonacci(20) 所需的时间。结果如下:
哇! ! !这让人很惊讶,使用缓存的 fibonacci 函数是最快的。然而,这一数字相当惊人。它执行 126,762 ops/sec,这远远大于执行 1,751 ops/sec 的纯递归解决方案,并且比较没有缓存的递归速度大约快 99%。
注:“ops/sec”表示每秒的操作次数,就是一秒钟内预计要执行的测试次数。
现在我们已经看到了缓存在函数级别上对应用程序的性能有多大的影响。这是否意味着对于应用程序中的每个昂贵函数,我们都必须创建一个修改后的变量来维护内部缓存?
不,回想一下,我们通过从函数返回函数来了解到,即使在外部执行它们,它们也会导致它们继承父函数的范围,这使得可以将某些特征和属性从封闭函数传递到返回的函数。
使用函数的方式
在下面的代码片段中,我们创建了一个高阶的函数 memoizer。有了这个函数,将能够轻松地将缓存应用到任何函数。
function memoizer(fun) { let cache = {} return function (n) { if (cache[n] != undefined) { return cache[n] } else { let result = fun(n) cache[n] = result return result } } }
上面,我们简单地创建一个名为 memoizer 的新函数,它接受将函数 fun 作为参数进行缓存。在函数中,我们创建一个缓存对象来存储函数执行的结果,以便将来使用。
从 memoizer 函数中,我们返回一个新函数,根据上面讨论的闭包原则,这个函数无论在哪里执行都可以访问 cache。
在返回的函数中,我们使用 if..else 语句检查是否已经有指定键(参数) n 的缓存值。如果有,则取出并返回它。如果没有,我们使用函数来计算结果,以便缓存。然后,我们使用适当的键 n 将结果添加到缓存中,以便以后可以从那里访问它。最后,我们返回了计算结果。
很顺利!
要将 memoizer 函数应用于最初递归的 fibonacci 函数,我们调用 memoizer 函数,将 fibonacci 函数作为参数传递进去。
const fibonacciMemoFunction = memoizer(fibonacciRecursive)
测试 memoizer 函数
当我们将 memoizer 函数与上面的例子进行比较时,结果如下:
memoizer 函数以 42,982,762 ops/sec 的速度提供了最快的解决方案,比之前考虑的解决方案速度要快 100%。
关于缓存,我们已经说明什么是缓存 、为什么要有缓存和如何实现缓存。现在我们来看看什么时候使用缓存。
何时使用缓存
当然,使用缓存效率是级高的,你现在可能想要缓存所有的函数,这可能会变得非常无益。以下几种情况下,适合使用缓存:
缓存库
总结
使用缓存方法 ,我们可以防止函数调用函数来反复计算相同的结果,现在是你把这些知识付诸实践的时候了。
天下数据是国内屈指可数的拥有多处海外自建机房的新型IDC服务商,被业界公认为“中国IDC行业首选品牌”。
天下数据与全球近120多个国家顶级机房直接合作,包括香港、美国、韩国、日本、台湾、新加坡、荷兰、法国、英国、德国、埃及、南非、巴西、印度、越南等国家和地区的服务器、云服务器的服务.
除提供传统的IDC产品外,天下数据的主要职责是为大中型企业提供更精细、安全、满足个性需求的定制化服务器解决方案,特别是在直销、金融、视频、流媒体、游戏、电子商务、区块链、快消、物联网、大数据等诸多行业,为广大客户解决服务器租用中遇到的各种问题。
言
本篇文章主要介绍了前端HTML5几种存储方式的总结 ,主要包括本地存储localstorage,本地存储sessionstorage,离线缓存(application cache),Web SQL,IndexedDB。有兴趣的可以了解一下。
正文开始~
总体情况
h5之前,存储主要是用cookies。cookies缺点有在请求头上带着数据,大小是4k之内。主Domain污染。
主要应用:购物车、客户登录
对于IE浏览器有UserData,大小是64k,只有IE浏览器支持。
目标
存储方式:
以键值对(Key-Value)的方式存储,永久存储,永不失效,除非手动删除。
大小:
每个域名5M
支持情况:
注意:IE9 localStorage不支持本地文件,需要将项目署到服务器,才可以支持!
if(window.localStorage){ alert('This browser supports localStorage'); }else{ alert('This browser does NOT support localStorage'); }
常用的API:
getItem //取记录
setIten//设置记录
removeItem//移除记录
key//取key所对应的值
clear//清除记录
存储的内容:
数组,图片,json,样式,脚本。。。(只要是能序列化成字符串的内容都可以存储)
2.本地存储sessionstorage
HTML5 的本地存储 API 中的 localStorage 与 sessionStorage 在使用方法上是相同的,区别在于 sessionStorage 在关闭页面后即被清空,而 localStorage 则会一直保存。
3.离线缓存(application cache)
本地缓存应用所需的文件
使用方法:
①配置manifest文件
页面上:
<!DOCTYPE HTML> <html manifest="demo.appcache"> ... </html>
Manifest 文件:
manifest 文件是简单的文本文件,它告知浏览器被缓存的内容(以及不缓存的内容)。
manifest 文件可分为三个部分:
①CACHE MANIFEST - 在此标题下列出的文件将在首次下载后进行缓存
②NETWORK - 在此标题下列出的文件需要与服务器的连接,且不会被缓存
③FALLBACK - 在此标题下列出的文件规定当页面无法访问时的回退页面(比如 404 页面)
完整demo:
CACHE MANIFEST # 2016-07-24 v1.0.0 /theme.css /main.js NETWORK: login.jsp FALLBACK: /html/ /offline.html
服务器上:manifest文件需要配置正确的MIME-type,即 "text/cache-manifest"。
如Tomcat:
<mime-mapping> <extension>manifest</extension> <mime-type>text/cache-manifest</mime-type> </mime-mapping>
常用API:
核心是applicationCache对象,有个status属性,表示应用缓存的当前状态:
0(UNCACHED) : 无缓存, 即没有与页面相关的应用缓存
1(IDLE) : 闲置,即应用缓存未得到更新
2 (CHECKING) : 检查中,即正在下载描述文件并检查更新
3 (DOWNLOADING) : 下载中,即应用缓存正在下载描述文件中指定的资源
4 (UPDATEREADY) : 更新完成,所有资源都已下载完毕
5 (IDLE) : 废弃,即应用缓存的描述文件已经不存在了,因此页面无法再访问应用缓存
相关的事件:
表示应用缓存状态的改变:
checking : 在浏览器为应用缓存查找更新时触发
error : 在检查更新或下载资源期间发送错误时触发
noupdate : 在检查描述文件发现文件无变化时触发
downloading : 在开始下载应用缓存资源时触发
progress:在文件下载应用缓存的过程中持续不断地下载地触发
updateready : 在页面新的应用缓存下载完毕触发
cached : 在应用缓存完整可用时触发
Application Cache的三个优势:
① 离线浏览
② 提升页面载入速度
③ 降低服务器压力
注意事项:
1. 浏览器对缓存数据的容量限制可能不太一样(某些浏览器设置的限制是每个站点 5MB)
2. 如果manifest文件,或者内部列举的某一个文件不能正常下载,整个更新过程将视为失败,浏览器继续全部使用老的缓存
3. 引用manifest的html必须与manifest文件同源,在同一个域下
4. 浏览器会自动缓存引用manifest文件的HTML文件,这就导致如果改了HTML内容,也需要更新版本才能做到更新。
5. manifest文件中CACHE则与NETWORK,FALLBACK的位置顺序没有关系,如果是隐式声明需要在最前面
6. FALLBACK中的资源必须和manifest文件同源
7. 更新完版本后,必须刷新一次才会启动新版本(会出现重刷一次页面的情况),需要添加监听版本事件。
8. 站点中的其他页面即使没有设置manifest属性,请求的资源如果在缓存中也从缓存中访问
9. 当manifest文件发生改变时,资源请求本身也会触发更新
离线缓存与传统浏览器缓存区别:
1. 离线缓存是针对整个应用,浏览器缓存是单个文件
2. 离线缓存断网了还是可以打开页面,浏览器缓存不行
3. 离线缓存可以主动通知浏览器更新资源
4.Web SQL
关系数据库,通过SQL语句访问
Web SQL 数据库 API 并不是 HTML5 规范的一部分,但是它是一个独立的规范,引入了一组使用 SQL 操作客户端数据库的 APIs。
支持情况:
Web SQL 数据库可以在最新版的 Safari, Chrome 和 Opera 浏览器中工作。
核心方法:
①openDatabase:这个方法使用现有的数据库或者新建的数据库创建一个数据库对象。
②transaction:这个方法让我们能够控制一个事务,以及基于这种情况执行提交或者回滚。
③executeSql:这个方法用于执行实际的 SQL 查询。
打开数据库:
var db = openDatabase('mydb', '1.0', 'Test DB', 2 * 1024 * 1024,fn); //openDatabase() 方法对应的五个参数分别为:数据库名称、版本号、描述文本、数据库大小、创建回调
执行查询操作:
var db = openDatabase('mydb', '1.0', 'Test DB', 2 * 1024 * 1024); db.transaction(function (tx) { tx.executeSql('CREATE TABLE IF NOT EXISTS WIN (id unique, name)'); });
插入数据:
var db = openDatabase('mydb', '1.0', 'Test DB', 2 * 1024 * 1024); db.transaction(function (tx) { tx.executeSql('CREATE TABLE IF NOT EXISTS WIN (id unique, name)'); tx.executeSql('INSERT INTO WIN (id, name) VALUES (1, "winty")'); tx.executeSql('INSERT INTO WIN (id, name) VALUES (2, "LuckyWinty")'); });
读取数据:
db.transaction(function (tx) { tx.executeSql('SELECT * FROM WIN', [], function (tx, results) { var len = results.rows.length, i; msg = "<p>查询记录条数: " + len + "</p>"; document.querySelector('#status').innerHTML += msg; for (i = 0; i < len; i++){ alert(results.rows.item(i).name ); } }, null); });
由这些操作可以看出,基本上都是用SQL语句进行数据库的相关操作,如果你会MySQL的话,这个应该比较容易用。
索引数据库 (IndexedDB) API(作为 HTML5 的一部分)对创建具有丰富本地存储数据的数据密集型的离线 HTML5 Web 应用程序很有用。同时它还有助于本地缓存数据,使传统在线 Web 应用程序(比如移动 Web 应用程序)能够更快地运行和响应。
异步API:
在IndexedDB大部分操作并不是我们常用的调用方法,返回结果的模式,而是请求——响应的模式,比如打开数据库的操作
这样,我们打开数据库的时候,实质上返回了一个DB对象,而这个对象就在result中。由上图可以看出,除了result之外。还有几个重要的属性就是onerror、onsuccess、onupgradeneeded(我们请求打开的数据库的版本号和已经存在的数据库版本号不一致的时候调用)。这就类似于我们的ajax请求那样。我们发起了这个请求之后并不能确定它什么时候才请求成功,所以需要在回调中处理一些逻辑。
关闭与删除:
function closeDB(db){ db.close(); } function deleteDB(name){ indexedDB.deleteDatabase(name); }
数据存储:
indexedDB中没有表的概念,而是objectStore,一个数据库中可以包含多个objectStore,objectStore是一个灵活的数据结构,可以存放多种类型数据。也就是说一个objectStore相当于一张表,里面存储的每条数据和一个键相关联。
我们可以使用每条记录中的某个指定字段作为键值(keyPath),也可以使用自动生成的递增数字作为键值(keyGenerator),也可以不指定。选择键的类型不同,objectStore可以存储的数据结构也有差异。
天下数据是国内屈指可数的拥有多处海外自建机房的新型IDC服务商,被业界公认为“中国IDC行业首选品牌”。
天下数据与全球近120多个国家顶级机房直接合作,提供包括香港、美国、韩国、日本、台湾、新加坡、荷兰、法国、英国、德国、埃及、南非、巴西、印度、越南等国家和地区的服务器、云服务器的租用服务,需要的请联系天下数据客服!
除提供传统的IDC产品外,天下数据的主要职责是为大中型企业提供更精细、安全、满足个性需求的定制化服务器解决方案,特别是在直销、金融、视频、流媒体、游戏、电子商务、区块链、快消、物联网、大数据等诸多行业,为广大客户解决服务器租用中遇到的各种问题。
*请认真填写需求信息,我们会在24小时内与您取得联系。