web资源预加载-生产环境实践
此文记录资源预加载在我们项目的实践,技术难度不算高,重在介绍一套技术方案的诞生与实施,其中都进行了哪些思考,依据什么来做决策,如何进行效果评估,等等。为读者在制定技术方案时提供一定启示。
背景
资源预加载机制很好理解,即在用户访问页面之前,提前加载好相应的资源。这样用户在访问页面的时候,省去了加载资源的时间,达到“秒开”的效果。
资源预加载的方案很多,本文所述的是纯web下的资源预加载,区别于利用容器做资源预置。所以采用的技术都是纯web方案。
另外还有一个背景:项目是SPA架构,webpack+vue全家桶,我们做的是在加载完首页之后的事情,即跳转其他页面时的预加载。配合SPA应用的优势,可以实现媲美原生应用的零延迟跳转。
下面来介绍下技术细节。
预加载哪些资源
可以预加载的资源是很多的,页面异步chunk、vue组件、js模块,甚至是接口数据也可以预请求。所以就要看你的目的是什么了。
此处我们以零延迟跳转页面为目标,所以要预加载的就是页面异步chunk。所谓页面异步chunk,是指使用vue-router定义的页面路由所对应的异步加载的文件,其实也是个vue组件,为了跟其他vue组件区分,我们管它叫页面异步chunk吧。代码里一般这么写的:
const routes = [
name: 'home',
path: '/home',
children: [
{
path: 'A',
component: () => import('pageA.vue'),
},
{
path: 'B',
component: () => import('pageB.vue'),
}
]
]
pageA.vue和pageB.vue打包出的异步chunk在对应的路由下才会加载,给页面跳转带来延迟感,我们要预加载的就是这部分资源啦。
资源加载时机
明确了要加载的资源,接下来要考虑的是,什么时候加载这些资源呢?如果我们预加载了pageB而用户却不跳转B怎么办呢?
资源加载时机,这是个技术活,需要抓住两个关键点:
1.预加载资源应该尽量不影响用户的正常操作
2.预加载的资源要尽量保证用户能使用到
第1点,很容易想到,我们可以在页面空闲的时候去预加载。而且还要尽早,如果你加载晚了,用户就用不上你预加载的资源了。总结一下,资源加载时机就是“尽早的在页面空闲的时候”。
第2点,这就不好办了,用户跳不跳B页面是用户行为,我们怎么能保证他一定能用到预加载的资源呢。所以这个只能采取策略,保持一个可接受的比例,在“浪费流量”与“用户体验”之间找到权衡点。
下面就这两点展开说一说。
页面空闲检测
如何检测页面是否空闲呢?很不幸目前没有这样的api可用,有一个requestIdleFrameCallback,支持在每帧绘制空闲期执行一个回调函数,但不能确保一定执行。
所以我们只能自己想办法判断了,思路其实也比较简单,浏览器的渲染进程有js引擎和UI引擎这两个线程,只要这两个线程是空闲的,我们就认为页面是空闲的,这应该是行得通的。
那么,如何检测这两个线程空闲呢?其实很简单,看代码就明白了:
// 检测js线程空闲
const d1 = new Date();
setTimeout(()=>{
const offset = new Date() - d1;
if (offset < 25 ) {
// JS线程空闲
}
}), 20);
启动一个延时函数,查看真正执行时候的延时是多少,如果延时很大,那说明当前js引擎繁忙。如果小于某个阈值,那说明js引擎空闲。
这个阈值该如何确定呢?统计真实用户的情况肯定是最准确的,所以我们用一个空闲页面统计了用户的延时均值,最终确定为5ms。
同样的思路,UI引擎的空闲也可以检测出来:
// 检测UI线程空闲
const d1 = new Date();
requestAnimationFrame(()=>{
const offset = new Date() - d1;
if ( offset < 30 ) {
// UI线程空闲
}
})
阈值的确定也是统计的真实用户均值,最终确定为30ms。
有了这两项检测,我们就拿到了页面的“空闲时刻”。那如何“尽早的”拿到呢?做法相对简单,我们在页面加载完后用setInterval启动轮询,每隔1秒检测一次,一但检测到空闲,就进行资源预加载。
资源清单策略
接下来看第2点,如何在“浪费流量”与“用户体验”之间找到权衡。
考虑一个问题,如果只有5%的用户会从首页跳转页面B,那B的资源有必要预加载吗?答案显然是不需要。也就是说需要预加载的资源是要人工确定的,那个我们依据什么来确定资源清单呢?
有两条途径:
1.页面访问漏斗图。根据漏斗图我们能够得到每一次页面跳转的流失率,对于流失率较大的页面,我们就可以不去预加载了。多大的值算”较大“,这也是需要权衡的,比如我们认为60%就算流失较大了。一个典型的漏斗图如下:
2.根据统计指标动态调整。我们的资源预加载方案效果怎样,成本收益比怎样,是需要明确指标来衡量的。给指标造成负向作用的页面,就不去预加载它了。
那么,统计指标该如何设计呢?我们继续来讲。
指标设计
指标是为了用数据化的方式来评估和指导我们的工作、决策。既然是生产环境实践,就得有一套严谨的指标来衡量这个方案的优劣。
上文提到我们统计了用户执行setTimeout和requestAnimationFrame的延时均值,用于指导我们设定空闲检测阈值,就是一例应用。
对于整套方案,我们还设计了以下指标:
页面跳转时间
即用户跳转页面的耗时均值。这是我们要关注的核心指标,整个方案的目标就是降低页面跳转时间。由于是SPA应用,这个时间是比较容易统计到的。
触发率
即触发了资源预加载的比例,计算公式:触发率 = 资源预加载次数 / 页面加载次数。用来衡量我们的加载策略(空闲检测)是不是合理,理想情况下这个值应该接近100%,也就是说绝大多数的用户都预加载到了资源。如果发现触发率不及预期,那我们应该去调整加载策略。
命中率
即用户使用了预加载资源的比例,计算公式:使用了预加载的资源次数 / 资源预加载次数。这是用来衡量预加载资源的有效性的,比如一个资源的命中率是80%,说明80%的用户都使用到了预加载的资源,效果是非常好的。但实际的命中率往往达不到这么高,所以我们评估的标准是:与漏斗图的比例越接近,说明效果越好。
页面停留时间
即用户在一个页面的平均停留时间。这个指标也是为了指导加载策略而采集的,比如首页的平均停留时间是3秒,那么我们检查页面空闲的轮询间隔可以设为600ms一次,共检测5次。
除此之外,我们还采集了用户的网速情况,用以分析这个方案对不同网速段的用户的影响情况。
有了以上指标,我们就能够科学的制定策略了,比如某个资源的命中率低于10%,那说明是个低频页面,干脆从清单中剔除掉,不预加载了。
如何加载资源
整个思路已经清晰了起来,接下来到了加载资源环节。话说预加载资源的方式有很多种,我们选哪种呢?
不妨一一来看。
1.手写script标签
项目是用webpack打包的,通过manifest文件可以拿到资源清单,在需要预加载的时候手动创建一个script标签,用户真正跳转的时候就可以使用缓存中的资源。
这个方法的优点是侵入性较小,只是多了一次额外的资源加载,对原有代码改动很小。
但缺点也很明显:不好统计指标。要统计加载完成可以在每个script标签的onload事件里,还可以接受。但是统计命中率就没办法了,没法判断用户使用的是缓存中的资源还是新加载的。
2.浏览器prefetch
即使用<link rel="prefetch" href="xx.js">,这是浏览器提供的预加载资源方式,它会在浏览器空闲的时候自动给加载资源。
这个方式能力有点弱,因为没有js API供调用,对我们是黑盒的,没法进行相关的指标统计。
3.webpack提供的import()
webpack提供的动态加载资源的方法,虽然本质上也是写script标签,但webpack给做了很好的封装,我们通过.then()就可以知道资源加载完毕。而且资源被保存在内存中,一方面便于统计各种指标,另一方面连缓存都不必走了,速度更快。
综上,我们最终选择了方法3,能够满足我们的各项需求。
效果评估
以上就是整个方案的技术内容了,但事情到这里还不能结束。跟踪评估方案的效果,并持续优化,这才是生产环境实践的正确姿势。
我们关心的核心指标,页面跳转时间,有了50%以上的降低。这是预期之内的,之前页面跳转比如耗时200ms,命中预加载的资源后,可能一下就到了10ms以内了。这对大盘的影响是很显著的。
触发率这个指标,应该至少得在90%以上才算合格。都没触发预加载,后续还怎么谈命中。当然这个指标是可以通过调整加载策略来优化的,后面会讲。
至于命中率,就不太好说了。首先是上文提过的,它与用户行为相关,是个不稳定因素。其次,如果资源预加载触发的太晚,用户已经自己去跳转了,也会影响这个指标。所以整体从15%~35%不等吧。这个值是什么水平呢?合格还是不合格?我们通过翻阅业界也做预加载方案的资料,看到他们得到的命中率也大概在20%,所以可以认为我们的效果也算不错了。
持续优化
大家也看到了,我们的指标是存在优化空间的,方向就是:提高触发率、命中率,降低页面跳转时间。
上面也有谈到,我们为了制定出最精确的加载策略,采集了很多辅助指标。一个有意思的优化是,我们之前是进入页面1秒后开始检测空闲的,后来改成了立即进行检测。没想到这个改动给触发率带来了显著提升,原因是有部分用户在1秒内就跳到其他页面了,根本来不及触发预加载。
类似的优化还有,我们调整空闲检测的时间间隔,发现对触发率也有影响。
另外,我们也根据统计到的命中率,剔除了一些低频页面,避免这些资源拉低命中率。
总而言之,各项指标的参考值就是一个权衡的过程,需要根据自己的业务情况来商定。
更多思考
作为一个性能优化的方案,我们只是在特定项目架构、特定场景下做了一部分探索。事实上这个课题能做的还远不止此。
就比如资源加载策略,难道只能在页面空闲的时候加载吗?在PC时代,有些方案是根据鼠标的轨迹来猜测用户的下一步动作,进而决定预加载哪些资源。在移动端,我们是否可以根据滑动动作来做预测,又或者,是否可以把用户行为统计(业务埋点)作为参照,这都是可以探索的方向。
有朋友可能会问,SPA应用把首页不需要的资源给按需加载,结果你还用预加载又给加回来了,这不是多此一举吗?干脆打包成一个文件不就行了?听起来好像很有道理,不过细细思考一下,打包成一个文件和SPA+预加载,这两种的优劣各拉出清单对比一下,就知道还是有区别的。这就需要思辨能力了。
所以本文的目的就在于,描述一个技术方案的设计与落地过程,给大家提供技术上或是方法论上的参考。
web资源预加载-生产环境实践的更多相关文章
- 关于cocos2d-x 与 cocos2d-html5 资源预加载的思考
移动端资源预加载,可以做到需要加载的时候,从本地磁盘加载到内存,当纹理不需要的时候,都是强制清理内存里的纹理占用: cc.TextureCache.getInstance().removeAllTex ...
- 资源预加载 Preload
当提到前端性能优化时,我们首先会联想到文件的合并.压缩,文件缓存和开启服务器端的 gzip 压缩等,这使得页面加载更快,用户可以尽快使用我们的 Web 应用来达到他们的目标. 资源预加载 是另一个性能 ...
- HTML5的页面资源预加载技术(Link prefetch)加速页面加载
不管是浏览器的开发者还是普通web应用的开发者,他们都在做一个共同的努力:让Web浏览有更快的速度感觉.有很多已知的技术都可以让你的网站速度变得更快:使用CSS sprites,使用图片优化工具,使用 ...
- 页面资源预加载(Link prefetch)功能加速你的页面加载速度
有了浏览器缓存,为何还需要预加载? 用户可能是第一次访问网站,此时还无缓存 用户可能清空了缓存 缓存可能已经过期,资源将重新加载 用户访问的缓存文件可能不是最新的,需要重新加载 页面资源预加载/预读取 ...
- 使用HTML5的页面资源预加载(Link prefetch)功能加速你的页面加载速度
不管是浏览器的开发者还是普通web应用的开发者,他们都在做一个共同的努力:让Web浏览有更快的速度感觉.有很多已知的技术都可以让你的网站速度变得更快:使用CSS sprites,使用图片优化工具,使用 ...
- 资源预加载preload和资源预读取prefetch简明学习
前面的话 基于VUE的前端小站改造成SSR服务器端渲染后,HTML文档会自动使用preload和prefetch来预加载所需资源,本文将详细介绍preload和prefetch的使用 资源优先级 在介 ...
- DNS、链接网页、资源预加载处理
从网页性能的角度来看,DNS的解析时间是比较耗时的.因此如果能预先下载网页中用到的其它域的资源.可提前进行DNS解析: <link rel="dns-prefetch" hr ...
- preloadjs实现网页资源预加载
<!doctype html> <html lang="zh"> <head> <title>PreloadJS的基础使用</ ...
- 图片,音频资源预加载和文档dom加载
在项目中遇到一个问题,ajax请求音频资源,然后动态的插入到文档中,其中.原生的音频外观实在太丑了,而且每个浏览器的样式都不一样,采取了一个audio插件. 就遇到一个问题,请求后的直接调用插件的话, ...
随机推荐
- iOS之正则表达式(一)
这几天研究正则表达式发现正则表达式真是个好东西,可以在支付的时候轻松匹配交易数额,入门内容以及匹配如下: @interface ViewController () @property (weak, n ...
- 4412开发板QtE系统下MT6620-wifi的测试
基于iTOP4412系统烧写并启动之后,使用如下命令.wpa_passphrase XXX "YYY " >> /etc/wpa_supplicant.conf其中 X ...
- erp和crm的区别
CRM(Customer Relationship Management)即客户关系管理.从字面上来看,是指企业用CRM来管理与客户之间的关系.在不同场合下,CRM可能是一个管理学术语,可能是一个软件 ...
- JavaScript创建函数的方式
在JavaScript中,创建函数是比较常见的操作,但是JavaScript中怎么创建函数呢,有几种方式可以创建函数呢?在JavaScript一般有三种方式创建对象1.函数声明方式格式:functio ...
- C++ new和delete运算符简介
在C语言中,动态分配内存用 malloc() 函数,释放内存用 free() 函数.如下所示: ); //分配10个int型的内存空间 free(p); //释放内存 在C++中,这两个函数仍然可以使 ...
- Python基本了解
1. 计算机基础知识 CPU : 人类的大脑,运算处理问题 内存 : 临时储存数据,断点数据就会消失,存储数据快 硬盘 : 永久存储各种数据,相对于内存存储速度慢 操作系统 : 本质上是一个软件,用于 ...
- Factual question|具体特殊
Factual question:答案一定出于文章,不是总结 流程:找定位词做连连看,找对定位词,先看此句,然后看选项回文对照.如果找不到找上下两句话. 为了缩小范围,定位词是具体特殊,不要抽象词,可 ...
- MyBatis延迟加载及缓存
延迟加载 lazyLoadingEnabled 定义: MyBatis中的延迟加载也成为懒加载,就是在进行关联查询的时候按照设置延迟加载规则推迟对关联对象的select检索.延迟加载可以有效的减少数据 ...
- 89)PHP,跳转练习(1)
首先是需要两个简单的文件: 目录关系是: 我的beifen.php代码展示,这个先执行: <?php header('Location:b.php'); b.php代码展示: <?php ...
- windows下apache运行环境搭建
apache的安装 要求: 1,不要安装到有中文的目录中: 2,尽量将apache,php,mysql安装到一个总的目录,便于管理.(如都建立在amp目录下,然后在该目录下分别建立apache,php ...