昨天的一篇文章《经典面试题-Web前端性能优化方法之延迟加载》中,我们讲到的是延迟加载技术,既然有延迟加载,那么就有预加载,今天这篇文章讲述的是预加载如何实现Web性能优化。
loading
预加载
预加载技术主要是通过预测浏览器在未来某个时间需要用到的资源,提前进行加载,在特定的时间段内直接使用即可。作为开发者,肯定比普通用户更加了解网页资源的加载顺序,因此通过预加载技术来告知浏览器Web中需要用到的核心资源,可以提升网页的访问速度。
预加载技术包括很多个方面,包括DNS prefetching,Preconnect,Prefetch,Subresource,Prerender等,我们一一来看
DNS prefetching
DNS prefetching顾名思义,就是提前解析DNS。
开发人员可以通过代码告知浏览器尽早的解析特定DNS,当请求该域名下的文件时,就不用再解析DNS,而是可以直接使用该DNS下的文件了。这中方法在使用第三方库时显得尤为有效。
使用方法是,在<head>标签中添加如下一段代码
DNS预加载
Preconnect
Preconnect类似于DNS prefetching,它会预先建立TCP握手连接,在需要的时候还会建立TLS连接。
在现代浏览器中,会尽可能的预测网站所需要的连接,通过提前连接,可以消除在真实请求时的DNS解析,TCP连接的时间。
开发者可以通过代码去告知浏览器哪些资源是可以提前连接的,代码段如下
Preconnect提前连接
Prefetch
Prefetch是告知浏览器在未来某个时候需要用到的资源,可以提前请求并缓存下来,等到真正需要用的时候,可以直接从缓存中获取。Prefetch可以预拉取图片,css,js等资源文件。
通过以下代码我们可以实现一张图片的预拉取并缓存起来。
Prefetch预拉取
当然,浏览器并不是在任何情况下都会去执行预拉取操作,比如在网络情况不好的情况下不会请求较大的资源,Firefox只会在空闲时去执行Prefetch操作。
Subresource
Subresource是用来指定加载资源的高优先级,使用的方法如下。
Subresource提前加载
Subresource与Prefetch不同,Prefetch指定是未来的页面需要用到的低优先级的资源,而Subresource指定的是当前页面需要用到的高优先级的资源。因此,在指定当前页面中需要用到的资源时,推荐用Subresource,而在后续页面中用到的资源提前加载推荐用Prefetch。
Prerender
Prerender可以让浏览器提前加载指定页面上的所有资源。
Prerender我们可以这样理解:浏览器实际上会在后台开启一个隐藏的标签页,在这个标签页中会下载页面上需要的所有资源,如果用户进入了指定的链接,则这个隐藏的页面会立马显示出来,而用户是无法辨别这个页面是之前的隐藏页面还是重新开启的页面。
Prerender预加载
并不是所有的页面都适合Prerender特性,如果用户最终未能进入指定的链接的页面,那么提前加载的很多资源文件会浪费很多的带宽。
我们可以分析一下一些比较适合使用Prerender特性的页面:
一个具有分页展示的图片集或者文字集,在我们点击下一页的时候,可以将下一页的内容使用Prerender属性预加载
注册与登录页面,新用户注册完成之后一般会选择去登录,登录页面完成之后会跳转到主页面,这两个过程就比较适合Prerender属性的预加载
总结
今天这篇文章主要将的是预加载的Web性能优化方式,后面会持续更新其他Web性能优化的文章。
试经:懒加载和和预加载。难度:
什么是懒加载和预加载?请说下什么是懒加载和预加载?
懒加载和预加载其实都是一种页面的性能优化,都是为了提高网站和应用程序的性能,减少页面的加载时间。
其中来说懒加载一般是指在页面滚动时按需加载页面上的图片、视频或者是其他资源。当页面加载时只会加载页面的初始内容而不会加载页面上的所有资源。所以当用户去滚动页面的时候再依据需要去加载对应的资源。这样就可以减少页面的加载时间,提高用户体验。
预加载是指在页面加载时提前加载页面上的资源,以便在用户需要时可立即访问。
那么预加载是通过页面中的头部标签去使用外部<link>标签,外链样式以及<script>外部js方式去实现的。可以使用<link>标签来预加载CSS文件,使用<script>标签来预载图片或者是其他资源。
这样来说就可以避免在用户需要的时候再去请求资源,提高响应速度和用户体验。
总体来说懒加载和预加载都是优化网站的有效手段,所以我们基本上来说可以根据具体的场景和需要去选择合适的方式。
一般来说懒加载需要去加载大量资源的页面,就适用于需要去提高响应速度的关键资源。我是旭旭,助你面试成功!关注我,助你成功!
作为直接面向用户的前端开发人员,我们都知道页面首屏打开速度的重要性,直接关系到用户的留存。
提升 H5 页面打开速度的方法也有很多,有从网络优化入手的 CDN、gzip压缩、Keep-Alive等,有从加载优化入手的资源大小优化、懒加载、按需加载、代码拆分、Tree Shaking等,以及页面渲染时的一些优化。 在我们的项目中,上述优化基本都有使用,当优化达到一定程度,想要继续仅基于 H5 自身优化已难以再提升,就需要和客户端合作,提升 H5 页面的打开速度,比如离线缓存、预加载等。
这里介绍一个我们使用的接口预请求的方案,它可以相对低成本的提升 FMP (首次有效绘制) 的渲染速度,而且从我们的上线效果上来看提升非常明显。
当我们打开在 APP 内的 H5 页面时,一般会经过原生切页面动画、创建新页面、加载 webview、加载 HTML、请求静态资源并解析、获取首屏数据、渲染内容、下载图片等步骤。 其实在加载的同时,可以利用 APP 在帮助 H5 获取首屏数据,这样在 H5 页面加载完成后,可以直接使用 APP 已请求的首屏数据进行渲染,这样就节省了获取首屏数据的时间。首屏数据接口越慢,该方案的价值就越大。
大体如下图所示:
在做 H5 秒开优化前,我们也讨论过 SSR(服务端渲染) 的方案。 基于前后端分离的服务端渲染,一般由前端团队使用 nodeJS 在服务器进行页面渲染,前端服务器会组装一个携带了具体数据的HTML文本,并且返回给浏览器。 服务端渲染虽然可以减少白屏时间,但相较于接口预请求有如下问题:
既然我们要利用 APP 来帮助 H5 获取首屏数据,而我们的 H5 页面也不止一个,并且每个页面的参数都不一样。 那第一个要解决的问题就是怎么告诉 APP 在打开那些页面时需要同步请求那些接口,这些接口的入参是什么样的?
当 APP 请求完成后,要解决的第二个问题是怎么把请求结果及时通知给 H5 页面,并且防止 H5 页面自身发起重复请求,增加服务端负担?
第三个要解决的是如果 H5 修改了代码,更新了参数,而告知 APP 入参的配置还未更新,怎么避免使用旧配置请求的结果?
我们先带着问题看下整个方案的流程图:
我们在每个 H5 项目先新增的另一个叫 app.config.json 的配置文件,配置了该页面需要请求的主接口(可以是多个),请求方法、header、body、url 参数等。
单个页面配置:
json复制代码{
pageName: '页面名称',
key: 'H5页面的URL', //页面唯一路径,作为key
preRequest: { // 接口预请求配置
appVersion: '8.0.1', // 支持app最低版本号,为了处理APP识别字段新增等变更情况
support: 0, // 是否支持接口预请求,0 | 1
apis:[ // 预请求接口信息,数组
{
fragment: "#/detail", // 新增fragment 指定对应的路由(hash模式)页面
url: 'https://xxxx/api/address', // 请求url
method: 'get', // 请求类型,如get|post,不传默认get
header: {}, // 请求头,规则同请求体
body:{ // 请求体
shopId: "getUserInfo.shopId:String|''",
cityId:'getLocation.cityId:Int',
key5:'params.orderId',
key6:'params.xxx'
}
}
]
}
}
如上配置,页面 URL、接口地址等都好理解。 在打开一个 H5 时,接口的入参一般来自两个地方,最常见的是通过 URL 来获取,另一种常见情况是通过 JSBridge 在 APP 内获取,比如全局的用户信息等。 这里我们通过不同的字符串告知 APP 需要从哪里获取那些参数,比如 params.orderId 是告知 APP 需要从打开 H5 页面的URL 中获取 orderId 的参数,而 getUserInfo.shopId,则是告知 APP 需要通过 JSBridge 中的 getUserInfo 方法获取 shopId 参数。 这里APP并非真的去自己调用 JSBridge 的方法,而是告知 APP 该值需要和 JSBridge 中的方法达到同样的效果。
也可以传递固定值,比如接口请求中入参 type 是固定值 1,只需要和 APP 商议好规范,读取识别即可。 以上我们解决了 APP 请求那些接口以及参数的问题。
我们再 H5 打包构建时对 app.config.json 进行处理,在增加些其他必备配置后,提交到 H5配置平台,这时可以选择在打开 APP 时调配置平台的接口,但每次都读取数据库中的配置,会给服务端带来极大的压力。
这里我们做了一个小小的优化,来很好的规避了这个问题。 我们再构建完成,发布上线后,在 H5配置平台 打开预请求的开关时,由配置平台读取所有配置的页面,生成一个配置的 JSON 文件,文件名类似 h5config.xxx.json,xxx 代表版本号,并把配置文件上传到 CDN。 用户打开 APP 时,通过接口下发最新配置文件的 地址和版本,APP 本地和缓存的文件对比,判断是否要下载最新的配置文件。
这样改造后,只需要在点击发布的那一刻,读库生成新配置文件,大量的 C 端用户访问时,只需要调一个简单的接口获取最新配置版本和地址即可。
最终的配置文件类似这样:
json复制代码{
version: "0.0.1",
global:{
// 全局配置
},
pages: [
{
key: "https://xxx.com/pages/xxx.html",
pageName: "XXX活动页",
openImageCache: 0, // 是否开启图片缓存,0关闭,1开启
openPreRequest: 1, // 是否开启接口预请求,0关闭,1开启
config: {
// 上面的单个页面配置生成而来
}
}
]
}
APP 每次打开 H5 页面,都从配置中排查,是否有配置的页面 URL,如果有并且打开了接口预请求,则在打开 H5 页面的同时基于配置发起接口请求。
至此就解决了我们的第一个问题。
最初我们想到的是通过 JSBridge 去轮循获取 APP 中请求的数据,但这种方式显然不够优雅。 后来通过 APP 在获取到结果,并且 webview 触发“onLoad ”事件后,把数据注入到 window 对象下。
如图所示,正常情况下,存在两种情况:
不管是哪种情况,我们都可以通过监听 注入的 window 对象,来获取数据,当第一次没有读取到时,会立即发起 H5 本身的请求,这样即使接口预请求异常也不影响 H5 本身。 对于 window 对象我们通过 Object.defineProperty 进行监听,APP 和 H5 自身的请求谁先返回就用谁。
这样就解决了第二个问题,既能提升又完全不影响 H5 自身业务。
上文我们提到,因为 H5 可以随时发版,发版后,如果没有及时更新配置文件,或者更新了但 APP 有缓存,没有立即使用最新的配置文件。 此时如果 H5 本身的参数变化了,APP 还是按旧配置获取请求,得到的数据并非新版本的数据时如何处理了?
还记得前面提到注入的 window.request_cache_xxx 对象吗,其中 xxx 是配置中 url、method、header、body、params(params 是指参数挂载在 URL 上)这些字符串的 md5 值,每个H5 项目每次更新配置都有一个唯一的 md5 值。其中 H5 本地保留一份,另一份通过配置文件下发给APP,如果两边的 md5 值匹配不上,则 H5 不会使用 APP 注入的数据。
这里其实还有另一个风险。 如果 H5 开发者在代码中的参数和配置文件不同步,比如迭代时间长后,忘记需要更新配置文件了。那么也就出现悲剧。 作为技术方案设计者,我们不能依赖人员的谨慎性,而应该靠系统设计来规避这些问题。
能发生的事情终将会发生
在这里我们通过修改 H5 项目的 HTTP 公共库,使其在开发和测试环境都是通过读取 app.config.json 中配置的接口、参数等来发起请求,这样就做到开发调试和线上一致。
以上我们就解决了 APP 请求数据的正确性问题。
通过以上方案,我们基本上没有借助其他部门的力量,单单依靠大前端组内的资源,就极大解决了 H5 秒开的问题。 通过我们的性能监控的数据,在开启 H5 秒开后包含接口预请求、DNS 预请求(通过 APP 对 H5 使用到的域名进行 DNS 缓存)策略后,所有 H5 项目,FMP 达到 1.2 秒的超过 80%。
PS:FMP 我们取的是从用户在 APP 点击 H5 链接到主内容渲染完成的时间
绝大部分页面有 200~600ms 的提升,主要使用的 CMS 活动页,安卓提升了 27%,iOS 提升了 43%,平均 FMP 在 900ms 左右。
视频对比
作者:思考的Joey 链接:https://juejin.cn/post/7339846378125033523
*请认真填写需求信息,我们会在24小时内与您取得联系。