小程序生命周期
运行机制
小程序什么时候会被销毁
当小程序进入后台,客户端会维持一段时间的运行状态,超过一定时间后(目前是5分钟)会被微信主动销毁。
当短时间内(5s)连续收到两次以上收到系统内存告警,会进行小程序的销毁。
再次打开逻辑
用户打开小程序的预期有以下两类场景:
- A. 打开首页:场景值有1001,1019,1022,1023,1038,1056
- B. 打开小程序指定的某个页面:场景值为除A以外的其他
当再次打开一个小程序逻辑如下:
上一次的场景 |
当前打开的场景 |
效果 |
A |
A |
保留原来的状态 |
B |
A |
清空原来的页面栈,打开首页(相当于执行wx.reLaunch到首页) |
A或B |
B |
清空原来的页面栈,打开指定页面(相当于执行wx.reLaunch到指定页 |
小程序的生命周期
App()函数注册一个小程序。接受一个Object参数,其指定小程序的生命周期回调等。这里的生命周期针对整个小程序项目,而不是哪个页面。
object参数说明:
前台、后台定义: 当用户点击左上角关闭,或者按了设备Home键离开微信,小程序并没有直接销毁,而是进入了后台;当在此进入微信或再次打开小程序,又会从后台进入前台。
下面是一个示例,代码如下:
//app.js
App({
onLaunch: function (options) {
// 小程序初始化完成时触发,全局只触发一次。
// options说明:
// path:打开小程序的路径
// query 打开小程序的query
// scene 打开小程序的场景值
// shareTicket 转发信息相关
// referrerInfo 当场景为由另一个小程序或公众号或App打开时,返回此字段
console.log('app >> onLaunch , options::',options);
},
onShow: function(options){
// 小程序启动,或从后台进入前台显示时触发
// 参数与onLaunch一致
console.log('app >> onShow, options :: ',options);
},
onHide:function(){
//小程序从前台进入后台时触发。
console.log('app >> onHide');
},
onError:function(error){
// 小程序发生脚本错误,或者api调用失败时触发。
// error String 错误信息,包含堆栈信息
console.log('app >> onError , error::'+error);
},
onPageNotFound(Object){
// 基础库1.9.90开始支持
// 小程序要打开的页面不存在时触发。
// Object参数说明:
// path String 不存在的页面路径
// query object 打开不存在页面的query
// isEntryPage Boolean 是否本次启动的首个页面
console.log('app >> onPageNotFound , Object :: ',Object);
//注:如果开发者没有添加 onPageNotFound 监听,当跳转页面不存在时,将推入微信客户端原生的页面不存在提示页面。
// 如果 onPageNotFound 回调中又重定向到另一个不存在的页面,将推入微信客户端原生的页面不存在提示页面,并且不再回调 onPageNotFound。
}
})
当启动小程序时,在开发者工具中可以看到控制台打印如下信息
页面的生命周期
Page(Object)函数用来注册一个页面。接受一个Object类型参数,其指定页面的初始数据、生命周期回调、时间处理函数等。
下面用一个示例来尝试下小程序的生命周期流程
该示例有4个页面pageA/pageB/pageC/pageD,其中pageA和pageB是两个tab页面,即通过底部标签切换的页面。而pageC和pageD是两个普通页面。示例页面如下:
四个页面如图所示,有一些按钮分别表示不同的路由跳转方式。
下面是pageA相关的部分页面代码,其他页面类似
Page({
data:{
},
onLoad:function(options){
//页面加载时触发。一个页面只会调用一次,可以在onLoad的参数中获取打开当前路径中的参数。
//参数 options Object 打开当前页面路径中的参数
console.log('pageA >> onLoad , options ::',options);
},
onReady:function(){
//页面初次渲染完成时触发。一个页面只会调用一次,代表页面已经准备妥当,可以和视图层进行交互。
console.log('pageA >> onReady');
},
onShow:function(){
//页面显示/切入前台时触发
console.log('pageA >> onShow');
},
onHide:function(){
//页面隐藏/切入后台时触发。
console.log('pageA >> onHide');
},
onUnload:function(){
//页面卸载时触发。
console.log('pageA >> onUnload');
},
// 自定义方法
navigateToC:function(){
//保留当前页面,跳转到应用内的某个页面,但是不能跳到 tabbar 页面。使用 wx.navigateBack 可以返回到原页面。
console.log('%cpageA==========navigateToC===========','color:red');
wx.navigateTo({
url:'/pages/page-c/index'
});
},
redirectToC:function(){
//关闭当前页面,跳转到应用内的某个页面,但是不允许跳转到tabbar页面
console.log('%cpageA==========redirectToC===========','color:red');
wx.redirectTo({
url:'/pages/page-c/index'
});
},
reLaunchToC:function(){
//关闭所有页面,打开到应用内的某个页面
console.log('%cpageA==========reLaunchToC===========','color:red');
wx.reLaunch({
url:'/pages/page-c/index'
})
}
});
下面是各种情况下的试验结果:
当前页面 |
路由后页面 |
跳转方式 |
触发的生命周期(按顺序) |
说明 |
A |
A |
首次打开 |
|
执行小程序的onlaunch>onShow ,然后执行页面的onLoad>onShow>onReady
|
A |
B |
点击tab标签 |
|
pageA隐藏,pageB加载 |
A |
B(再次打开) |
点击tab标签 |
|
pageA隐藏,pageB显示 |
A |
C |
navigateTo |
|
pageA隐藏,pageC加载 |
A |
C |
redirectTo |
|
pageA卸载,pageC加载 |
A |
C |
reLaunchTo |
|
卸载所有页面,pageC加载 |
C |
A |
switchTabTo |
|
卸载所有非tab页面,pageA显示 |
D |
C |
navigateBack |
|
pageD卸载,pageC显示 |
D |
B |
switchTabTo |
|
卸载所有非tab页面,pageC显示,pageA任然存在 |
|
D |
关闭小程序 |
|
打开A/B/C/D,从pageD关闭小程序,可以看到执行的D和App的onHide ,小程序并没有真正退出 |
上面说到的情况都较为简单的流程,从官方文档便可以理解到,下面试验一些复杂的流程。
第一种 A->C->D-B
,其实这个过程按正常思维便可以理解,下图为整个过程的展示:
上面的图片展示了小程序从打开到走完这个流程的所有生命周期,其余都好理解,值得注意的是,DswitchTabTo
B的时候,会干掉所有其他非tabBar页面,所以pageC和pageD都会触发onUnload
第二种 A->C->C->C
,这种情况下我们从C页面继续跳转到C页面,为了看下小程序是新创建一个C页面,还是复用之前的C页面,我们在C页面中加一个input用来识别页面是否被复用。试验结果如下图:
事实证明每次我们都打开了一个新的页面,我们的输入框是空白的,而我每次分别输入了1/2/3,从生命周期函数也可以看到每次C都执行了onHide但没有执行onUnload
说明之前的页面还在,而每次打开C都执行了onLoad/onShow/onReady
说明我们打开的是一个新的页面,但是打开控制台看页面栈信息,截图如下:
页面栈树中只有A和C两个,不过我们跳转时打开这里可以看到C的__webviewId__
是在变化的,也证明了我们打开的是一个新的页面。虽然从控制台AppData的Tree看上去有要两个,实际上我们跳转够10次还是会受到微信页面深度最大为10层的限制。下面按微信左上角的返回键,看下生命周期流程:
可以看到依次执行了C页面的onUnload/onShow
,也就是之前的多个C页面被一一卸载掉了。这里注意一点,对于二级页面,使用navigateBake或者微信自己的返回按键都会卸载掉当前页面,所以离开页面只有navigateTo的时候会保留当前页面,其他情况都会卸载掉当前页面。(自我总结:对于二级页面,只有从下一个页面返回自身的情况下,不调用onLoad其他任何情况进入二级页面,都会触发onLoad。没有想到其他情况,顾作此总结,欢迎指正)。
组件的生命周期函数
小程序支持自定义组件,使用Component构造器定义组件,使用Component构造器时可以定义组件的属性、数据、方法等。这里整理下生命周期相关函数。
组件的生命周期函数有两种形式,除了写在外面,还可以统一写在lifetimes
中,在下面的示例代码备注中可以看到。
Component({
properties:{
innerText:{
type:String
}
},
data:{
},
methods:{
},
created:function(){
// 组件生命周期函数,在组件实例进入页面节点树时执行,注意此时不能调用setData
console.log('Component-1 >> created');
},
attached:function(){
// 组件生命周期函数,在组件实例进入页面节点树时执行。
console.log('Component-1 >> attached');
},
ready:function(){
// 在组件布局完成后执行,此时可以获取节点信息
console.log('Component-1 >> ready');
},
moved:function(){
// 在组件实例被移动到节点树另一个位置时执行
console.log('Component-1 >> moved');
},
detached:function(){
// 在组件实例被从页面节点树移除时执行
console.log('Component-1 >> detached');
},
lifetimes:{
// 组件生命周期声明对象,将组件的生命周期收归到该字段进行声明,原有声明方式仍旧有效,如同时存在两种声明方式,则lifetimes字段内声明方式优先级最高
created:function(){
console.log('Component-1 lifetimes >> created');
},
attached:function(){
console.log('Component-1 lifetimes >> attached');
},
ready:function(){
console.log('Component-1 lifetimes >> ready');
},
moved:function(){
console.log('Component-1 lifetimes >> moved');
},
detached:function(){
console.log('Component-1 lifetimes >> detached');
}
},
pageLifetimes:{
// 组件所在页面的生命周期声明对象,目前仅支持页面的show和hide两个生命周期
show:function(){
console.log('Component-1 pageLifetimes >> Show');
},
hide:function(){
console.log('Component-1 pageLifetimes >> Hide');
}
}
})
分别在B页面和C页面引入该组件, 从以下几种情况看下生命周期函数的执行过程
第一种情况同时引入上面所有生命周期函数,由A通过tab切换到B,再由B通过navigateTo切换到C,生命周期执行打印如下:
可以看到组件中只执行了lifetimes
中的生命周期函数,外层的生命周期函数并没有执行。而且可以看到先执行组件的created/attached
函数,随后执行页面的onLoad/onShow
,再执行组件的ready
,最后执行页面的onReady
,这是页面中引入组件时组件的生命周期函数执行顺序。
lifetimes中的生命周期函数执行了,外层的生命周期函数没有执行,所有当两者同时存在时,lifetimes中的优先级要高。
这里组件中的pageLifetimes
没有执行,不清楚具体原因,官网说是2.2.3版本以上支持,我在2.3.0的环境还是没有执行,不清楚具体原因,求解!
第二种情况,不引入lifetimes的生命周期函数,只使用外层的生命周期函数,执行结果如下图所示:
可以看到,生命周期函数执行顺序没有变,外层的生命周期生效。
第三种情况,在B页面中使用两个组件,这里我把lifetimes中的created生命周期注释掉了,看生命周期的执行情况,这里组件1和组件2的代码相同,执行结果情况如下图:
从执行的结果来看,整个生命周期的执行顺序不变,只是要在每个阶段执行所有组件的相应生命周期,如上图,现行玩所有组件的created,再执行所有组件的attached,然后执行页面的onLoad和onShow,再执行所有组件的ready,最后执行页面的onReady。
总结:通过这些试验,对小程序相关的生命周期有了一个基本的认识。
1、小程序初次打开会执行小程序的生命周期钩子函数:onLaunch->onShow
,而且这些钩子函数只会执行一次。关闭小程序,小程序并不会真正退出,所以执只行了onHide
。
2、页面的初次打开会执行页面的生命周期钩子函数:onLoad->onShow->onReady
,通过navigateTo
离开页面会保留该页面,此时只执行onHide
,其他方式离开(包括navigateBack)都会干掉当前页面,此时会执行onHide>onUnload
。特殊情况:switchTabTo
会干掉所有非tab页面,但是保留所有已经加载的tab页面。
3、包含组件的页面,先执行所有组件的created
,再执行所有组件的attached
,然后执行页面的onLoad>onshow
,再执行所有组件的ready
,随后执行页面的onReady
。当页面被卸载时,先执行页面的onUnload
,再执行组件的detached
。页面不卸载,不会触发组件的detached
。
初次接触小程序,能力有限,欢迎指正![/抱拳]
参考文档:https://developers.weixin.qq.com/miniprogram/dev/framework/custom-component/component.html