Prefetch和预加载实践

Stella981
• 阅读 490

之前介绍了利用Preload优化首屏关键资源的加载。今天跟大家介绍另一个性能优化手段——Prefetch。文末会结合常见工具,教大家在项目中使用Preload和Prefetch。

跟Preload类似,Prefetch也是Link的一种关系类型值,用于提示浏览器提前加载资源。跟Preload不同,Prefetch指示的是下一次导航可能需要的资源。浏览器识别到Prefetch时,应该加载该资源(且不执行),等到真正请求相同资源时,就能够得到更快的响应。

它的使用方式与Preload类似:

  • 在HTML的 中:

  • 通过JS动态插入:

    var hint = document.createElement("link");

    hint.rel = "prefetch";

    hint.as = "html";

    hint.href = "/article/part3.html";

    document.head.appendChild(hint);

  • 在HTTP头中:Link: https://example.com/banner.jpg; rel=prefetch; as=image;

Prefetch的兼容性如下:

Prefetch和预加载实践

跟Preload比起来,Prefetch的兼容范围更广。唯独在Safari上对Preload的支持度比Prefetch要好。

由于Preload和Prefetch两个小朋友的名字太像了,行为也十分相似。它们站在一起的时候,很多人傻傻分不清楚。下面来说一说它俩的区别:

Prefetch vs Preload

1. 网络请求的优先级

在Chrome中,Prefetch的优先级为Lowest。而Preload的优先级则是根据as属性值所对应的资源类型来决定,总体上,Preload的优先级比Prefetch高。不过两者都不应该延迟页面的load事件。

2. 缓存策略

Preload加载的资源至少会被缓存到内存中,下一次请求的时候直接从缓存读取,从而减少从服务器加载的时间。

Prefetch的缓存并未在标准中定义,所以浏览器不保证缓存资源。不过会根据资源本身的缓存头进行相应的处理。

2017年Addy Osmani的“Preload, Prefetch and Priorities in Chrome”文章提到:

Furthermore, prefetch requests are maintained in the unspecified net-stack cache for at least 5 minutes regardless of the cachability of the resource.

意思是不论资源的缓存配置如何,Prefetch的请求会被维护在网络栈中至少5分钟。那么现在的Chrome中是不是这样呢?

笔者在Chrome 69中测试发现,如果资源配置了no-store,或者在开发者工具的网络面板中禁用缓存,浏览器并不会缓存该资源。下次请求还会再次从服务器加载资源。

Prefetch和预加载实践

(图中第一次index.js的请求是使用的Prefetch,第二次是正常请求。笔者在服务端做了延迟1s响应的处理,可以从加载时长看出第二次请求仍然是从服务器获取)

在不禁用缓存且配置了适当的缓存控制的情况下,下次请求则会从缓存加载(from disk cache),可以节约网络加载时间:

Prefetch和预加载实践

所以对于想要Prefetch的资源要做好缓存控制,以便下次请求时命中缓存。而对于动态HTML文档,则没必要使用Prefetch加载。

3. 重复加载

如果Preload的资源还在途中,此时对相同的资源再发起请求,浏览器不会重复请求资源,而是等返回了再进行处理。

而如果Prefetch的资源还在途中,再发请求,会导致二次请求(如上面“缓存策略”所示)。除此之外,有人可能会将Prefetch作为Preload的降级方案紧跟在Preload后面,也会产生两次请求,如下图所示:

Prefetch和预加载实践

Prefetch和预加载实践

4. 页面跳转时的行为

如果在当前页面跳转到下一页,在途的Preload请求会被取消。

而Prefetch的请求会在导航过程中保持,如下图所示:

document.getElementById('btn').addEventListener('click', () => {

var hint = document.createElement("link");

hint.rel = "prefetch";

hint.as = "img";

hint.href = "https://p1.ssl.qhimg.com/t01709ea6aebf12d69f.jpg";

document.head.appendChild(hint);

location.href="https://code.h5jun.com/cuma"

})

Prefetch和预加载实践

(第一个图片请求在跳转时没有被取消)

5. 适用场景

Preload的设计初衷是为了让当前页面的关键资源尽早被发现和加载,从而提升首屏渲染性能。

Prefetch是为了提前加载下一个导航所需的资源,提升下一次导航的首屏渲染性能。但也可以用来在当前页面提前加载运行过程中所需的资源,加速响应。

那么在生产环境中如何方便地使用Preload和Prefetch呢?

实践:单页应用中的Preload和Prefetch

关键资源:在单页应用中,应尽早加载关键资源。以React项目为例,应尽早加载React.js以及入口文件。如果项目使用Webpack和htmlWebpackPlugin,入口脚本文件和CSS都是直接输出到HTML中,大部分浏览器能预测解析这些资源,所以不必特意Preload这些资源。

但是有一些隐藏资源,比如font文件,则需要Preload。这种资源可以使用preloadWebpackPlugin,结合htmlWebpackPlugin,在编译阶段插入link rel="preload"标签。配置如下:

const preloadWebpackPlugin = require('preload-webpack-plugin')

...

// webpack配置

plugins: [

new htmlWebpackPlugin(),

new preloadWebpackPlugin({

as(entry) {

if (/\.woff2$/.test(entry)) return 'font';

return 'script';

},

include: 'allAssets',

rel: 'preload',

fileWhitelist: [/\.woff2/]

})

]

在HTML文件的头部会生成如下标签:

Prefetch和预加载实践

异步路由组件则应当在初始化后再加载。之前有读者朋友说异步路由组件应该用Prefetch,这个策略很好,只有一个地方需要注意:如果当前路由文件也是异步的,那么在Prefetch它的途中大概率会再次请求当前路由组件,从而导致二次加载。所以需要更细粒度的加载策略。

比如可以在鼠标移入导航菜单时再预加载其他的路由组件。既可以使用Prefetch也可以使用Preload。这里笔者认为使用Preload更优,因为Prefetch的优先级比较低而且容易引起二次加载。在React项目中,可以使用react-loadable管理异步组件的加载,它还提供了Loading状态和 preload方法:

const AboutComponent = Loadable({

loader: () => import(/* webpackChunkName: "about" */'./routes/about.js'),

loading: Loading

});

...

onMouseOver = (page) => {

AboutComponent.preload(); // 鼠标移入时再预加载相应路由组件

};

...

<li onMouseOver={() => {this.onMouseOver()}}><Link to="/about">关于</Link></li>

其他异步模块可以用Webpack的魔法注释:import(/* webpackPrefetch: true */ "...")、 import(/* webpackPreload: true */ "...") 。有一个细节需要注意:Preload魔法注释只有写在非入口文件的chunk中才能“动态”插入 link rel="preload"标签。感兴趣的小伙伴可以试试。

点赞
收藏
评论区
推荐文章
blmius blmius
3年前
MySQL:[Err] 1292 - Incorrect datetime value: ‘0000-00-00 00:00:00‘ for column ‘CREATE_TIME‘ at row 1
文章目录问题用navicat导入数据时,报错:原因这是因为当前的MySQL不支持datetime为0的情况。解决修改sql\mode:sql\mode:SQLMode定义了MySQL应支持的SQL语法、数据校验等,这样可以更容易地在不同的环境中使用MySQL。全局s
皕杰报表之UUID
​在我们用皕杰报表工具设计填报报表时,如何在新增行里自动增加id呢?能新增整数排序id吗?目前可以在新增行里自动增加id,但只能用uuid函数增加UUID编码,不能新增整数排序id。uuid函数说明:获取一个UUID,可以在填报表中用来创建数据ID语法:uuid()或uuid(sep)参数说明:sep布尔值,生成的uuid中是否包含分隔符'',缺省为
待兔 待兔
5个月前
手写Java HashMap源码
HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程HashMap的使用教程22
Jacquelyn38 Jacquelyn38
3年前
2020年前端实用代码段,为你的工作保驾护航
有空的时候,自己总结了几个代码段,在开发中也经常使用,谢谢。1、使用解构获取json数据let jsonData  id: 1,status: "OK",data: 'a', 'b';let  id, status, data: number   jsonData;console.log(id, status, number )
Stella981 Stella981
3年前
Android So动态加载 优雅实现与原理分析
背景:漫品Android客户端集成适配转换功能(基于目标识别(So库35M)和人脸识别库(5M)),导致apk体积50M左右,为优化客户端体验,决定实现So文件动态加载.!(https://oscimg.oschina.net/oscnet/00d1ff90e4b34869664fef59e3ec3fdd20b.png)点击上方“蓝字”关注我
Wesley13 Wesley13
3年前
mysql设置时区
mysql设置时区mysql\_query("SETtime\_zone'8:00'")ordie('时区设置失败,请联系管理员!');中国在东8区所以加8方法二:selectcount(user\_id)asdevice,CONVERT\_TZ(FROM\_UNIXTIME(reg\_time),'08:00','0
Wesley13 Wesley13
3年前
00:Java简单了解
浅谈Java之概述Java是SUN(StanfordUniversityNetwork),斯坦福大学网络公司)1995年推出的一门高级编程语言。Java是一种面向Internet的编程语言。随着Java技术在web方面的不断成熟,已经成为Web应用程序的首选开发语言。Java是简单易学,完全面向对象,安全可靠,与平台无关的编程语言。
Wesley13 Wesley13
3年前
35岁是技术人的天花板吗?
35岁是技术人的天花板吗?我非常不认同“35岁现象”,人类没有那么脆弱,人类的智力不会说是35岁之后就停止发展,更不是说35岁之后就没有机会了。马云35岁还在教书,任正非35岁还在工厂上班。为什么技术人员到35岁就应该退役了呢?所以35岁根本就不是一个问题,我今年已经37岁了,我发现我才刚刚找到自己的节奏,刚刚上路。
Wesley13 Wesley13
3年前
MySQL部分从库上面因为大量的临时表tmp_table造成慢查询
背景描述Time:20190124T00:08:14.70572408:00User@Host:@Id:Schema:sentrymetaLast_errno:0Killed:0Query_time:0.315758Lock_
Python进阶者 Python进阶者
11个月前
Excel中这日期老是出来00:00:00,怎么用Pandas把这个去除
大家好,我是皮皮。一、前言前几天在Python白银交流群【上海新年人】问了一个Pandas数据筛选的问题。问题如下:这日期老是出来00:00:00,怎么把这个去除。二、实现过程后来【论草莓如何成为冻干莓】给了一个思路和代码如下:pd.toexcel之前把这