技术探索与实践优化:从“运营提需求”到“代码跑得飞起”的血泪史
大家好,我是阿哲,腾讯客户端开发一枚,在微信小程序相关业务线摸爬滚打了三年。平时在家远程办公,边听周杰伦的《七里香》循环一百遍边撸代码——别笑,这歌真的能让我静下心来 debug。如果你也在搞小程序、被运营追着要数据、被产品经理画大饼、被 deadline 追着跑,那这篇踩坑实录你一定要看完。
今天不聊高大上的架构设计,也不吹什么微前端、Serverless(虽然我也在学),就说说去年双11前后,我怎么被“运营同学的一句轻描淡写”逼上技术优化的绝路,最后硬生生把一个卡成 PPT 的活动页面干到首屏 800ms 内加载完成的真实经历。
起因:运营一句“能不能快点?”
事情发生在去年 10 月底。那天周五晚上九点,我正准备关掉电脑去煮泡面(远程办公的快乐谁懂),钉钉突然弹出一条消息:
“阿哲哥,明天上线的双11主会场,用户反馈加载太慢了,能不能优化一下?我们这边 KPI 就看这个转化率了😭”
发信人是运营小李,语气卑微但字字诛心。
我点开测试环境链接——好家伙,白屏时间 3.2 秒,JS 执行耗时 1.8 秒,图片还没加载完用户就已经划走了。当时我真的想砸键盘(但忍住了,毕竟 Macbook 很贵)。
问题出在哪?表面看是“慢”,但根子在于技术债 + 需求膨胀。
这个活动页最初只是个简单的商品列表,结果三个月里被加了:
- 动态配置的 banner 轮播
- 实时库存倒计时
- 用户行为埋点(50+ 个点位)
- A/B 测试逻辑
- 多套皮肤切换
- 甚至还有个隐藏的“摇一摇抽奖”彩蛋……
代码早就成了“意大利面条”,连我自己都懒得碰。但没办法,双11 是公司级重点项目,领导盯着,运营哭着要数据,我只能硬着头皮重构。
第一步:别急着改代码,先搞清楚“慢在哪”
很多人一听说性能差,立马开始删 console.log、压缩图片、上 CDN。但老司机都知道:没 profiling 的优化都是耍流氓。
我先用小程序开发者工具的 Performance 面板跑了一轮:
| 指标 | 优化前 | 目标 |
|---|---|---|
| FCP(首次内容绘制) | 2800ms | <1000ms |
| TTI(可交互时间) | 4200ms | <1500ms |
| JS 包体积 | 2.3MB | <1MB |
看到 2.3MB 我差点一口老血喷出来——小程序主包上限才 2MB!怪不得有些低端机直接报 package size exceed 错误。
再看 Network 面板,发现几个致命问题:
- 所有 JS 代码打包进主包,包括那些只在“摇一摇”彩蛋里才用到的逻辑;
- 图片未做懒加载,首屏就加载 12 张高清 banner;
- 埋点 SDK 同步初始化,阻塞主线程;
- 倒计时组件每秒 setState,引发频繁 re-render。
这时候我意识到:这不是一个小修小补的问题,而是一场结构性优化。
踩坑一:分包加载?没那么简单!
第一反应:赶紧分包!把非核心功能拆出去。
但现实很骨感。我们的项目结构混乱,很多公共组件和 utils 散落在各处,甚至有跨页面的直接引用。贸然分包,大概率会遇到:
Error: Module not found: Can't resolve './utils/api' in subpackage
更坑的是,小程序分包有个隐式规则:分包不能引用主包之外的资源,也不能互相引用。这意味着我得手动梳理依赖树。
我花了整整两天,用脚本分析 import 关系,画了一张“依赖蜘蛛网”(后来被同事做成表情包嘲讽我)。最终方案:
- 主包只保留首页、核心商品列表、基础网络请求;
- 活动规则、抽奖、个人中心等挪到独立分包;
- 公共组件统一抽到
common/目录,并通过npm引入(对,小程序也支持 npm 了!)
关键配置(app.json):
{
"subpackages": [
{
"root": "pages/activity",
"pages": ["rule/index", "lottery/index"]
},
{
"root": "pages/user",
"pages": ["center/index"]
}
],
"preloadRule": {
"pages/home/index": {
"network": "all",
"packages": ["pages/activity"]
}
}
}
注意那个 preloadRule —— 别等用户点进去了才加载分包,首页就预加载活动分包,体验丝滑多了。
教训:分包不是万能药,前期架构混乱的话,重构成本可能比重写还高。建议新项目一开始就规划好分包策略。
踩坑二:图片优化,你以为只是压缩?
运营给的 banner 图,动不动就是 2M 一张的 PNG。我说:“能不能给 WebP?”
对方回:“WebP 是啥?能吃吗?”
行吧,自己动手丰衣足食。
我做了三件事:
- 构建时自动压缩:用
imagemin+gulp在 CI 流程里压图; - 首屏懒加载:非首屏图片用
IntersectionObserver延迟加载; - 占位图 + 渐显:避免布局抖动。
小程序代码示例:
// utils/lazyLoad.js
Component({
properties: {
src: String,
placeholder: String // 占位图 base64
},
data: {
loaded: false
},
lifetimes: {
ready() {
this.createIntersectionObserver().relativeToViewport().observe('.img', (res) => {
if (res.intersectionRatio > 0) {
this.setData({ loaded: true });
}
});
}
}
})
模板里这样用:
<view class="img-container">
<image wx:if="{{!loaded}}" src="{{placeholder}}" class="placeholder" />
<image wx:else src="{{src}}" class="real-img" mode="widthFix" />
</view>
效果:首屏图片加载时间从 1.5s → 0.3s,而且再也不闪屏了。
踩坑三:埋点别再“同步初始化”了!
最让我血压飙升的是埋点 SDK。它在 App onLaunch 里同步执行,还要读本地缓存、上报设备信息……直接拖慢启动 600ms。
我跟埋点团队沟通,他们说:“这是规范,不能改。”
我说:“那双11 GMV 掉了算谁的?”
最后妥协方案:异步初始化 + 队列缓冲。
改造后:
- App 启动时不初始化 SDK;
- 用户触发第一个埋点事件时,才动态 import 并初始化;
- 初始化完成前的事件先存队列,完成后批量上报。
伪代码:
let sdk = null;
const eventQueue = [];
export function track(event) {
if (sdk) {
sdk.send(event);
} else {
eventQueue.push(event);
if (!sdkLoading) {
sdkLoading = true;
import('./analytics').then(module => {
sdk = module.default;
sdk.init();
eventQueue.forEach(e => sdk.send(e));
eventQueue.length = 0;
});
}
}
}
这一招直接砍掉 500ms 的启动耗时。记住:任何非核心路径的同步操作,都是性能杀手。
踩坑四:别让 setState 成为“重绘炸弹”
那个倒计时组件,每秒调一次 this.setData({ time: newTime })。看起来没啥,但结合商品列表滚动,FPS 直接掉到 20。
解决方案:合并更新 + 节流。
我把倒计时改成只在视口内才更新,并且用 requestAnimationFrame 合并渲染:
let rafId = null;
function updateTimer() {
if (isInView) {
const now = Date.now();
// 只在需要视觉更新时 setData(比如秒数变化)
if (Math.floor(now / 1000) !== lastSecond) {
this.setData({ time: formatTime(now) });
lastSecond = Math.floor(now / 1000);
}
rafId = requestAnimationFrame(updateTimer);
}
}
同时,在页面 onHide 时取消 raf,彻底杜绝后台消耗。
成果 & 反思
经过两周的“闭关修炼”(其实就是天天加班),最终上线数据:
| 指标 | 优化前 | 优化后 | 提升 |
|---|---|---|---|
| FCP | 2800ms | 780ms | ↓72% |
| TTI | 4200ms | 1200ms | ↓71% |
| 主包体积 | 2.3MB | 950KB | ↓59% |
| 用户停留时长 | 28s | 45s | ↑61% |
运营小李终于露出了笑容,还请我喝了杯瑞幸(虽然只点了最便宜的)。
但更重要的是,这次经历让我深刻体会到:
技术优化不是炫技,而是为业务结果负责。
我们常吐槽“运营不懂技术”,但反过来,如果技术人员只关心“代码是否优雅”,而不看数据、不理解业务目标,那写的代码再漂亮也是空中楼阁。
现在我们团队已经建立了 “性能基线”机制:每次提 PR,CI 会自动检查包体积、FCP 等指标,超标直接打回。虽然一开始大家都骂,但现在反而成了习惯——毕竟谁也不想半夜被叫起来救火。
最后:一点技术分享的真心话
写这篇文章,一是记录自己的踩坑,二是想告诉同行们:别怕重构,别怕得罪人。有时候推动技术改进,需要一点“轴”劲。
另外,多和技术以外的角色沟通。我后来和运营约了个 coffee chat(线上),教她怎么看 Lighthouse 报告,她也给我讲了哪些数据指标最关键。结果?下次提需求时,她会主动问:“这个功能会不会影响性能?”
技术分享的意义,从来不只是“我学会了什么”,而是“我们一起解决了什么”。
好了,泡面都凉了,我去加热了。如果你也在搞小程序性能优化,欢迎留言交流——或者,直接甩个简历过来?我们组还在招人 😎
(PS:别问为什么用周杰伦,问就是情怀。代码可以重构,青春不能。)

评论 0