5.1 逻辑层运行环境
小程序的 JavaScript 代码运行在独立的逻辑层线程中,与 WebView 渲染层分离。这意味着:
- 不能访问
document、window、DOM API(逻辑层没有 DOM) - 支持 ES2020+ 语法(async/await、可选链
?.、空值合并??) - 支持 CommonJS 模块(
require)和 ES Module(import/export) - 内置
wx全局对象,提供所有小程序 API
5.2 App 生命周期
App() 是小程序的全局入口,整个小程序只有一个 App 实例,贯穿整个运行周期。
// app.js
App({
// 1. 初始化(只触发一次,冷启动/热启动都会)
async onLaunch(options) {
// options.scene: 场景值(1001=发现栏,1011=扫码,1044=转发卡片等)
// options.query: 启动参数(如分享链接携带的参数)
// options.path: 启动页面路径
// 典型用途:初始化云开发、检查登录态
await this.initCloud()
await this.checkLogin()
},
// 2. 前台显示(每次从后台切换到前台都触发)
onShow(options) {
// 可用于检查版本更新
const updateManager = wx.getUpdateManager()
updateManager.onUpdateReady(() => {
wx.showModal({
title: '更新提示',
content: '新版本已就绪,是否重启?',
success(res) {
if (res.confirm) updateManager.applyUpdate()
}
})
})
},
// 3. 后台隐藏(按 Home 键、来电等)
onHide() {
// 暂停播放、保存草稿等
},
// 4. 全局错误捕获
onError(error) {
console.error('全局错误:', error)
// 上报错误监控(如 Sentry)
},
// 5. 页面不存在(路径错误)
onPageNotFound(options) {
wx.redirectTo({ url: '/pages/404/404' })
},
// 全局共享状态(通过 getApp().globalData 访问)
globalData: {
userInfo: null,
token: '',
isLoggedIn: false,
systemInfo: null
},
// 全局方法
async initCloud() {
wx.cloud.init({ env: 'your-env-id', traceUser: true })
},
async checkLogin() {
try {
const token = wx.getStorageSync('token')
if (token) {
this.globalData.token = token
this.globalData.isLoggedIn = true
}
} catch (e) {
console.error(e)
}
}
})
5.3 Page 生命周期
Page() 注册一个页面,每个页面都有独立的数据和生命周期。
Page({
// 页面初始数据
data: {
list: [],
loading: true,
pageNum: 1,
hasMore: true
},
// ═══ 生命周期函数 ═══
// 1. 页面加载(只触发一次,options 为页面参数)
async onLoad(options) {
const { id, type } = options // 从路由参数获取数据
this.categoryId = id // 临时数据不需要 setData
await this.fetchList()
},
// 2. 页面显示(每次进入都触发,包括 onLoad 之后)
onShow() {
// 从其他页面返回时刷新数据的常用位置
if (this.data.needRefresh) {
this.setData({ needRefresh: false })
this.fetchList()
}
},
// 3. 页面初次渲染完成(一次)
onReady() {
// 此时可以操作页面元素(如获取节点尺寸)
const query = wx.createSelectorQuery()
query.select('#banner').boundingClientRect((rect) => {
console.log('Banner 高度:', rect.height)
}).exec()
},
// 4. 页面隐藏(跳转到其他页面时)
onHide() {
// 暂停视频/音频播放
},
// 5. 页面卸载(redirectTo 或 navigateBack 时)
onUnload() {
// 清除定时器、取消网络请求
this.timer && clearInterval(this.timer)
},
// ═══ 页面事件 ═══
// 下拉刷新(需在 json 中开启 enablePullDownRefresh)
async onPullDownRefresh() {
this.setData({ pageNum: 1, list: [], hasMore: true })
await this.fetchList()
wx.stopPullDownRefresh() // 必须手动停止
},
// 触底加载更多
onReachBottom() {
if (this.data.hasMore && !this.data.loading) {
this.fetchList()
}
},
// 页面分享(设置分享信息)
onShareAppMessage() {
return {
title: '这个小程序超好用!',
path: '/pages/index/index?from=share',
imageUrl: '/assets/share-cover.jpg'
}
},
// ═══ 自定义方法 ═══
async fetchList() {
this.setData({ loading: true })
try {
const res = await getProductList({
page: this.data.pageNum,
categoryId: this.categoryId
})
this.setData({
list: [...this.data.list, ...res.items],
hasMore: res.hasMore,
pageNum: this.data.pageNum + 1
})
} finally {
this.setData({ loading: false })
}
}
})
5.4 setData 深度解析
setData 是小程序响应式的核心,它将数据的变化同步到渲染层。理解其工作原理有助于写出高性能代码。
setData 的工作流程
- 调用
this.setData({key: value}) - 逻辑层将变更数据 JSON 序列化
- 通过 Native Bridge 传输到渲染层
- 渲染层反序列化并更新 data
- WXML 重新渲染变更的节点(局部更新)
setData 性能优化技巧
// ❌ 错误:频繁 setData(每次都有通信开销)
onScroll(e) {
this.setData({ scrollTop: e.detail.scrollTop }) // 每帧触发!
}
// ✅ 正确:节流处理
onScroll: throttle(function(e) {
this.setData({ scrollTop: e.detail.scrollTop })
}, 100),
// ❌ 错误:传输大量无变化数据
updateItem(index) {
const list = [...this.data.list]
list[index].liked = true
this.setData({ list }) // 传输整个数组!
},
// ✅ 正确:路径语法只更新变化的字段
updateItem(index) {
this.setData({
[`list[${index}].liked`]: true // 只传输这一个字段
})
},
// ✅ 合并多次 setData 为一次
afterFetch(data) {
this.setData({ // 一次性更新所有字段
list: data.items,
total: data.total,
loading: false,
pageNum: this.data.pageNum + 1
})
}
5.5 事件系统
小程序事件分为冒泡事件和非冒泡事件,通过 bind 或 catch 前缀绑定。
<!-- bind 绑定:事件会向父节点冒泡 -->
<view bindtap="onViewTap">
<button bindtap="onBtnTap">按钮</button>
</view>
<!-- 点击按钮:先触发 onBtnTap,再冒泡触发 onViewTap -->
<!-- catch 绑定:阻止冒泡 -->
<view bindtap="onViewTap">
<button catchtap="onBtnTap">阻止冒泡</button>
</view>
<!-- 点击按钮:只触发 onBtnTap,不会触发 onViewTap -->
<!-- data-* 传递参数 -->
<view
wx:for="{{list}}"
wx:key="id"
bindtap="onItemTap"
data-id="{{item.id}}"
data-type="{{item.type}}"
>
{{item.name}}
</view>
// 事件处理函数中读取 data-* 参数
onItemTap(e) {
// e.currentTarget.dataset: 当前节点的 data-* 集合
// e.target.dataset: 实际触发事件的节点(可能是子节点)
const { id, type } = e.currentTarget.dataset
console.log('点击了:', id, type)
// e.detail: 组件自定义事件携带的数据
// e.touches: 触摸点信息数组
// e.timeStamp: 事件时间戳
}
5.6 页面路由
// 保留当前页面,跳转到新页面(可返回,最多 10 层页面栈)
wx.navigateTo({ url: '/pages/detail/detail?id=123' })
// 关闭当前页面,跳转(不可返回)
wx.redirectTo({ url: '/pages/login/login' })
// 关闭所有页面,跳转到 TabBar 页面
wx.switchTab({ url: '/pages/index/index' })
// 返回上一页
wx.navigateBack({ delta: 1 }) // delta: 返回几层,默认 1
// 关闭所有页面并打开(常用于登录后跳转)
wx.reLaunch({ url: '/pages/index/index' })
// 页面间通信:EventChannel(navigateTo 携带回调)
wx.navigateTo({
url: '/pages/picker/picker',
events: {
onSelect(data) { // 接收被打开页面发来的事件
console.log('选择了:', data)
}
},
success(res) {
res.eventChannel.emit('init', { current: this.data.selected })
}
})