热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

【性能优化】quicklink:实现原理与给前台的启发

近来,GoogleChromeLabs推出了quicklink,用以实现链接资源的预加载(prefetch)。本文在详情其实现思路的基础上,会进一步讨论在预加载方面前台工程师还可以

近来,GoogleChromeLabs 推出了 quicklink,用以实现链接资源的预加载(prefetch)。本文在详情其实现思路的基础上,会进一步讨论在预加载方面前台工程师还可以做什么。

1. quicklink 是什么的?

quicklink 是一个通过预加载资源来提升后续方案速度的轻量级工具库。旨在提升浏览过程中,客户访问后续页面时的加载速度。

当我们提到性能优化,往往都会着眼于对当前客户访问的这个页面,如何通过压缩资源大小、删减不必要资源、加快页面解析渲染等方式提升客户的访问速度;而 quicklink 用了另一种思路:我预先帮你加载(获取)你接下来最可能要用的资源,这样之后的真正使用到该资源(链接)时就会感觉非常顺畅。

照着这个思路,我们需要处理的问题就是如何预先帮客户加载资源呢?这里其实涉及到两个问题:

  • 如何去预加载一个指定资源?(预加载的方式)
  • 如何确定某个资源能否要加载?(预加载的策略)

下面就结合 quicklink 源码来看看如何处理这两个问题。

注:下文提到的“预加载”/“预获取”均指 prefetch

2. quicklink 实现原理

2.1. 如何去预加载一个指定资源?

首先要处理的是,通过什么方式来实现资源的预加载。即预加载的方式。

我们这里的预加载对应的英文是 prefetch。提到 prefetch 自然会想到使用浏览器的 Resource Hints,通过提醒浏览器做少量“预操作”(例如 DNS 解析、资源下载等)来加快后续的访问。

假如对 prefetch 与 Resource Hints 不熟习,可以看看这篇《使用Resource Hint提升页面加载性能与体验》。

只要要下面这样一行代码即可以实现浏览器的资源预加载。是不是非常美妙?

因而,要预加载一个资源可以通过下面四行代码:

const link = document.createElement(`link`);link.rel = `prefetch`;link.href = url;document.head.appendChild(link);

然而,我们不得不面对兼容性的问题,在低版本 IE 与手机端是重灾区。

image

美梦破灭。既然如此,我们就需要一个相似 prefetch shim 的方式:在不支持 Resource Hints 的浏览器中,使用其余方式来预加载资源。对此,我们可以利用浏览器自身的缓存策略,“实实在在”预先请求这个资源,这也形成了一种资源的“预获取”。而这最方便的就是通过 XHR:

const req = new XMLHttpRequest();req.open(`GET`, url, req.withCredentials=true);req.send();

这样 shim 也完成了。最后,如何检测浏览器能否支持 prefetch 呢?

我们可以通过 link 元素上 relList 属性的 support 方法来检查对 prefetch 的支持情况:

const link = document.createElement('link');link.relList || {}).supports && link.relList.supports('prefetch');

结合这三个段代码,就形成了一个简易的 prefetcher:判断能否支持 Resource Hints 中的 prefetch,支持则使用它,否则回退使用 XHR 加载

值得一提的是,使用 Resource Hints 与使用 XHR 来预加载资源还是有少量重要差异的。草案中也提到了少量(主要是与性能以及与浏览器其余行为之间的冲突)。其中还有一点就是,Resource Hints 中的 prefetch 能否执行,完全是由浏览器决定的,草案里有句话非常显著 —— the user agent SHOULD fetch。因而,所有 prefetch 的资源并不肯定会真正被 prefetch。相较之下,XHR 的方式“成功率”则更高。这点在 Netflix 实施的性能优化案例中也提到了。

image

题外话:quicklink 中使用 fetch API 实现高优先级资源的加载。这是由于浏览器中会为所有的请求都设置一个优先级,高优请求会被优先执行;目前,fetch 在 Chrome 中属于高优先级,在 Safari 中属于中等优先级。

2.2. 如何确定某个资源能否要预加载?

有了资源预加载的方式,那么接下来就需要一个预加载的策略了。

这其实是个见仁见智的问题。例如直接给你一个链接 https://my.test.com/somelink,在没有任何背景信息的情况下,恐怕你完全不知道能否需要预加载它。那对于这个问题,quicklink 是怎样处理的呢?或者者说,quicklink 是通过什么策略来进行预加载的呢?

quicklink 用了一个比较直观的策略:只对处于视口内的资源进行预加载。这一点也比较好了解,网络上大多的资源加载、页面跳转都伴随着客户点击这类行为,而它要是不在你的视野内,你也就无从点击了。这肯定程度上算是个必要条件。

这么一来,我们所要处理的问题就是,假如判断一个链接能否处于可视区域内?

以前,对于这种问题,我们做的就是监听 scroll 事件,而后判断某元素的位置,从而来“得知”元素能否进入了视区。传统的图片懒加载库 lazysize 等也是用这种策略。

document.addEventListener('scroll', function () { // ……判断元素位置});

注:目前 lazysize 也有了基于 IntersectionObserver 的实现

当然,需要特别注意滚动监听的性能,例如使用截流、避免强制同步布局、 passive: true 等方式缓解性能问题。

不过现在我们有了一个新的方式来实现这一功能 —— IntersectionObserver

const observer = new IntersectionObserver(entries => { entries.forEach(entry => { if (entry.isIntersecting) { const link = entry.target; // 预加载链接 } });});// 对所有 a 标签增加观察者Array.from(options.el.querySelectorAll('a'), link => { observer.observe(link);});

IntersectionObserver 会创立一个观察者,专门用来观察与通知元素进出视口的情况。如上述代码所示,IntersectionObserver 可以观察所有 a 元素的位置情况(主要是进入视野)。

IntersectionObserver 不理解的同学可以参考 Google 的 IntersectionObserver 详情文章。

但是如下图所示, IntersectionObserver 存在兼容性问题,因而要在不兼容的浏览器中使用 quicklink,会需要一个 polyfill。

image

目前,我们已经把 quicklink 的两大部分(预加载的方式和预加载的策略)的原理和简单实现讲完了。整个 quicklink 非常简洁,这些基本就是 quicklink 的核心。剩下的就是少量参数检查、额外的规则特性等。

题外话:为了进一步保证性能,quicklink 使用 requestIdleCallback 在空闲时间查询页面 a 标签并挂载观察者。对 requestIdleCallback 不理解的同学可以看看 Google 的这篇文章。

3. 到此为止?不,我们还能做更多

到这里,quicklink 的实现就基本讲完了。仔细回想一下,quicklink 其实提供了我们一种通过“预加载”来实现性能优化的思路(粗略来说像是用流量换体验)。这种方式我在前面也提到了,其实可以分为两个部分:

  • 如何去预加载一个指定资源?(预加载的方式)
  • 如何确定某个资源能否要加载?(预加载的策略)

其实两部分似乎都有可以作为的地方。例如如何保证 prefetcher(资源预加载器)的成功率能更高,以及目前使用的回退方案 XHR 其实在预加载无法缓存的资源时所受的限制等。

此外,我们在这里还可以来聊一聊策略这块。

因为 quicklink 是一个业务无关的轻量级功能库,所以它采用了一个简单但肯定程度上有效的策略:预加载视野内的链接资源。然而在实际生产中,我们面对的是更复杂的环境,更复杂的业务,反而会需要更精准的预加载判断。因而,我们完全可以从 quicklink 中剥离出 prefetcher 来作为一个预加载器;而在策略部分使用自己的实现,例如:

  • 结合访问日志、打点记录的更精准的预加载。例如,我们可以通过访问日志、打点记录,根据 refer 来判断,从 A 页面来的 B、C、D 页面的比例,从而设置一个阈值,超过该阈值则认为访问 A 页面的客户接下来更容易访问它,从而对其预加载。

  • 结合客户行为数据来进行个性化的预加载。例如我们有一个阅读类或者商品展现类站点,从客户行为发现,当该链接暴露在该客户视野内 XX 秒(客户阅读内容 XX 秒)后点击率达到 XX%。而不是简单的一刀切或者进入视野就预加载。

  • 后置非必要资源,精简某类落地页。落地页就是要让新客户尽快“落地”,为此我们可以像 Netflix 详情的那样,在宣贯页/登录页精简加载内容,而预加载后续主站的主包(主资源)。例如有些站点的首页大多偏静态,可以用原生 Javascript 加 内联关键 CSS 的方式,加快加载,客户访问后再预加载 React、Vue 等一系列主站资源。

  • 等等。

上面这些场景只是抛砖引玉,相信大家还会有更多更好的场景可以来助力我们的前台应用“起飞”。此外,我们完全可以借助少量构建工具、数据采集与分析平台来实现策略的自动提取与注入,优化整个预加载的流程。

写在最后

预加载、Resource Hints等由来已久。quicklink 通过提出了一种可行的方案让它又进入了大家的视野,给我们展示了性能优化的另一面。希望大家通过理解 quicklink 的实现,也能有自己的想法与启发。

相信随着浏览器的不断进化,标准的不断前行,前台工程师对极致体验与性能要求的不断提高,我们的产品将会越来越好。


推荐阅读
  • Java太阳系小游戏分析和源码详解
    本文介绍了一个基于Java的太阳系小游戏的分析和源码详解。通过对面向对象的知识的学习和实践,作者实现了太阳系各行星绕太阳转的效果。文章详细介绍了游戏的设计思路和源码结构,包括工具类、常量、图片加载、面板等。通过这个小游戏的制作,读者可以巩固和应用所学的知识,如类的继承、方法的重载与重写、多态和封装等。 ... [详细]
  • 本文详细介绍了GetModuleFileName函数的用法,该函数可以用于获取当前模块所在的路径,方便进行文件操作和读取配置信息。文章通过示例代码和详细的解释,帮助读者理解和使用该函数。同时,还提供了相关的API函数声明和说明。 ... [详细]
  • 在Android开发中,使用Picasso库可以实现对网络图片的等比例缩放。本文介绍了使用Picasso库进行图片缩放的方法,并提供了具体的代码实现。通过获取图片的宽高,计算目标宽度和高度,并创建新图实现等比例缩放。 ... [详细]
  • PHP图片截取方法及应用实例
    本文介绍了使用PHP动态切割JPEG图片的方法,并提供了应用实例,包括截取视频图、提取文章内容中的图片地址、裁切图片等问题。详细介绍了相关的PHP函数和参数的使用,以及图片切割的具体步骤。同时,还提供了一些注意事项和优化建议。通过本文的学习,读者可以掌握PHP图片截取的技巧,实现自己的需求。 ... [详细]
  • android listview OnItemClickListener失效原因
    最近在做listview时发现OnItemClickListener失效的问题,经过查找发现是因为button的原因。不仅listitem中存在button会影响OnItemClickListener事件的失效,还会导致单击后listview每个item的背景改变,使得item中的所有有关焦点的事件都失效。本文给出了一个范例来说明这种情况,并提供了解决方法。 ... [详细]
  • 一句话解决高并发的核心原则
    本文介绍了解决高并发的核心原则,即将用户访问请求尽量往前推,避免访问CDN、静态服务器、动态服务器、数据库和存储,从而实现高性能、高并发、高可扩展的网站架构。同时提到了Google的成功案例,以及适用于千万级别PV站和亿级PV网站的架构层次。 ... [详细]
  • Android自定义控件绘图篇之Paint函数大汇总
    本文介绍了Android自定义控件绘图篇中的Paint函数大汇总,包括重置画笔、设置颜色、设置透明度、设置样式、设置宽度、设置抗锯齿等功能。通过学习这些函数,可以更好地掌握Paint的用法。 ... [详细]
  • 本文介绍了在Android开发中使用软引用和弱引用的应用。如果一个对象只具有软引用,那么只有在内存不够的情况下才会被回收,可以用来实现内存敏感的高速缓存;而如果一个对象只具有弱引用,不管内存是否足够,都会被垃圾回收器回收。软引用和弱引用还可以与引用队列联合使用,当被引用的对象被回收时,会将引用加入到关联的引用队列中。软引用和弱引用的根本区别在于生命周期的长短,弱引用的对象可能随时被回收,而软引用的对象只有在内存不够时才会被回收。 ... [详细]
  • sklearn数据集库中的常用数据集类型介绍
    本文介绍了sklearn数据集库中常用的数据集类型,包括玩具数据集和样本生成器。其中详细介绍了波士顿房价数据集,包含了波士顿506处房屋的13种不同特征以及房屋价格,适用于回归任务。 ... [详细]
  • 本文介绍了深入浅出Linux设备驱动编程的重要性,以及两种加载和删除Linux内核模块的方法。通过一个内核模块的例子,展示了模块的编译和加载过程,并讨论了模块对内核大小的控制。深入理解Linux设备驱动编程对于开发者来说非常重要。 ... [详细]
  • 上图是InnoDB存储引擎的结构。1、缓冲池InnoDB存储引擎是基于磁盘存储的,并将其中的记录按照页的方式进行管理。因此可以看作是基于磁盘的数据库系统。在数据库系统中,由于CPU速度 ... [详细]
  • 本文介绍了H5游戏性能优化和调试技巧,包括从问题表象出发进行优化、排除外部问题导致的卡顿、帧率设定、减少drawcall的方法、UI优化和图集渲染等八个理念。对于游戏程序员来说,解决游戏性能问题是一个关键的任务,本文提供了一些有用的参考价值。摘要长度为183字。 ... [详细]
  • LVS实现负载均衡的原理LVS负载均衡负载均衡集群是LoadBalance集群。是一种将网络上的访问流量分布于各个节点,以降低服务器压力,更好的向客户端 ... [详细]
  • 场景1.IE,Firefox浏览器访问不了网站,谷歌浏览器可以,返回错误码DNS_PROBE_POSSIBLE.2.pingwww.qq.com可以ping通,ping局域 ... [详细]
  • 三、查看Linux版本查看系统版本信息的命令:lsb_release-a[root@localhost~]#lsb_release-aLSBVersion::co ... [详细]
author-avatar
ym_泳梅
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有