ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

小程序面试指南

小程序面试指南 一、为什么会出现小程序App太重H5网页太糙小程序是 App 和 H5 网页之间的“黄金平衡点”。它既保留了 App 流畅的体验和强大的功能又拥有了 H5 网页无需下载、极其轻便的优势。App、H5、小程序对比对比维度原生AppH5网页小程序获取与安装太重需去应用商店搜索、下载几百兆安装包、注册登录门槛极高。最轻无需下载复制个链接就能打开。适中无需下载安装扫码或微信搜索“即用即走”不占手机内存。性能与流畅度极佳直接调用手机系统底层硬件滑动丝滑加载极快。较差依赖浏览器渲染每次打开都要重新加载复杂动效容易卡顿。较好体验接近原生 App加载快、流畅度高告别了 H5 的卡顿感。系统权限与功能无限制能调用手机所有底层功能如后台常驻、复杂蓝牙等。极受限几乎没有系统权限无法调用摄像头、蓝牙等高级硬件功能。较丰富依托微信生态能调用支付、定位、扫码等常用底层 API满足 90% 业务需求。开发与维护成本极高需 iOS 和安卓两套代码开发周期长每次更新都要重新提交应用商店审核。极低一套代码多端运行修改服务器代码即可无需审核迭代极快。适中一套代码如 uni-app即可生成开发门槛较低更新只需提交微信审核非常便捷。传播与分享困难用户很难主动去分享一个 App获客成本高昂。容易链接可随意发到任何聊天软件或朋友圈但容易流失。极佳背靠微信庞大流量可一键转发给好友或微信群裂变传播极快。用户留存与触达强图标常驻桌面可通过系统推送随时唤醒沉睡用户。极弱关闭网页就找不到了无法主动触达用户。中等用完即走导致留存较弱但可通过微信订阅消息在限定时间内触达用户。二、小程序生命周期1. 应用级别生命周期App 级生命周期函数触发时机核心作用与实战场景Vue3 对标onLaunch小程序首次启动时触发全局仅执行 1 次全局初始化检查登录状态、获取用户信息、初始化全局配置如globalData。App 初始化onShow小程序启动或从后台切回前台时触发可多次执行全局状态刷新检查 Token 是否过期、恢复全局音频/视频播放、埋点统计。App activatedonHide小程序从前台切到后台时触发全局资源释放保存临时表单草稿、暂停全局定时器、停止音乐播放。App deactivated2. 页面级别生命周期Page 级生命周期函数触发时机核心作用与实战场景Vue3 对标onLoad页面首次加载时触发仅执行 1 次接收参数与初始请求接收页面跳转传来的参数options发起核心的接口请求如获取商品详情。onMounted初始化onShow页面显示或从后台切回前台时触发可多次执行数据动态刷新比如从详情页返回列表页时刷新列表的未读消息数或最新状态。onActivatedonReady页面初次渲染完成时触发仅执行 1 次DOM 操作与动画获取节点信息如获取元素宽高、启动页面入场动画、初始化地图/图表组件。onMountedonHide页面被隐藏或切到后台时触发暂停与保存暂停当前页面的视频播放、停止页面专属的倒计时器、保存表单进度。onDeactivatedonUnload页面被卸载关闭时触发彻底清理清除定时器防止内存泄漏、取消未完成的网络请求、解绑事件监听。onUnmounted3. 组件级别生命周期Component 级核心阶段uni-appVue3生命周期原生小程序生命周期触发时机与核心作用实战开发场景创建阶段setupcreated实例刚创建数据初始化。此时组件实例刚刚被创建视图层DOM还未生成。初始化组件内部状态、定义响应式变量、接收父组件传参。注意不能操作 DOM。挂载阶段onMountedattached进入节点树DOM 准备就绪。组件实例进入页面节点树视图层已渲染完毕。最常用的初始化时机。发起组件自身的网络请求、初始化第三方图表如 ECharts、获取 DOM 节点宽高。布局完成Vue3 无直接对应ready视图层布局完成。在attached之后触发此时可以获取节点信息。在原生小程序中若需要精确获取组件在屏幕上的位置或宽高放在这里最稳妥。更新阶段onUpdated原生无直接对应数据变更视图重新渲染后。响应式数据更新导致 DOM 重新渲染后触发。在数据更新后执行依赖最新 DOM 的操作如列表渲染完后自动滚动到底部、重新计算长列表高度。销毁阶段onUnmounteddetached离开节点树组件销毁。组件被从页面节点树移除时触发。极其重要的清理时机。清除定时器clearInterval、解绑全局事件监听、断开 WebSocket防止内存泄漏。三、小程序生命周期流程1. 应用级生命周期流程App.vue应用级生命周期负责管理整个小程序从出生到死亡的全局状态。启动与初始化onLaunch当用户首次打开小程序时触发全局仅执行 1 次。通常在这里做全局配置、检查登录状态等。进入前台onShow小程序启动或者从后台切回前台时触发可多次执行。适合做全局状态刷新、恢复播放等。进入后台onHide当用户点击右上角胶囊关闭、切回微信聊天界面或手机锁屏时触发。适合做数据保存、暂停全局定时器等“善后”工作。销毁后台机制小程序进入后台后如果超过一定时间如 10 分钟未切回前台或者系统内存不足小程序会被系统彻底销毁。下次再打开时就会重新走一遍onLaunch冷启动。2. 页面级生命周期流程Page 级页面级生命周期是日常开发中最常用的它管理单个页面的生老病死。页面加载onLoad页面首次加载时触发仅执行 1 次。主要用于接收路由参数和发起核心网络请求。页面显示onShow页面显示/切入前台时触发可多次执行。每次从其他页面返回或从后台切回前台都会触发适合刷新页面数据。初次渲染完成onReady页面初次渲染完成时触发仅执行 1 次。此时 DOM 节点已经准备好适合获取元素宽高或初始化图表。页面隐藏onHide页面被隐藏如navigateTo到下一个页面或切到后台时触发。适合暂停当前页面的视频或定时器。页面卸载onUnload页面被销毁如navigateBack返回上一页或redirectTo重定向时触发。必须在这里清除定时器和事件监听防止内存泄漏。四、小程序登录流程判断登录是否过期1. 小程序登录时序小程序客户端调用uni.login向微信服务器发起获取临时凭证的请求。微信服务器验证通过后向小程序客户端返回临时凭证code。小程序客户端通过自定义网络请求将临时凭证code发送至开发者服务器。开发者服务器将临时凭证code、AppID 和 AppSecret 发送至微信服务器。微信服务器验证通过后向开发者服务器返回用户唯一标识openid和会话密钥session_key。开发者服务器生成自定义登录态Token。开发者服务器将自定义登录态Token返回给小程序客户端。小程序客户端将Token存储至本地。script setup import { onLaunch } from dcloudio/uni-app; onLaunch(() { console.log(App 启动开始静默登录...); // 1. 调用 uni.login 获取临时 code uni.login({ provider: weixin, // 指定微信登录 success: async (loginRes) { if (loginRes.code) { // 2. 将 code 发送给咱们自己的后端 const res await uni.request({ url: https://your-api.com/api/login, // 替换为你的后端接口 method: POST, data: { code: loginRes.code } }); // 3. 接收后端返回的 Token并存储在本地 if (res.data.token) { uni.setStorageSync(user_token, res.data.token); console.log(登录成功Token已保存); } } }, fail: (err) { console.error(微信登录失败, err); } }); }); /script2. 登录过期判断校验微信底层登录态session_key微信的session_key具有一定的时效性微信官方并没有公开具体的过期时间而是根据用户使用小程序的行为动态续期。为了判断其是否过期开发者需要使用微信提供的检测接口前端校验通过调用wx.checkSession接口来检测当前用户的微信登录态是否有效。如果接口调用成功说明session_key未过期如果调用失败则说明已经失效需要重新执行wx.login登录流程。注意事项由于wx.checkSession接口平均耗时约 200ms如果在每次发起网络请求前都进行校验会产生较大的性能开销。因此不建议在每次请求前都调用此接口。校验开发者服务器登录态自定义 Token除了微信底层的登录态开发者服务器自身维护的登录态Token也存在过期的可能。在实际业务中推荐采用以下策略来判断接口调用前仅校验前端本地是否存在有效的Token不校验后端及微信的登录态以节省每次请求的校验开销。接口调用后在获取到后端的响应数据后进行解析。如果后端返回了与登录态相关的错误码例如 HTTP 401 状态码则说明后端的Token已经失效。此时前端应重置本地的登录态并自动触发重新登录流程然后重新发起业务请求。五、小程序支付流程小程序前端调用uni.login向微信服务器获取用户的openid。小程序前端将订单信息及openid发送至开发者后端服务器。开发者后端服务器调用微信支付统一下单接口向微信服务器请求生成预付单。微信服务器返回预支付交易会话标识prepay_id给开发者后端服务器。开发者后端服务器对支付参数进行二次签名并将结果返回给小程序前端。小程序前端调用uni.requestPayment向微信客户端发起调起支付的请求。用户在微信客户端完成验密及支付授权。微信服务器向开发者后端服务器异步推送支付成功通知。微信客户端向小程序前端返回支付结果回调。小程序前端根据回调结果向用户展示支付状态。template view classpay-page button classpay-btn :disabledisPaying clickhandlePay {{ isPaying ? 支付处理中... : 立即支付 }} /button /view /template script setup import { ref } from vue; // 1. 定义防抖状态防止用户手抖连续点击导致重复支付 const isPaying ref(false); // 2. 发起支付的主流程 const handlePay async () { // 如果正在支付直接拦截 if (isPaying.value) return; isPaying.value true; try { // 【第一步】向你的后端请求支付参数 // 实际项目中这里通常还会先调用创建订单的接口拿到 orderId const payParams await requestPayParams(ORDER_20240526001); // 【第二步】调起微信支付 await wxPay(payParams); // 【第三步】支付动作完成跳转到支付结果页 // 注意这里的 success 只代表用户完成了支付动作真正的结果以后端为准 uni.redirectTo({ url: /pages/pay/result?statussuccessorderIdORDER_20240526001 }); } catch (err) { console.error(支付流程异常:, err); // 【第四步】异常兜底处理 if (err.errMsg err.errMsg.includes(cancel)) { // 用户在支付密码框点击了“取消” uni.showToast({ title: 支付已取消, icon: none }); } else if (err.code ORDER_EXPIRED) { // 订单超时等后端自定义错误 uni.showModal({ title: 提示, content: 当前订单已超过支付时效请重新下单, showCancel: false }); } else { // 其他未知错误 uni.showToast({ title: 支付失败请重试, icon: none }); } } finally { // 无论成功失败解除按钮禁用状态 isPaying.value false; } }; // 3. 封装请求后端获取支付参数 const requestPayParams (orderId) { return new Promise((resolve, reject) { uni.request({ url: https://your-api.com/api/pay/create, // 替换为你的后端接口 method: POST, data: { orderId }, success: (res) { if (res.data.code 200) { resolve(res.data.data); // 返回后端生成的支付参数对象 } else { reject(res.data); } }, fail: reject }); }); }; // 4. 封装调起微信支付 const wxPay (payParams) { return new Promise((resolve, reject) { uni.requestPayment({ timeStamp: payParams.timeStamp, nonceStr: payParams.nonceStr, package: payParams.package, // 格式必须是 prepay_id*** signType: payParams.signType, // V3接口通常为 RSAV2接口为 MD5 paySign: payParams.paySign, success: (res) { console.log(支付动作完成:, res); resolve(res); }, fail: (err) { reject(err); } }); }); }; /script六、小程序路由跳转路由方法核心参数说明页面栈的变化wx.navigateTourl(必填)保留当前页面跳转到应用内的某个非 tabBar 页面。可以使用wx.navigateBack返回到原页面。新页面入栈。当前页面被隐藏触发onHide新页面被压入栈顶触发onLoad、onShow。页面栈深度加 1。wx.redirectTourl(必填)关闭当前页面跳转到应用内的某个非 tabBar 页面。不允许返回到原页面。当前页面出栈新页面入栈。相当于“替换”了当前页面。原页面被卸载触发onUnload页面栈深度保持不变。wx.switchTaburl(必填)跳转到 tabBar 页面并关闭其他所有非 tabBar 页面。不能携带参数。页面全部出栈只留下新的 Tab 页面。页面栈被清空仅包含目标 tabBar 页面。wx.reLaunchurl(必填)关闭所有页面打开到应用内的某个页面任意页面均可。页面全部出栈只留下新的页面。相当于强制重启小程序到指定页面。wx.navigateBackdelta(选填)关闭当前页面返回上一页面或多级页面。delta表示返回的层数默认为 1。页面不断出栈直到目标返回页。页面栈深度减少delta的值。若返回到栈底则停止。页面栈上限小程序的页面栈通常最多只有 10 层。如果频繁使用wx.navigateTo而不及时返回或重定向很容易导致页面栈溢出之后再调用navigateTo会失效。因此在长流程如电商下单、表单填写的终点建议使用redirectTo或reLaunch。API 使用限制wx.navigateTo和wx.redirectTo只能打开非 tabBar 页面而wx.switchTab只能打开 tabBar 页面。如果路径配置错误API 会调用失败。不要直接修改页面栈虽然可以通过getCurrentPages()获取当前页面栈实例但千万不要尝试直接修改这个数组否则会导致路由和页面状态出现严重错误。路由参数获取通过路由跳转携带的参数如 url 中拼接的?id123可以在目标页面的onLoad(options)生命周期函数中直接通过options对象获取。七、小程序的实现原理1. Web 单线程的执行机制传统 H5 网页传统网页的渲染和 JS 逻辑是互斥的单线程模型。执行机制浏览器中JavaScript 引擎线程处理逻辑和 GUI 渲染线程处理页面显示是互斥的。同一时刻只能执行一个任务如果 JS 执行了耗时的计算或死循环页面渲染就会被阻塞导致用户感觉“页面卡死”。为什么设计成单线程主要是为了避免复杂的并发问题。如果允许多线程同时操作 DOM比如一个线程在修改按钮颜色另一个线程在删除按钮会导致不可预测的渲染混乱和竞态条件。痛点复杂的 JS 运算会直接抢占 UI 渲染的资源导致页面卡顿、掉帧。2. 小程序双线程的执行机制为了解决 Web 单线程的卡顿问题并满足微信的安全管控需求小程序采用了逻辑层与视图层分离的双线程模型。逻辑层App Service运行在独立的 JavaScript 引擎中iOS 用 JavaScriptCoreAndroid 用 V8 等。它负责处理所有的 JS 业务逻辑、数据请求、API 调用等。视图层View Layer运行在 WebView 线程中。它负责解析 WXML 和 WXSS进行页面的 UI 渲染。一个小程序有多个页面就会有多个 WebView 线程。核心优势性能隔离即使逻辑层在进行极其复杂的数据计算也不会阻塞视图层的渲染。用户的滑动、点击依然能保持丝滑流畅。安全管控逻辑层是一个纯粹的 JS 沙箱环境没有window、document对象开发者无法直接操作 DOM。这从根源上防止了恶意脚本攻击和页面篡改。3. 小程序实现的底层原理Native Bridge 通信既然逻辑层和视图层是两个独立的线程它们之间是如何协同工作的呢这就涉及到了小程序的底层通信原理。Native 层中转JSBridge这两个线程之间是完全隔离、不共享内存的。它们之间的任何数据传递和事件触发都必须通过宿主环境微信客户端的 Native 层作为桥梁进行异步转发。数据驱动视图setData原理当逻辑层数据发生变化时调用setData。框架将数据序列化为字符串JSON通过 Native 层转发给视图层。视图层接收到数据后进行反序列化利用虚拟 DOMVirtual DOM机制进行 Diff 算法对比最后将差异应用到真实的 DOM 树上完成渲染。事件触发流程用户在界面点击按钮视图层WebView 拦截事件将事件信息序列化后通过 Native 层传递给逻辑层逻辑层找到对应的 JS 事件处理函数执行。八、小程序启动与销毁1. 小程序的两种启动方式小程序的启动主要分为两种状态冷启动Cold Start当用户首次打开小程序或者小程序被系统销毁后再次打开时就会触发冷启动。此时小程序需要重新加载全部资源并执行初始化触发onLaunch耗时相对较长。热启动Hot Start如果用户之前打开过小程序但在一定时间内再次打开此时小程序并未被销毁只是从“后台”状态切换回“前台”状态。这个过程就是热启动它无需重新加载资源几乎是瞬间恢复。2. 小程序销毁机制什么情况下会销毁当用户点击小程序右上角的“关闭”按钮或者按 Home 键离开时小程序并不会立即被销毁而是进入了“后台”状态。只有在满足以下特定条件时小程序才会被彻底销毁下次打开即为冷启动① 后台停留时间过长小程序进入后台后如果长时间没有再次切回前台系统会主动将其销毁。不同平台的判定时间有所不同抖音小程序后台停留超过 5 分钟会被主动销毁。B站小程序进入后台并被挂起后超过 30 分钟未进入前台会被销毁。其他平台部分平台如涂鸦小程序的后台存活时间可能长达 10 分钟。② 系统资源紧张内存告急当手机系统内存不足时宿主应用如微信、抖音会主动回收后台的小程序。例如当客户端连续收到系统内存告警时会根据策略主动销毁后台的小程序以保证宿主 App 的正常运行。③ 后台小程序数量达到上限系统对同时存在于后台的小程序数量是有上限的。例如在 iOS 系统上最多允许有 5 个小程序同时存在。如果用户打开了第 6 个小程序那么最早进入后台的那一个就会被强制销毁。④ 业务异常或主动配置直接销毁不进入缓存在某些特殊场景下小程序关闭时会直接销毁不进行内存缓存页面异常如果用户关闭小程序时页面处于白屏状态系统会直接销毁避免下次打开时恢复异常页面。流程未结束如果关闭时小程序仍有启动流程或网络请求在执行中会直接销毁。主动禁用缓存开发者可以在全局配置如disableCache: true中强制设置退出时销毁小程序确保每次打开都是全新的冷启动。非正式版本开发版、体验版等非正式版本的小程序通常不做内存缓存关闭即销毁。九、小程序的渲染流程1. 启动与初始化阶段当用户点击小程序图标时宿主环境如微信客户端会启动小程序进程并同步初始化两个核心线程渲染线程视图层初始化 WebView 环境加载基础库和渲染框架准备接收渲染指令。逻辑线程逻辑层加载基础库执行app.js创建 App 实例并触发App.onLaunch和App.onShow生命周期。2. 页面加载与数据准备阶段初始化完成后系统开始加载具体的页面逻辑层执行加载入口页面对应的page.js创建 Page 实例收集初始化数据initData并依次触发页面的onLoad和onShow生命周期。数据下发逻辑层将准备好的initData序列化后通过 Native 层JSBridge派发给渲染线程。3. 首次渲染阶段构建视图树渲染线程接收到初始数据后开始执行真正的渲染工作解析与编译解析页面的 WXML 模板和 WXSS 样式文件将其转换为对应的节点树和样式规则。构建视图树将节点树与样式规则合并生成最终的“视图树”Render Tree仅包含可见节点。首次绘制将视图树交由底层图形系统进行绘制完成页面的首次内容绘制FCP和首次有意义绘制FMP。渲染完成后会通知逻辑层触发onReady生命周期。4. 运行时更新阶段数据驱动视图在页面展示后用户的交互或网络请求会触发数据更新进而引发重新渲染触发更新开发者调用setData修改数据。逻辑层会对新旧数据进行 Diff 对比找出发生变化的部分。跨线程通信逻辑层将变化的数据序列化为消息通过 Bridge 异步发送给渲染线程。局部重绘渲染线程接收消息并反序列化精准更新对应的 DOM 节点触发页面的局部重排与重绘最终通知逻辑层更新完成。十、小程序提速手段启动性能优化打破首屏加载瓶颈代码包体积优化最影响首次打开速度代码分包将主包体积严格控制在 2MB 以内。主包只放首页和 TabBar 页面其他低频页面如个人中心、活动详情拆分为子包按需加载。清理冗余删除未使用的页面、组件、图片和样式避免打包多余的第三方库。提前发出请求与预加载数据预加载在页面跳转前如点击列表项时提前请求下一页的数据并挂载到全局如getApp().globalData。新页面加载时直接使用可大幅缩短首屏渲染时间。App.js瘦身不要在onLaunch中做耗时操作如复杂的同步计算、同步请求非必要逻辑可放到首页的onShow或延迟执行。优化首屏渲染体验initData预热在页面初始化时构造好核心渲染数据的骨架结构。底层框架会提前创建 DOM 元素待真实数据返回时仅替换内容无需重新创建元素节省大量渲染时间。骨架屏在页面加载时展示骨架屏占位降低用户的等待焦虑。2. 运行时性能优化保证交互流畅度极致优化setData最核心的痛点减少调用频率严禁在for循环、高频滚动事件或定时器中调用setData。尽量合并多次数据更新一次性传给视图层。控制数据量单次setData的数据量建议控制在 100KB 以内避免 JSON 序列化耗时过长。路径更新使用数据路径语法精确更新如list[0].name: newName避免整个大对象或大数组的全量刷新。长列表与渲染优化虚拟列表对于成百上千条数据的列表必须使用分页加载或虚拟列表技术只渲染用户可视区域内的 DOM 节点防止内存暴涨和滑动卡顿。避免复杂模板逻辑不要在 WXML 的{{}}中写复杂的表达式或函数调用。复杂的数据处理应在 JS 层逻辑层计算好再传给视图层。图片与静态资源优化图片懒加载使用lazy-load属性仅加载可视区域内的图片减少初始网络请求和内存占用。WebP 格式与云端化将图片转换为 WebP 格式体积可减小 30%~50%并将大图、封面图全部上传至云端OSS/CDN不要放在本地包中。内存管理防止闪退及时清理资源在页面或组件卸载时onUnload/onUnmounted务必清理定时器clearInterval、解绑全局事件监听防止内存泄漏。控制页面层级避免连续跳转 5 层以上的页面不用的页面及时使用redirectTo关闭避免页面栈过深占用过多内存。
返回列表