关于javascript:性能优化实战宝宝知道小程序FMP优化实录

54次阅读

共计 3868 个字符,预计需要花费 10 分钟才能阅读完成。

背景

宝宝晓得小程序从首次公布至今,通过了几十个版本的迭代。随着业务倒退,页面性能内容的一直增多,相干性能数据一直变差,外围性能数据 FMP 长期处在 2000ms 以上。

在该我的项目之前,咱们团队也对小程序做了肯定的性能调优工作,内容包含:

  1. 包体积优化,去除了不少援用在我的项目中的图片素材文件,将包体积优化至 500kb 以下;
  2. 联结后端对耗时较高的业务接口做优化,单个接口返回速度须要管制在 100ms 左右;
  3. 优化了局部业务逻辑,小程序启动时缩小了一些不必要的操作逻辑;
  4. 应用了小程序框架提供的最新生命周期 onInit,可提前 100ms 左右发动业务网络申请;
  5. 应用 prelink 预连贯网络,晋升数据接口的申请效率。

通过上述伎俩之后,FMP 降到了 1900ms 左右,后续再也无奈产生优化成果。

以上优化伎俩,根本排除了网络连接,包体积优化不到位引起的性能不佳。那么咱们就只有一个问题须要认真排查 —— 内容的渲染效率。

问题发现

目前从手百上关上宝宝晓得小程序的最大入口页面为问答页,整体 pv 占比超过 6 成,那么咱们优先优化这个页面,便能够带来性能收益的最大化。

通读问答页代码,按显示程序从上到下,整个页面的性能点顺次为:

  1. 直播信息横条
  2. 问题区
  3. 答复区
  4. 广告组件区
  5. 为你举荐 feedlist

须要展示的内容类别很多,内容信息量较为宏大。局部内容须要独自接口获取,外加上引入的广告组件,展示效率齐全无奈优化。

因为以上业务内容的展示须要,在加载时,应用 setData 触发内容渲染,会造成较大问题,比方:

  1. 加载期间调用 setData 的频次过多,onLoad 时会 set、onShow 时会 set,不同阶段发动的异步数据加载后也会 set。以后线程内同时的屡次 setData,极易造成小程序渲染线程拥塞,影响内容渲染效率
  2. 单次 setData 数据量过多,接口数据返回后,所有页面内须要的数据都一次性被提交到渲染线程中渲染,导致线程等待时间长,影响了无效内容的最终展示。尽管缩小 setData 调用次数是官网提倡的,然而单次提交过多数据渲染,也并不是最优的策略。

以上两条 setData 的应用问题,在配置较好的手机设施上,并不会体现出问题,然而对于中低配置的手机设施,因为操作拥塞或大量数据渲染操作带来的渲染提早,造成的用户体验损失还是很大的。

优化前的问答页数据渲染示意图

优化之前,页面加载完数据之后的首次渲染,会一次提交问题区、答复区、广告组件区三个局部的渲染工作,因为这三个区域波及的内容量比拟大,根本都会超过一屏,甚至两屏以上,另外各个区域也都蕴含一些图文内容,加上自身耗时较高的广告组件。整体页面内容渲染速度很差。并且,因为存在直播信息横条等独自异步申请加载的数据内容渲染,也容易造成 setData 操作在小程序渲染线程中拥塞景象的产生。

所以,从小程序 FMP 的统计规定来看,目前的数据渲染逻辑,显然并不是最优的。

既然 FMP 次要统计的是用户第一眼能够看到的首屏地位内容,那么咱们是不是能够换个思路来实现咱们的内容渲染工作。

在确保数据接口性能曾经合乎惯例规范的状况下,咱们能够应用更聪慧的渲染策略。

优化计划

为了解决上述问题,咱们构思了一套 分屏式内容渲染 策略,意在让用户能最快速度的先看到一部分要害内容,再分阶段渲染剩下须要被渲染的数据,而那些不须要被主动渲染的数据,能够改成由用户某种行为(比方滑动页面)触发加载和渲染。

优化后的问答页渲染示意图


PS:广告组件自身为异步组件,第二次 setData 会触发广告组件渲染,而广告组件外部自行发动异步内容的加载。

优化后的问答页渲染逻辑,整体上被拆分为四个阶段:

  1. 核心内容疾速渲染阶段。该阶段为 FMP 次要检测的数据渲染时长,所以在这个阶段,咱们须要让页面的内容和元素,足够装满一屏。
  2. 核心内容补全渲染阶段。该阶段将核心内容中存在的耗时内容,比方图片、视频以及小程序 native 组件等内容渲染上屏(注:对于渲染比拟耗时的组件,目前已知视频 video、所有小程序 native 组件,都不合适放在第一阶段间接渲染,图片 image 如果条件容许,也尽量不放在第一屏)。
  3. 后续内容渲染阶段。该阶段将本次接口返回的须要渲染的数据全副上屏。
  4. 其余非次要异步数据渲染阶段(图例中的直播信息横条)。将另外一个接口的数据渲染上屏。

PS:如果存在核心内容渲染实现后仍旧无奈撑满一屏的状况,能够思考设置整体页面 min-height:100vh,或者页面下方搁置占位元素,来达到撑满一屏的成果。

优化成绩

该优化版于 2020 年 8 月 4 日上午 11 点左右全量上线,在手百中逐渐放量。FMP 指标在 8 月 5 日和 6 日两天疾速降落,7 日逐渐稳固。总计优化 FMP 指标 540ms。


从数据体现来看,优化成果非常明显。

并且,问答页作为宝宝晓得小程序 pv 最大的落地页,占据总 pv 的 60% 左右,另外还有 40% 的其余页面须要咱们继续优化,将来数据体现还有不小的优化空间。

工具建设

工欲善其事必先利其器。后续咱们还须要优化其余入口页面的性能,以及为后续开发高性能页面做继续的技术储备,所以咱们将开发中遇到的和性能无关的问题做了一些形象,通过打造根底操作的工具类库,从底层上来解决或者躲避问题。

上文中有提到,同时发动多个 setData 操作,极易造成小程序渲染线程的拥塞,导致渲染效率受到影响,升高小程序内容上屏的效率。理论开发中,咱们如果要防止同时发动多个 setData,必然会带来额定的逻辑思考老本和代码结构调整的老本,也容易因为调整,升高代码的可读性和可维护性。为了兼顾渲染性能的须要和代码构造的可读性,以及代码观感,咱们专门设计了一个内容渲染工作管理器。

DataSetter

DataSetter 目前曾经集成在团队外部的小程序工程脚手架中,通过 AdvancedPage 创立的小程序 Page 实例,即可反对通过该管理器凋谢的 api 接口,向小程序的渲染线程提交数据渲染工作。

DataSetter 将小程序 setData 操作封装为一个队列式的渲染工作管理器,应用 DataSetter 进行 set 数据操作,能够使得单位工夫内只有一个 setData 操作被执行,而其余被同时 set 的数据,将在队列中排队顺次执行。

图例:优化前同时 setData,会导致小程序渲染线程的拥塞

图例:优化后同时 set,DataSetter 会整体治理数据渲染工作,不会造成渲染线程拥塞

为了反对分屏式渲染策略的编写,DataSetter 的 API 被设计为链式调用式设计。能够以非嵌套的形式编写 N 阶段内容渲染逻辑,代码行文清晰易懂。

this.$dataSetter.set({
// 第一阶段渲染数据
status:'success',
aaa:111
}).done(e => {
// 第一阶段渲染实现
console.log('第一阶段渲染实现');
}).set({
// 第二阶段渲染数据
bbb:222
}).set({
// 第三阶段渲染数据
ccc:333
}).done(e => {
// 第三阶段渲染实现
console.log(' 第三阶段渲染实现‘);
});
复制代码

DataSetter 源码

/**
* @name DataSetter
* @description setData 语法加强, 反对链式调用和队列式 set 数据, 一次 setData 胜利之后才开始下一次 setData
*/
class DataSetter {queue = [];
context = null;
index = 0;
constructor(context) {this.context = context;}
set(dataset = {}) {this.queue.push({dataset, callback: null});
if (this.queue.length === 1) {this.exec();
}
return this;
}
done(callback) {this.queue[this.queue.length - 1].callback = callback;
return this;
}
exec() {let task = this.queue[this.index];
if (!task) {// console.log('all task done!');
this.refresh();
return;
}
const next = () => {// console.log(set data ${this.index} ok!);
task.callback && task.callback();
this.index++;
this.exec();};
// 如果当前任务 dataset 为空, 则不调用原生 setData, 间接执行回调
if (!task.dataset || Object.keys(task.dataset).length < 1) {next();
return;
}
// console.log(set data ${this.index});
this.context.setData(task.dataset, next);
}
refresh() {this.queue = [];
this.index = 0;
}
}
// Page Demo
Page({
$dataSetter: null,
onLoad() {this.$dataSetter = new DataSetter(this);
}
});
复制代码

后记

造成小程序性能不现实的状况有很多,而渲染问题的解决和优化是能够带来最大收益的,并且如果能依据理论的业务场景,来灵便设计视图的渲染策略,往往能够带来奇效。渲染问题优化是一件十分精密的事件,尤其是面对逐步简单的业务代码,敢于去革新尝试,才是最终胜利的终点。

正文完
 0