启动优化:小程序启动流程分析、首屏加载优化、预加载策略

大家好,我是老张。今天咱们聊聊启动优化。

说实话,小程序启动慢这个问题,我踩过的坑比你们想象的多。有一次上线一个活动页面,用户反馈说「打开小程序像在等火车」,那叫一个尴尬。后来我花了整整两天时间,把启动流程从头到尾捋了一遍,才彻底搞明白问题出在哪。

嗯,今天就把这些经验分享给你们。

小程序启动流程,到底发生了什么?

先问一个问题:你点开一个小程序,到看到首页内容,中间经历了什么?

很多人觉得就是「下载代码 → 渲染页面」两步。其实没那么简单。我习惯把启动流程拆成三个阶段:

  1. 环境准备阶段:抖音客户端拉起小程序容器,初始化 JS 引擎、渲染引擎。这个阶段用户看到的是白屏。
  2. 代码加载阶段:下载小程序包、解析 app.json、加载全局代码。这个阶段用户看到的是加载中。
  3. 首屏渲染阶段:执行页面逻辑、请求数据、渲染 DOM。这个阶段用户看到的是页面逐渐出现。

说白了,启动慢就是这三个阶段中某个环节卡住了。我见过最夸张的一个项目,光代码加载就花了 3 秒多,用户早跑了。

核心结论:启动优化的目标,就是让用户从「点击图标」到「看到有用内容」的时间尽可能短。抖音小程序官方建议控制在 2 秒以内,我个人觉得 1.5 秒是及格线。

首屏加载优化,从哪下手?

首屏加载,说白了就是用户第一眼看到的东西。你想想看,如果首页要等 3 秒才出来,用户会怎么想?

我总结了一套「三板斧」优化方法,你们可以直接拿去用。

第一板斧:减少包体积

包体积越大,下载越慢,这是常识。但很多人不知道的是,抖音小程序对包体积有硬性限制——主包不能超过 2MB。超过?不好意思,审核都过不了。

我在项目中遇到过一个小程序,主包 1.8MB,看起来没超对吧?但用户反馈启动慢。我一查,发现里面塞了一张 500KB 的 PNG 图片当背景。你说气不气人?

优化建议:

  • 图片能压缩就压缩,能用 WebP 就别用 PNG
  • 非首屏的组件、页面,全部扔到分包里去
  • 第三方库能按需加载就别全量引入

小技巧:用抖音开发者工具的「代码依赖分析」功能,一眼就能看出哪个文件是「胖子」。我每次优化前必看这个。

第二板斧:减少首屏请求数

首屏渲染时,页面通常会发好几个请求:获取用户信息、拉取首页数据、加载配置……每个请求都有网络延迟,串行执行的话,时间全浪费在等待上了。

我曾经接手过一个项目,首页首屏发了 7 个请求。你想想看,就算每个请求 200ms,7 个串行就是 1.4 秒。用户还没看到内容,光等请求就花了 1.4 秒。

优化方案:

  • 请求并行化:把不依赖彼此的请求改成并行发起。用 Promise.all 或者直接同时调用多个接口。
  • 数据预加载:有些数据其实在上一页就已经有了,别重复请求。比如从首页跳到详情页,详情页的数据完全可以在跳转前就准备好。
  • 本地缓存:用户信息、配置类数据,第一次请求后缓存到本地,下次直接读缓存。
// 并行请求示例
async function loadHomePage() {
  const [userInfo, bannerData, productList] = await Promise.all([
    getUserInfo(),
    getBanner(),
    getProductList()
  ]);
  // 渲染页面
}

注意:并行请求虽好,但别一股脑全发。如果某个接口特别慢,会拖慢整体渲染。我一般会把「关键数据」和「非关键数据」分开处理——关键数据必须等,非关键数据可以后加载。

第三板斧:减少渲染节点

这个坑我踩过。有一次首页用了大量 wx:for 渲染列表,数据量不大,但每个列表项里嵌套了七八层 view。结果渲染时间直接飙到 800ms。

为什么会这样?因为抖音小程序的渲染机制和浏览器不一样。节点越多,渲染越慢。尤其是深层嵌套的节点,性能损耗特别大。

优化建议:

  • 减少 DOM 层级,能用 flex 布局就别用绝对定位嵌套
  • 首屏只渲染「可见区域」的内容,下面的内容用滚动加载
  • 复杂组件用 wx:if 控制显示,别用 hidden——前者是懒渲染,后者是始终渲染

预加载策略,让用户感觉「秒开」

预加载,说白了就是「把未来的工作提前做」。用户还没点,你就把东西准备好了。这样用户点的时候,自然就快了。

我常用的预加载策略有三种:

策略 适用场景 实现方式
数据预拉取 首页数据、用户信息 在 app.onLaunch 中提前请求,存入全局变量或缓存
页面预加载 Tab 页、热门页面 使用 wx.preloadPage 或自定义预加载逻辑
分包预下载 非首屏分包 使用 wx.preloadSubPackage

数据预拉取,我最常用的招

app.onLaunch 里,我会把首页需要的数据提前请求好。这样首页渲染时,直接从全局变量里拿数据,不用再等网络请求。

// app.js
App({
  onLaunch() {
    // 预拉取首页数据
    this.globalData.homeData = null;
    this.preloadHomeData();
  },
  async preloadHomeData() {
    const data = await request('/api/home');
    this.globalData.homeData = data;
  }
});

// pages/index/index.js
Page({
  onLoad() {
    const app = getApp();
    if (app.globalData.homeData) {
      // 直接使用预拉取的数据
      this.setData({ list: app.globalData.homeData });
    } else {
      // 兜底:重新请求
      this.loadData();
    }
  }
});

避坑指南:我曾经在预拉取时犯过一个错误——把用户 token 还没拿到就发请求,结果接口返回 401。后来我加了一个「等待 token 就绪」的逻辑,才彻底解决。记住:预拉取也要考虑依赖关系。

页面预加载,让跳转「零等待」

抖音小程序提供了 wx.preloadPage 接口,可以提前加载某个页面。比如用户在首页时,我就把「热门推荐」页预加载好。用户一点,瞬间跳转。

// 在首页预加载热门推荐页
wx.preloadPage({
  url: '/pages/hot/hot'
});

不过要注意,预加载会占用内存。别一股脑把所有页面都预加载了。我一般只预加载「用户最可能点击的 1-2 个页面」。

分包预下载,解决「分包加载慢」

如果你的小程序用了分包,用户第一次访问某个分包时,需要下载分包代码。这个过程可能会让用户等 1-2 秒。

解决方案:在主包加载完成后,悄悄把常用分包下载好。

// 主包加载完成后,预下载活动分包
wx.preloadSubPackage({
  packages: ['activity'],
  success() {
    console.log('分包预下载成功');
  }
});

总结一下:启动优化不是一蹴而就的事。我建议你们先做一次「启动耗时分析」,找到瓶颈在哪。然后针对性地用上面这些方法去优化。记住一个原则:让用户尽快看到有用的内容,哪怕只是骨架屏,也比白屏强。

好了,这一章就聊到这。下一章我会讲「渲染优化」,到时候咱们聊聊为什么你的列表滚动起来会卡顿,以及怎么解决。到时候见。