分类
小程序

微信小程序生命周期浅析

小程序生命周期

运行机制

小程序什么时候会被销毁
当小程序进入后台,客户端会维持一段时间的运行状态,超过一定时间后(目前是5分钟)会被微信主动销毁。
当短时间内(5s)连续收到两次以上收到系统内存告警,会进行小程序的销毁。
再次打开逻辑
用户打开小程序的预期有以下两类场景:
  • A. 打开首页:场景值有1001,1019,1022,1023,1038,1056
  • B. 打开小程序指定的某个页面:场景值为除A以外的其他

当再次打开一个小程序逻辑如下:

上一次的场景当前打开的场景效果
AA保留原来的状态
BA清空原来的页面栈,打开首页(相当于执行wx.reLaunch到首页)
A或BB清空原来的页面栈,打开指定页面(相当于执行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。
      }
    })
当启动小程序时,在开发者工具中可以看到控制台打印如下信息

app


页面的生命周期

Page(Object)函数用来注册一个页面。接受一个Object类型参数,其指定页面的初始数据、生命周期回调、时间处理函数等。

下面用一个示例来尝试下小程序的生命周期流程

该示例有4个页面pageA/pageB/pageC/pageD,其中pageA和pageB是两个tab页面,即通过底部标签切换的页面。而pageC和pageD是两个普通页面。示例页面如下:

pageA

pageB

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'
            })
        }
    });


下面是各种情况下的试验结果:

当前页面路由后页面跳转方式触发的生命周期(按顺序)说明
AA首次打开 first 执行小程序的onlaunch>onShow,然后执行页面的onLoad>onShow>onReady
AB点击tab标签 AtoB pageA隐藏,pageB加载
AB(再次打开)点击tab标签 AtoB2 pageA隐藏,pageB显示
ACnavigateTo An2C pageA隐藏,pageC加载
ACredirectTo Ar2C pageA卸载,pageC加载
ACreLaunchTo AL2c 卸载所有页面,pageC加载
CAswitchTabTo Cs2A 卸载所有非tab页面,pageA显示
DCnavigateBack Db2C pageD卸载,pageC显示
DBswitchTabTo Ds2B 卸载所有非tab页面,pageC显示,pageA任然存在
D关闭小程序 image.png 打开A/B/C/D,从pageD关闭小程序,可以看到执行的D和App的onHide,小程序并没有真正退出
上面说到的情况都较为简单的流程,从官方文档便可以理解到,下面试验一些复杂的流程。
第一种 A->C->D-B,其实这个过程按正常思维便可以理解,下图为整个过程的展示:

ACDB

上面的图片展示了小程序从打开到走完这个流程的所有生命周期,其余都好理解,值得注意的是,DswitchTabToB的时候,会干掉所有其他非tabBar页面,所以pageC和pageD都会触发onUnload
第二种 A->C->C->C,这种情况下我们从C页面继续跳转到C页面,为了看下小程序是新创建一个C页面,还是复用之前的C页面,我们在C页面中加一个input用来识别页面是否被复用。试验结果如下图:

ACCC

事实证明每次我们都打开了一个新的页面,我们的输入框是空白的,而我每次分别输入了1/2/3,从生命周期函数也可以看到每次C都执行了onHide但没有执行onUnload说明之前的页面还在,而每次打开C都执行了onLoad/onShow/onReady说明我们打开的是一个新的页面,但是打开控制台看页面栈信息,截图如下:

trace

页面栈树中只有A和C两个,不过我们跳转时打开这里可以看到C的__webviewId__是在变化的,也证明了我们打开的是一个新的页面。虽然从控制台AppData的Tree看上去有要两个,实际上我们跳转够10次还是会受到微信页面深度最大为10层的限制。下面按微信左上角的返回键,看下生命周期流程:

Cback

可以看到依次执行了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,生命周期执行打印如下:

Components

可以看到组件中只执行了lifetimes中的生命周期函数,外层的生命周期函数并没有执行。而且可以看到先执行组件的created/attached函数,随后执行页面的onLoad/onShow,再执行组件的ready,最后执行页面的onReady,这是页面中引入组件时组件的生命周期函数执行顺序。
lifetimes中的生命周期函数执行了,外层的生命周期函数没有执行,所有当两者同时存在时,lifetimes中的优先级要高。
这里组件中的pageLifetimes没有执行,不清楚具体原因,官网说是2.2.3版本以上支持,我在2.3.0的环境还是没有执行,不清楚具体原因,求解!

第二种情况,不引入lifetimes的生命周期函数,只使用外层的生命周期函数,执行结果如下图所示:

COMPONENT

可以看到,生命周期函数执行顺序没有变,外层的生命周期生效。

第三种情况,在B页面中使用两个组件,这里我把lifetimes中的created生命周期注释掉了,看生命周期的执行情况,这里组件1和组件2的代码相同,执行结果情况如下图:

image.png

从执行的结果来看,整个生命周期的执行顺序不变,只是要在每个阶段执行所有组件的相应生命周期,如上图,现行玩所有组件的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

发表评论

电子邮件地址不会被公开。 必填项已用*标注