前端性能优化--容器篇
更新日期:
前面我们讲了很多前端应用内部的性能优化,实际上除了前端自身,我们还可结合容纳 Web 页面本身的客户端一起做优化。
首先,本文中提到的容器,基本上都是指 Web 页面的宿主,比如浏览器、APP 客户端、小程序,它们提供了 WebView 环境来运行 Web 应用。
容器性能优化
由于 Web 应用本身只运行在 WebView 中,而 WebView 的能力又依赖于宿主容器,因此 Web 应用本身很多能力都比较局限。如果宿主容器能配合一起做一些优化,效果要远胜于我们自身做的很多优化效果。
从性能优化的角度来说,宿主容器主要能提供的能力包括:
- 加速页面打开
- 加速页面切换
加速页面打开
对前端项目来说,我们常常会对首屏打开做很多的优化,包括尽量减少首屏需要的代码、对首屏渲染的内容进行分片等等(参考《前端性能优化–归纳篇》)。
即使前端本身优化到极致,对于资源获取、请求数据等这些耗时占比较大的部分,还是存在的。但是如果容器能提供类似的能力,我们就可以将这部分的耗时做优化了,比如:
- 提前下载并缓存 Web 相关资源,页面打开时直接获取缓存,比如 HTML/JavaScript/CSS
- 提前获取和缓存页面渲染相关的请求资源,页面请求时直接返回,或是直接从缓存中获取
- 提前启动 WebView 页面,并加载基础资源
资源准备
我们可以在客户端即将打开某个 WebView 页面之前,提前将该页面资源下载下来,由此加快 WebView 页面加载的速度。
由于资源请求本身也会消耗一定的资源,一般来说会在比较明确使用的场景下才会使用。也就是说用户很可能会点进去该 WebView 页面,基于这样的前提来做资源准备,比如列表页进入详情页,比如底部 TAB 进入的页面等等。
这些提前下载并临时缓存的资源,可以包括:
- 页面加载资源,包括 HTML/CSS/JavaScript 等
- 首屏页面内容的请求数据,比如分片数据的首片数据等
资源预下载要做的时候相对简单,需要注意的是下载后的资源的管理问题,在使用完毕或是不需要的情况下需要及时的清理,如果过多的缓存会占用用户机器的资源。
其实除了依赖客户端,前端本身也有相关的技术方案,比如说可以使用 PWA 提前请求和缓存页面需要的资源。
预加载
在需要的资源已经准备好的前提下,容器还可以提供预加载的能力,包括:
- 容器预热:提前准备好 WebView 资源
- 资源加载:将已下载的 Web 资源进行加载,比如基础的 HTML/CSS/JavaScript 等资源
举个例子,小程序中也有对资源预加载做处理。在小程序启动时,微信会为小程序展示一个固定的启动界面,界面内包含小程序的图标、名称和加载提示图标。此时,微信会在背后完成几项工作:下载小程序代码包、加载小程序代码包、初始化小程序首页。
小程序的启动过程也分了两个步骤:
- 页面预渲染。这是准备 WebView 页面的过程,由于小程序里是双线程的设计,因此渲染层和逻辑层都会分别进行初始化以及公共库的注入。逻辑层和渲染层是并行进行的,并不会相互依赖和阻塞。
- 小程序启动。当用户打开小程序后,小程序开始下载业务代码,同时会在本地创建基础 UI(内置组件)。准备完成后,就会开始注入业务代码,启动运行业务逻辑。
显然,小程序基础库和环境初始化相关的资源,都被提前内置在 APP 中了,并提前准备好相关的资源,使得用户打开小程序的时候,可以快速地加载页面。除此之外,小程序还提供了预加载的能力,业务方只需要配置提前拉取的资源,微信则可以在启动的过程中,提前将相关的资源拉取回来。
很多宿主预加载的方案也类似,比如对 WebView 页面做前置的资源下载和加载,当用户点击时尽快地给到用户体验。
加速页面切换
除了首次打开页面的加速,在页面切换时我们也可以做很多提速的事情。
容器预热
前面讲到,在打开小程序前,其实微信已经提前准备好了一个 WebView 层,由此减少小程序的加载耗时。
而当这个预备的 WebView 层被使用之后,一个新的 WebView 层同样地会被提前准备好。这样当开发者跳转到新页面时,就可以快速渲染页面了。这个过程也可以理解为容器的前置预热。
在这个例子中,小程序针对不同的页面使用了不同的 WebView 进行渲染,因此不管是首次打开,还是跳转/切换新页面,都会准备多一个 WebView 用来快速加载。
但多准备一个 WebView 本身也是对客户端的一种资源消耗,所以其实我们还可以考虑另外一种方案:容器切换。
容器切换
容器切换方案指当页面切换时复用同一个 WebView 资源,可以理解为前端单应用类似的方式在 APP 中做资源切换。
由于需要复用同一个 WebView,因此该方案对资源的管理要求较高,包括:
- 对页面应用的生命周期管理完善,自顶向下实现初始化、更新和销毁的能力
- 页面切换时,需要及时清理原有逻辑和资源,比如定时器、页面遗留的 UI 和事件监听等
- 资源占用、内存泄露等问题,会随着 WebView 复用次数而积累
要达到不同页面和前端应用之间的资源复用,要求比直接准备一个新的 WebView 容器要高很多。即使是不同的页面,也需要有统一的生命周期管理,约定好页面的一些销毁行为,并能执行到每个模块和组件中。
但如果项目架构和设计做得好,效果要远胜于容器预热,因为在进行页面切换的时候,很多资源可以直接复用,比如:
- 通用的框架库,比如使用了 Vue/React 等前端框架、Antd 等组件库,就可以免去获取和加载这些资源的耗时
- 公共库的复用,项目中自行封装的一些工具库,也可以直接复用
- 模块复用,通用的模块比如顶部栏、底部栏、工具栏、菜单栏等功能,可以在页面切换时选择性保留,直接省略这部分模块的加载和页面渲染
看到这里或许有些人会疑惑,如果是这样的话为什么不直接用单页面呢?要知道我们讨论的场景是客户端打开的场景,也就是说 WebView 页面的退出,大多数情况下是会先回到 APP 原生页面中。当用户进入到另外一个 WebView 页面时,才会重新打开 WebView,此时才考虑是用新预热的 WebView,还是直接复用刚才的 WebView。
总的来说,容器切换是一个设计要求高、副作用强、但优化效果好的方案。
客户端直出渲染
在有容器提供资源的基础上,我们还可以在 WebView 页面关闭前,对当前页面做截屏或是 HTML 保存处理。
在下一次用户进入到相同的页面中时,可以先使用上一次浏览的图片或是页面片段先预览,当页面加载完成后,再将预览部分移除。这种预加载(预览)的方案,由于是客户端提供的直出渲染能力,因此也被称为客户端直出渲染。
当然,相对于在页面关闭前保存,其实也可以直接实现直出渲染的能力,这样不管是否已经打开过某个页面,都可以通过容器预热时提前计算出直出渲染的内容,当页面打开时直接进行渲染。
这种方案有一个比较麻烦的地方:当缓存的页面内容发生变化时,需要及时更新直出渲染的内容。
因此,及时用户并不在页面内,也需要定期去获取最新的资源,并生成直出渲染的内容。当需要预渲染的页面多了,维护这些页面的实时性也需要消耗不少的资源,因此更适用于维护成本较低的页面。
结束语
其实,容器的作用不只是加速页面打开速度,由于结合了原生 APP 的能力,我们甚至可以给 WebView 提供完整的离线加载能力。比如在网络离线的情况下,通过提前将资源下载并缓存,用户依然可以正常访问 APP 里的页面。
当然,每一项技术方案都是有利有弊,容器提供了更优的能力,也需要消耗一定的资源,我们可以结合自己项目本身的情况来做取舍。
码生艰难,写文不易,给我家猪囤点猫粮了喵~
查看Github有更多内容噢:https://github.com/godbasin
更欢迎来被删的前端游乐场边撸猫边学前端噢
如果你想要关注日常生活中的我,欢迎关注“牧羊的猪”公众号噢