当前位置: 首页 > 科技观察

当你点击浏览器的前进后退按钮时,页面的缓存机制

时间:2023-03-18 02:18:16 科技观察

这是一篇翻译文原文标题:Back/forwardcache原文链接:https://web.dev/bfcache/Back/forward缓存(Back/forwardcache,以下简称bfcache)是一种浏览器优化,可以实现即时的前后导航。它显着改善了用户的浏览体验,尤其是那些网络或设备较慢的用户。作为Web开发人员,了解如何在所有浏览器上基于bfcache优化页面非常重要。这改善了用户体验。浏览器兼容性Firefox和Safari长期以来都支持桌面和移动设备上的bfcache。从版本86开始,Chrome已经为一小部分用户在Android上启用了跨站点导航。在chrome87中,将向所有Android用户推出跨站点导航的bfcache支持,目标是在不久的将来也支持同站点导航。bfcache基础知识bfcache是一种内存缓存,可在用户离开时存储页面的完整快照(包括JavaScript堆)。由于整个页面都在内存中,如果用户决定返回,浏览器可以快速轻松地恢复页面。有多少次您访问过一个网站,单击一个链接转到另一个页面,却发现这不是您想要的,然后点击后退按钮?此时bfcache会对上一页的加载速度有很大的影响:不支持bfc时:会发起一个新的请求来加载上一页,并且,取决于那个页面针对重复访问的优化程度,浏览器可能需要重新下载、重新解析和重新执行刚刚下载的部分(或全部)资源。BFC开启时:加载上一页基本上是即时的,因为整个页面可以从内存中恢复而无需访问网络。bfcache不仅可以加快导航速度,还可以减少数据使用,因为不必再次下载资源。Chrome使用数据显示,桌面设备上十分之一的导航和移动设备上五分之一的导航是后退或前进。启用bfcache后,浏览器每天可以消除数十亿网页的数据传输和加载时间!缓存如何工作bfcache使用与HTTP缓存不同的“缓存”(这对于加快重复导航也很有用)。bfcache是整个页面(包括JavaScript堆)的内存快照,而HTTP缓存仅包含对先前发出的请求的响应。由于加载页面所需的所有请求都可以从HTTP缓存中得到满足的情况非常罕见,因此使用bfcache恢复的重复访问将始终比最佳的非bfcache导航更快。然而,在内存中创建页面快照有一些复杂性,尤其是涉及到如何最好地保存正在进行的代码时。例如,当页面在bfcache中时,您如何处理超时的setTimeout()调用?答案是浏览器暂停运行任何挂起的计时器或未解决的承诺(实际上是JavaScript任务队列中的所有挂起任务),并在(或如果)页面从bfcache恢复时恢复处理。在某些情况下,暂停任务的风险较低(例如,超时或承诺),但在其他情况下,它可能会导致非常混乱或意外的行为。例如,如果浏览器暂停IndexedDB事务中所需的任务,它可能会影响在同一来源打开的其他选项卡(因为多个选项卡可以同时访问同一个IndexedDB数据库)。因此,浏览器通常不会尝试在IndexedDB事务中间缓存页面,也不会使用可能影响其他页面的API。有关各种API用法如何影响页面的bfcache的详细信息,请参见下文。听取bfcache的API虽然bfcache是浏览器自动执行的优化,但对于开发人员来说,了解何时发生这种情况仍然很重要,这样他们就可以针对bfcache优化页面并调整任何指标或性能指标。用于观察bfcache的主要事件是页面转换事件pageshow和pagehide,这两个事件与bfcache存在的时间一样长,并且在当今使用的几乎所有浏览器中都得到支持。当页面进入或离开bfcache以及其他一些情况时,也会触发新的页面生命周期事件freeze和resume。例如,当后台选项卡冻结以最小化CPU使用率时。请注意,页面生命周期事件目前仅在基于Chromium的浏览器中受支持。从bfc监听页面恢复当页面最初加载时,pageshow事件会在load事件之后立即触发。另外,pageshow也会在页面从bfcache中恢复时触发。pageshow事件有一个持久属性,如果页面从bfcache中恢复则为true,否则为false。您可以使用persisted属性来区分常规页面加载和bfcache恢复。例如:window.addEventListener('pageshow',function(event){if(event.persisted===true){//pagerestoredfrombfcconsole.log('Thispagewasrestoredfromthebfcache.');}else{//页面正常加载console.log('Thispagewasloadednormally.');}});在支持页面生命周期API的浏览器中,当页面从bfcache恢复时(就在pageshow事件之前),resume事件也会触发,但是当用户重新启动时它也会在访问冻结的后台选项卡时触发。如果你想在冻结一个页面(包括bfcache中的页面)后恢复页面状态,你可以使用freeze事件,但是如果你想衡量你站点的bfcache命中率,你需要使用pageshow事件。在某些情况下,您可能需要同时使用这两种方法。bfcpagehide事件是监听页面进入时pageshow事件的对应项。pageshow事件在页面正常加载或从bfcache恢复时触发。pagehide事件在页面正常卸载或浏览器尝试将其放入bfcache时触发。pagehide事件也有一个持久属性,如果它是false那么你可以确定页面不会进入bfcache。但是,如果持久性属性为真,则无法保证页面会被缓存。这意味着浏览器打算缓存该页面,但可能有一些因素阻止它被缓存。window.addEventListener('pagehide',function(event){if(event.persisted===true){//页面可能进入bfc缓存console.log('Thispage*might*beenteringthebfcache.');}else{//页面会正常退出并被丢弃console.log('Thispagewillunloadnormallyandbediscarded.');}});同样,freeze事件将在pagehide事件之后立即触发(如果该事件的持久属性为true),但这也意味着浏览器打算缓存该页面。在下面描述的情况下,它可能仍然不得不丢弃它。为bfcache优化页面并非所有页面都存储在bfcache中,即使页面确实存储在那里,它也不会无限期地保留在那里。开发人员必须了解什么使页面符合(和不符合)bfcache条件,以最大限度地提高缓存命中率。以下部分概述了使浏览器尽可能多地缓存页面的最佳实践。不要使用unload事件在所有浏览器中优化bfcache的最重要方法是永远不要使用unload事件。unload事件对浏览器来说是有问题的,因为它在bfcache之前触发,并且Web上的许多页面都在(合理的)假设下运行,即在unload事件触发后,页面不再存在。这带来了挑战,因为许多页面都是基于这样的假设构建的,即当用户离开时将触发卸载事件。但是,情况已不再如此(而且已经很长时间了)。译者注:我这里的理解是,浏览器在设计unload事件的时候,是在页面不需要的时候触发的。如果开发者监听到unload事件,说明页面销毁的时候需要执行一些逻辑。这时候页面自然不需要缓存了。但是,在这种情况下,很多开发者希望页面被缓存起来,这就与unload事件本身的意义相冲突了。所以浏览器面临着两难的境地。他们必须改善用户体验,但也有破坏页面的风险。如果添加了卸载侦听器,Firefox已选择使页面不符合bfcache的条件,这样风险较小,但也会使许多页面无法进行bfcach。Safari将尝试缓存监听卸载事件的页面,但为了减少潜在的破坏,Safari不会在用户导航离开时触发卸载事件。由于Chrome中65%的页面都注册了unload事件监听器,为了能够缓存尽可能多的页面,Chrome选择与Safari保持一致。不要使用unload事件,使用pagehide事件。pagehide事件在unload事件触发的所有情况下都会触发,并且当页面被放入bfcache时也会触发。注意,永远不要添加卸载事件监听器!请改用pagehide事件。在Firefox中添加卸载事件侦听器会降低您的网站速度,而代码在Chrome和Safari中大部分时间都不会运行。只是有条件地添加beforeunload事件不会使您的页面不符合Chrome或Safari的bfcache的条件,但在Firefox中不会,因此除非绝对必要,否则请避免使用它。但是,与卸载事件不同,beforeunload有合法用途。例如,当您想要警告用户他们有未保存的更改时,如果他们离开页面,他们将会丢失。在这种情况下,建议只在用户有未保存的更改时才添加一个beforeunload监听器,然后在保存未保存的更改后立即将其移除。下面的写法是?(无条件监听beforeunload事件):window.addEventListener('beforeunload',(event)=>{if(pageHasUnsavedChanges()){event.preventDefault();returnevent.returnValue='Areyousureyouwanttoexit?';}});下面写成?:functionbeforeUnloadListener(event){event.preventDefault();returnevent.returnValue='Areyousureyouwanttoexit?';};//Afunctionthatinvokesacallbackwhenthepagehasunsavedchanges.//只在页面内容未保存时监听beforeunloadEventonPageHasUnsavedChanges(()=>{window.addEventListener('beforeunload',beforeUnloadListener);});//当页面未保存的更改被解析时调用回调的函数。//当页面内容被保存时,移除beforeunload事件onAllChangesSaved(()=>{window.removeEventListener('beforeunload',beforeUnloadListener);});避免window.opener引用在某些浏览器中(包括chrome,86版本开始),如果使用window.open或者target=_blank打开新页面,但是没有写:rel="noopener",那么新打开的页面将包含对原始页面的引用。除了存在安全风险之外,保留对其他页面的引用的页面不能安全地放入bfcache,因为这可能会破坏任何试图访问它的页面。译者注:关于安全问题,请参考本文公众号:在使用标签时,你可能会忽略一个安全问题。所以最好使用rel="noopener",保证打开的页面不能引用上一个页面。如果你的站点需要打开一个新页面并通过window.postMessage()控制上一个窗口或者直接引用上一个窗口对象,无论是新打开的页面还是上一个页面都不符合bfcache的条件。始终在用户离开之前关闭打开的连接。如上所述,当页面被放入bfcache时,所有计划的JavaScript任务都会暂停,然后在页面从缓存中取出时恢复。如果这些计划的javascript任务只访问domapi或其他与当前页面隔离的api,那么在用户看不到页面时暂停这些任务不会造成任何问题。但是,如果这些任务涉及与其他页面相关的API(例如:IndexedDB、WebLocks、WebSockets等),则可能会出现问题,因为暂停这些任务可能会导致其他选项卡中的代码无法运行。因此,如果出现以下情况,大多数浏览器不会尝试将页面放入bfcache:页面具有未完成的indexdb事务页面具有正在进行的提取和ajax请求页面具有打开的WebSocket或WebRTC连接如果您的页面正在运行使用这些API中的任何一个,那么最好在页面隐藏或冻结事件期间始终关闭连接并删除或断开观察者。这将允许浏览器安全地缓存页面而不影响其他打开的选项卡。然后,如果页面从bfcache恢复,您可以重新打开或重新连接到这些API(在pageshow或resume事件中)。测试以确保页面可缓存虽然无法确定页面在卸载时是否已放入缓存中,但可以断言向后或向前导航确实会从缓存中恢复页面。目前,在Chrome中,一个页面可以在bfcache中停留3分钟,这应该足以运行测试(使用puppeter或WebDriver等工具)以确保在离开页面并单击后pageshow事件的持久属性为真后退按钮。请注意,虽然在正常情况下页面应在缓存中保留足够长的时间以运行测试,但它们可以随时被静默地逐出(例如,如果系统处于内存压力下)。失败的测试并不一定意味着该页面不可缓存,因此您需要相应地配置测试或构建失败标准。退出bfcache的方法如果不想让一个页面存储在bfcache中,可以通过在顶层页面的响应中设置Cache-Control头部为no-store来保证该页面不被缓存:Cache-Control:no-store所有其他的缓存指令(包括子帧中的no-cache甚至no-store)都不会影响页面的bfcache资格。虽然这种方法很有效并且可以跨浏览器工作,但它具有其他缓存和性能影响。为了解决这个问题,有人提议添加一个更明确的注销机制,包括一种在需要时清除bfcache的机制(例如,当用户从共享设备上的网站注销时)。在Chrome中,这可以通过设置#back-forward-cache标志或企业证书来实现。bfcache如何影响分析和性能指标如果您使用分析工具跟踪您的网站流量,您可能会注意到随着Chrome继续为更多用户启用bfcache,报告的页面浏览量减少了。事实上,您可能低估了其他实现bfcache的浏览器的页面浏览量,因为大多数流行的分析库不会将bfcache恢复跟踪为新的页面浏览量。如果你不想因为启用Chrome的bfcache而导致pv数下降,你可以通过监听pageshow事件并检查persistent属性将bfcache报告恢复为pv。例如://Sendapageviewwhenthepageisfirstloaded.gtag('event','page_view')window.addEventListener('pageshow',function(event){if(event.persisted===true){//Sendanotherpageviewifthepageisrestoredfrombfcache.gtag('event','页面预览')}});性能指标bfcache还会对字段中收集的性能指标产生负面影响,尤其是衡量页面加载时间的指标。由于bfcache导航恢复现有页面而不是启动新页面加载,因此启用bfcache时收集的页面加载总数将会减少。然而,至关重要的是,由bfcache恢复取代的页面加载可能是数据集中最快的。这是因为根据定义,后退和前进导航是重复访问,重复页面加载通常比首次页面加载更快(由于上述HTTP缓存)。结果是数据集中较少的快速页面加载,这可能会使分发变慢,尽管用户体验的性能可能有所提高!有几种方法可以解决这个问题。一种方法是使用相应的导航类型注释所有页面加载指标:导航、重新加载、后退或预加载。这将使您能够继续监控这些导航类型的性能,即使整体分布为负数也是如此。建议将此方法用于非以用户为中心的页面加载指标,例如首字节时间(TTFB)。对网页核心指标的影响网页核心指标从不同维度(加载速度、交互性、视觉稳定性)衡量用户对网页的体验。由于用户体验到bfcache比传统页面加载恢复更快的导航,因此核心WebVitals指标反映这一点很重要。毕竟用户不关心bfcache是??否开启,只关心导航快不快!Chrome用户体验报告等工具将很快更新,以将bfcache恢复视为数据集中的单个页面访问。尽管在bfcache恢复后没有专门的Web性能API来衡量这些指标,但可以使用现有的WebAPI来近似它们的值。对于最大内容绘制(LCP),您可以使用pageshow事件的时间戳和下一个绘制帧的时间戳之间的增量(因为框架中的所有元素将同时绘制)。请注意,对于bfcache还原,LCP和FCP(第一帧内容绘制)是相同的。对于首次输入延迟(FID),可以将事件侦听器重新添加到pageshow事件(与FIDpolyfill使用的侦听器相同),并将FID报告为bfcache恢复后首次输入的延迟。对于CumulativeLayoutShift(CLS),您可以继续使用现有的PerformanceObserver;你只要将当前的CLS值重置为0即可。本文转载自微信公众号《CoyPan符合预期》,可关注下方二维码。转载本文,请联系符合您期望的CoyPan公众号。

猜你喜欢