从源码角度理解 wx.reLaunch 执行过程

2,857 阅读14分钟

文末是最终梳理的流程图,可直接略过正文看结果,戳这

相信老司机们看到这个标题就笑了,wx.reLaunch有啥好解释的,顾名思义就是:「重新启动小程序」。重启这个词,对于工程师最熟悉不过了,很多问题一出现,往往第一反应就是“重启”一下就能解决掉,能重启解决的问题,那都不是“大问题”。

wx.reLaunch === 重启?

那这样来理解wx.reLaunch的话,那就应该是“微信会 kill 掉当前这个小程序进程,然后像第一次加载小程序那样,重新打开小程序”。

如果是这样的话,那bug应该离你就不远了。事实并非如此。

微信文档怎么说

进一步,那不是这样的话,那我们查查微信文档怎么说的?wx.reLaunch(Object object)

关闭所有页面,打开到应用内的某个页面

看到这里,老司机们都拍了拍大腿,原来是这样:清空所有路由,并打开新的页面。我知道了,也很简单嘛。ok,理解了的话,那我再追问几个问题吧:

  1. 关闭所有页面会执行什么操作?
  2. 打开新页面的过程中,除了新页面 Page 的各生命周期外,全局 js 会重新执行吗(加载时即执行)?
  3. 如果 reLaunch 的目标页是分包内的页面呢?执行过程又是怎样的

还是自己试下吧

事实胜于雄辩,依赖文档来编程是合理的,但是如果我们要继续深入来探讨代码的执行,还是远远不够的。

我们先来写段代码片段测试一下,链接:developers.weixin.qq.com/s/tis8tVmX7… appId)。

有 p0 - p4 共 5 个页面,我们的测试路径是这样的,打开小程序-p0-p1-p2,然后 reLaunch 到 p3,看控制台输出:

image-20200607144912534

这里我们能看到,“关闭所有页面”,对应会依次会把当前页面栈的页面出栈,并触发相应的 onUnload回调。

就不耽误大家的时间,经过反复测试,最终得出在 wx.reLaunch的执行过程如下:

image-20200607171237505

源码验证准没错

其实到了这一步,相信大家对 wx.reLaunch有了进一步的认识,而不是像开始那样觉得就是单纯的重启了。但是到这里就结束了吗?如果说有一样东西能够 100% 让人信服,单纯测试是不够的,总可能会有遗漏的测试用例,无法保证结论的正确性。so...

Talk is cheap, show me your code.

我们下一步打算从源码层去看一看 wx.reLaunch到底做了些什么。

什么?你不知道源码怎么看... (嗯,网上其实有文章,介绍怎么去找到小程序框架层的源码)

我的方法是这样的:在控制台输入wx.reLaunch()并执行,我们看到会抛出一个错误,我们点开错误堆栈:

image-20200607150030545

WAService.js 就是小程序框架的源码了,cmd+f,点进去输入关键词reLaunch搜索,我们会找到关键代码,也就是搜索结果的 9/40 和 40/40,代码如下:

9/40

b = function(e) {
    var t;
    "active" === a.default.runningStatus || "ios" !== c.PLATFORM ? Object(u.beforeInvoke)("reLaunch", e, {
        url: ""
    }) && (!v("reLaunch", e) || (t = g("reLaunch", e, !1)) && (e.url = t,
    e.url = Object(i.encodeUrlQuery)(e.url),
    h("reLaunch", e.url, e) && (a.default.navigatorLock = !0,
    Object(u.invokeMethod)("reLaunch", e, {
        afterFail: function() {
            a.default.navigatorLock = !1
        }
    }),
    p.emit({
        type: "reLaunch",
        start: Date.now()
    })))) : Object(u.beforeInvokeFail)("reLaunch", e, "can not invoke reLaunch in background")
}

40/40

"reLaunch" === r || "autoReLaunch" === r ?
function(t, e, n, r, o, i) {
    __appServiceSDK__.traceBeginEvent("Framework", "onReLaunch");
    var a = !1;
    for ("reLaunch" === o && (a = !0); 0 < lt.length;) {
        var s = !0,
        a = a && (s = !1);
        Pt(lt[lt.length - 1], t, !1, s)
    }
    Object.keys(st).forEach(function(e) {
        Pt(st[e], t, !1, !0)
    }),
    le(t),
    jt(t, e, n, r, {
        isMainTabBarPage: xt({
            route: t
        }),
        initialRenderingCacheData: i
    }),
    __appServiceSDK__.traceEndEvent()
} (e, C, t, n, r, i)

然后,我们开启漫长的 debug 的过程,因为我们对其内部实现并不熟悉,所以最开始我们找到入口,然后逐步调试。

我们第一步其实就有发现:

"active" === a.default.runningStatus || "ios" !== c.PLATFORM
  ? ...
  : Object(u.beforeInvokeFail)("reLaunch", e, "can not invoke reLaunch in background")

这段代码其实一眼看上去就能猜测到,wx.reLaunch在非 iOS 设备下,如果小程序不在前台,那执行会报错。我们看社区里反馈了很多类似的问题,最常见的一个就是「在微信支付后为什么页面没有跳转」,其实都可以从这行代码找到问题的原因(developers.weixin.qq.com/community/d…

这里其实我们会想为什么微信会有这个限制,我猜测可能和 wx.reLaunch执行过程中,有一些操作在非 iOS 设备下是无法完成的,具体是什么我暂时还不知道。(在使用小程序过程中,我们会发现安卓下的小程序和微信进程是相互独立的,但是在 iOS 下,小程序进程和微信进程是同一个。参见小程序运行环境

我们继续往下,又发现一个关键信息:

image-20200607153005991

这就验证了如果 reLaunch 目标页是分包代码代码时, 会先加载分包代码的结论了。而且事实上继续往下单步执行,在控制台也会先执行 p4/index.js 中 Page 外面写的 console 语句。

接下来,我们先把我们之前发现的第 2 段关键代码打上断点,因为发现如果不断的话会直接执行掉,这里应该是被另外一层函数封装了,没进到里面的代码。我们接下来分析wx.reLaunch定义的核心代码会执行什么逻辑:

代码结构是非常清晰的,是一个 IIFE,函数主体被包裹在了 begin 和 end 中,赞一个。

"reLaunch" === r || "autoReLaunch" === r ?
function(t, e, n, r, o, i) {
    __appServiceSDK__.traceBeginEvent("Framework", "onReLaunch");
    // ...
    __appServiceSDK__.traceEndEvent()
} (e, C, t, n, r, i)

入参的 6 个变量值分别是:

image-20200607154311896

我们分为 5 句代码:

第一句:

var a = !1;

这里定义了一个局部变量 a,值为 false

第二句:

for ("reLaunch" === o && (a = !0); 0 < lt.length;) {
  var s = !0,
      a = a && (s = !1);
  Pt(lt[lt.length - 1], t, !1, s)
}

看到这里,我们会看到有 3 个变量,我们没办法直接看出分别代表什么:

  • s,局部变量,初始值为 true,如果 a 为 true 的话,s 会变成 false。s 作为第 4 个变量传入 Pt 函数中

  • lt,通过 watch,猜测 lt 即为页面栈

image-20200607154711182

  • Pt,直接通过 watch,没办法看出来这个函数是什么,不过我们可以点击下面的 [[FunctionLocation]] 查看函数定义的位置。

image-20200607154751591

function Pt(e, t, n, r) {
  __appServiceSDK__.traceBeginEvent("Framework", "unloadPage"),
    Ze(e.webviewId),
    e.page.__toRoute__ = t,
    e.page.__isBack__ = n,
    e.page.__notReportHide__ = r,
    e.page.__callPageLifeTime__("onUnload"),
    e.node && Y.destroy(e.page),
    Object(L.isDevTools)() && (delete __wxAppData[e.route],
                               __appServiceSDK__.publishUpdateAppData()),
    delete st[e.webviewId],
    (lt = lt.slice(0, lt.length - 1)).length ? gt(lt[lt.length - 1].route) : gt(""),
    ze("pageUnload", e.page),
    ze("leavePage", e.page),
    __appServiceSDK__.traceEndEvent(),
    __appServiceSDK__.uploadUserLogOnHide(e.route)
}

这里,我们就能知道了,Pt 函数做的事情就是对传入的页面执行其 onUnload生命周期函数。

这段代码是**对页面栈进行遍历,依次触发页面的onUnload**回调。

第三句:

Object.keys(st).forEach(function(e) {
  Pt(st[e], t, !1, !0)
}),

我们先看看 st 是什么?

image-20200607155319709

发现 st 是以 webviewId 为 key,以 page 对象及一些其他带有 page 特性的标识字段组合 为 value 的结构。

所以这段代码仍然是遍历页面(这里我理解是无序的,因为 Object.keys 首先就不能保证有序,参考Object.keys(..)对象属性的顺序?),依次触发其 onUnload回调。

这里先抛出两个问题:

  1. 第 2 句代码执行后,这段代码还有什么作用?
  2. 重复触发 onUnload,不出意外,框架内部会根据 webviewId 来判断能否执行。即 __callPageLifeTime__函数内部逻辑。

暂时先跳过,回到主流程。

第四句:

le(t)

同样的方式,我们找到 le 函数的源码:

// line 94280
var ue = !1;
function le(e) {
  var t = __appServiceSDK__.isIsolatedSubpackage(e);	// 是否独立分包
  !ue && t ? ge() : ue && !t && he(),
  ue = t
}

function he(t) {
	t = t || pe,
	__appServiceSDK__.emitIsloatedAppShow(t),
	de.forEach(function(e) {
		e.preventOnShow || e.app.onShow(t),
		e.preventOnShow = !1
	}),
	pe = t
}
function ge(t) {
	__appServiceSDK__.emitIsloatedAppHide(),
	de.forEach(function(e) {
		7 === __wxConfig.appType ? e.app.onHide(t) : e.app.onHide()
	})
}

我们找到微信文档对于独立分包的定义:独立分包。从独立分包中页面进入小程序时,不需要下载主包,可以很大程度上提升分包页面的启动速度。

配置方式是在分包配置里加上independent字段。因为我们这里没考虑到这种情况,感觉这里不会影响主流程,先跳过。

第五句:

jt(t, e, n, r, {
  isMainTabBarPage: xt({
    route: t
  }),
  initialRenderingCacheData: i
})

通过前面,我们不难推测出,这段代码应该是“打开目标页面”。通过参数发现,目标页面对于 tab 页和非 tab 页有区别对待,继续找到 jt 函数:

function jt(e, t, n, r, o) {
    var i = 4 < arguments.length && void 0 !== o ? o: {};
    __appServiceSDK__.traceBeginEvent("Framework", "openNewPage"),
    se(te);
    var a = ot;
    ot = void 0;
    var s = null;
    Ye.call(ct, e) ? s = ct[e] : console.info('Page "' + e + '" has not been registered yet.');	// 检查是否注册
    var c = bt(e);
    wt.newPageTime = Date.now(),
    gt(e);
    var u = c ? Y.create(n, e) : q.create(n, e, s || {}),	// 初始化 page
    l = u.page,
    d = b(r); (nt = {	// nt,新页面对象
        page: l,
        webviewId: n,
        route: e,
        rawPath: t,
        lastRoute: nt ? nt.route: "",
        lastQuery: nt ? nt.page.options: {},
        node: u.node
    }).isTabBarPage = xt(nt),	// 是否 tabBar
    nt.isMainTabBarPage = i.isMainTabBarPage || !1,	// 是否主 tabBar
    lt.push(nt),	// 页面栈
    l.__exitState__ = a;
    var f, p, h = u.node,
    g = {},
    v = !1;
    Object.keys(d).forEach(function(e) {
        exparser.Component.hasProperty(h, e) && (g[e] = decodeURIComponent(d[e]), v = !0)
    }),
    v && h.setData(g),
    __virtualDOM__.attachView(n),	// attachView
    /^__wx__\//.test(e) && (f = {},
    /^__wx__\/open-api-redirecting-page/.test(e) ? f = me || {}: !/^__wx__\/functional-page/.test(e) || (p = _e()) && p.functionalPage && (f = Object.assign({},
    p.functionalPage, {
        accountInfo: __wxConfig.accountInfo
    })), l.setData(f)),
    l.options = d,
    Lt(nt, n, wt.newPageTime, void 0, !1),	// Lt 函数
    Object(L.isDevTools)() && (__wxAppData[e] = l.data, __wxAppData[e].__webviewId__ = n, __appServiceSDK__.publishUpdateAppData()),
    i.initialRenderingCacheData && l.setData(i.initialRenderingCacheData);
    var _ = __appServiceSDK__._getOpenerEventChannel();
    _ && (nt.eventChannel = _),
    l.__callPageLifeTime__("onLoad", r),	// 1.触发 onLoad
    l.__callPageLifeTime__("onShow"),			// 2.触发 onShow
    st[n] = {
        page: l,
        route: e,
        rawPath: t,
        webviewId: n,
        statesData: null,
        node: c ? u.node: void 0
    },	// webviewId 为 key 的特殊结构
    ze("pageLoad", l),
    ze("enterPage", l),
    vt("appRoute2newPage", wt.appRouteTime, wt.newPageTime),
    __appServiceSDK__.traceEndEvent()
}

我们在里面发现了我们熟悉的 lt、st 变量,并且在函数内被初始化。其中有一个 Lt 函数,我们找到其函数定义。

var Lt = _(function(e, t, n, r, o) {
    __appServiceSDK__.traceBeginEvent("Framework", "publishInitData"),
    j("Update view with init data");
    var i = e.page,
    a = {};
    a.wechatLibVersion = ("undefined" != typeof __libVersionInfo__ ? __libVersionInfo__.version: "") || "",
    a.webviewId = t,
    a.enablePullUpRefresh = _t(i, "onReachBottom"),
    a.enablePageScroll = _t(i, "onPageScroll"),
    a.onReachBottomDistance = function(e) {
        try {
            if ("number" == typeof __wxConfig.page[e + ".html"].window.onReachBottomDistance) return __wxConfig.page[e + ".html"].window.onReachBottomDistance
        } catch(e) {
            return $.DEFAULT_ON_REACH_BOTTOM_DISTANCE
        }
        return $.DEFAULT_ON_REACH_BOTTOM_DISTANCE
    } (i.__route__),
    a.statesData = r,
    a.scene = rt,
    a.route = i.__route__,
    a.query = i.options,
    a.lastRoute = e.lastRoute,
    a.lastQuery = e.lastQuery,
    a.wxConfig = {
        accountInfo: __wxConfig && __wxConfig.accountInfo || {},
        appContactInfo: __wxConfig && __wxConfig.appContactInfo || {},
        appLaunchInfo: __wxConfig && __wxConfig.appLaunchInfo || {},
        plugins: __wxConfig && __wxConfig.plugins || {}
    },
    a.windowConfig = __wxConfig && __wxConfig.page && __wxConfig.page[i.__route__ + ".html"] && __wxConfig.page[i.__route__ + ".html"].window || {},
    a.debug = __wxConfig && __wxConfig.debug,
    a.appId = __wxConfig && __wxConfig.accountInfo && __wxConfig.accountInfo.appId,
    a.appLaunchTime = wt.appLaunchTime,
    a.appFgTime = wt.appFgTime,
    a.isTabBarPage = e.isTabBarPage,
    a.isMainTabBarPage = e.isMainTabBarPage,
    a.navigationStyle = __wxConfig && __wxConfig.global && __wxConfig.global.window && __wxConfig.global.window.navigationStyle,
    a.packageType = function(e) {
        if (__wxConfig && __wxConfig.subPackages && __wxConfig.subPackages.length) {
            for (var t = 0; t < __wxConfig.subPackages.length; t++) {
                var n = __wxConfig.subPackages[t];
                if (0 === e.indexOf(n.root)) return n.independent ? "independent": "normal"
            }
            return "main"
        }
        return "none"
    } (i.__route__),
    a.needGetSubjectInfo = !At.isInit,
    a.subPackages = __wxConfig.subPackages,
    a.perfData = (ie[ne] = Date.now(), ie[X] = ae ? 1 : 0, ie[oe] = __wxConfig.isSubContext ? 1 : 0, ie[Q] = __wxConfig.onReadyStart, ie[re] = __wxConfig.onReadyEnd || 0, ae = !1, ie),
    a.isReload = o,
    a.adInfo = {
        preloadVideoAdUnitIds: __appServiceSDK__.getPreloadVideoAdUnitIds()
    },
    a.permissionBytes = __appServiceSDK__.getPermissionBytes(),
    a.fontFaceRecords = __appServiceSDK__.fontFaceRecords,
    a.fontSizeSetting = Object(L.getCachedSystemInfo)().fontSizeSetting;
    var s = {
        ext: a,
        options: {
            firstRender: !0,
            timestamp: n,
            path: i.__route__
        }
    };
    if (r) {
        var c = JSON.stringify(s),
        u = c.length;
        if (262144 < u) {
            for (var l = [], d = 0; d < u;) l.push(c.substr(d, 262144)),
            d += 262144;
            for (var f = ++$e,
            p = 0,
            h = l.length; p < h; p++) qe.emit({
                isSplitData: !0,
                splitInfo: {
                    id: f,
                    index: p + 1,
                    total: h,
                    data: l[p]
                }
            },
            t);
            return ze("pageReady", i),
            void __appServiceSDK__.traceEndEvent()
        }
    }
    qe.emit(s, t),	// qe 函数,s 是组装 page 信息的对象
    ze("pageReady", i),
    __appServiceSDK__.traceEndEvent()
})

qe 函数:

var qe = function() {
    function e() {
        Object(g.
    default)(this, e)
    }
    return Object(r.
default)(e, null, [{
        key: "emit",
        value: function(e, t, n) {
            __appServiceSDK__.invokeWebviewMethod("appDataChange", e, [t], n)
        }
    }]),
    e
} ();

搜索 appDataChange,找不到其他地方定义。我们找到 __appServiceSDK__.invokeWebviewMethod 的定义:

function(e, t, n) {
    n.r(t),
    n.d(t, "invokeWebviewMethod",
    function() {
        return r
    });
    var c = n(0),
    u = n(3),
    l = 0,
    d = [],
    r = function(e, t, n, r) {
        var o = 1 < arguments.length && void 0 !== t ? t: {},
        i = 2 < arguments.length ? n: void 0,
        a = 3 < arguments.length ? r: void 0,
        s = l++;
        d[s] = a,
        Object(c.publish)("invokeWebviewMethod", {	// 发布
            name: e,
            args: o,
            callbackId: s
        },
        void 0 === i ? [u.
    default.currentWebviewId]:
        i)
    };
    Object(c.subscribe)("callbackWebviewMethod",	// 订阅
    function(e) {
        var t = e.res,
        n = e.callbackId,
        r = d[n];
        delete d[n],
        r && r(t)	// 执行
    })
}

最终发现以下代码会被执行:

__appServiceSDK__.onWebviewEvent(_(function(e) {
    __appServiceSDK__.traceBeginEvent("Framework", "onWebviewEvent");
    var t = e.webviewId,
    n = e.eventName,
    r = e.data,
    o = function(e, t, n, r) {
        if (Ye.call(st, e)) {
            var o = st[e],
            i = o.page;
            if (n === $.DOM_READY_EVENT) return wt.pageReadyTime = Date.now(),
            j("Invoke event onReady in page: " + o.route),
            i.__callPageLifeTime__("onReady"),	// 3.触发 onReady
            void vt("newPage2pageReady", wt.newPageTime, wt.pageReadyTime);
            if (r._requireActive) {
                var a = lt[lt.length - 1];
                if (!a || a.webviewId !== e) return
            }
            if (r._relatedInfo && F.DisplayReporter.setEventRelatedInfo(r._relatedInfo), t) {
                var s = __virtualDOM__.getNodeById(t, e);
                if (!s) return;
                var c = exparser.Element.getMethodCaller(s);
                return j("Invoke event " + n + " in component: " + s.is),
                _t(c, n) ? tt(c, n, r) : void x("事件警告", "Do not have " + n + " handler in component: " + s.is + ". Please make sure that " + n + " handler has been defined in " + s.is + ".")
            }
            if (j("Invoke event " + n + " in page: " + o.route), _t(i, n)) return tt(i, n, r);
            x("事件警告", "Do not have " + n + " handler in current page: " + o.route + ". Please make sure that " + n + " handler has been defined in " + o.route + ", or " + o.route + " has been added into app.json")
        }
    } (t, e.nodeId, n, r);
    return __appServiceSDK__.traceEndEvent(),
    o
},
"onWebviewEvent"))

说实话,这段代码没太看懂,对着 Page 的 生命周期 一起看理解起来会更清晰点,这里还是回到最开始,看传入的 isMainTabBarPage字段做了什么操作。

我们回到 jt 函数,发现关键语句:

var u = c ? Y.create(n, e) : q.create(n, e, s || {})	// 初始化 page

Y:

K = ["onLoad", "onReady", "onShow", "onRouteEnd", "onHide", "onUnload", "onResize"],
J = __appServiceSDK__.getLogManager(),
Y = function() {
    function e() {
        Object(g.
    default)(this, e)
    }
    return Object(r.
default)(e, null, [{
        key: "create",
        value: function(d, e) {
            var f = __virtualDOM__.addView(d, e),
            p = exparser.Element.getMethodCaller(f),
            u = __virtualDOM__.getOwnerPluginAppId(p);
            if (p.__wxWebviewId__ = d, p.__route__ = e, p.route = e, p.__displayReporter = new F.DisplayReporter(e, 2), f.__customConstructor__ === __virtualDOM__.Page) {
                var t = f.getRootBehavior().methods,
                n = p.__freeData__;
                for (var r in t) p[r] = t[r].bind(p);
                for (var o in n) p[o] = b(n[o])
            }
            var h = __appServiceSDK__.getSystemInfoSync().deviceOrientation;
            p.__callPageLifeTime__ = function(e) {
                var t = this[e] || I;
                Reporter.__route__ = this.__route__,
                Reporter.__method__ = e;
                for (var n, r, o, i, a, s = arguments.length,
                c = new Array(1 < s ? s - 1 : 0), u = 1; u < s; u++) c[u - 1] = arguments[u];
                "onLoad" === e && (n = p.__displayReporter).setQuery.apply(n, c),
                "onShow" === e ? (p.__displayReporter.reportShowPage(), Object(F.checkWebviewAlive)(d)) : "onReady" === e ? p.__displayReporter.setReadyTime(Date.now()) : "onHide" === e || "onUnload" === e ? (r = this.__toRoute__, o = this.__isBack__, i = this.__notReportHide__, delete this.__toRoute__, delete this.__isBack__, delete this.__notReportHide__, i || p.__displayReporter.reportHidePage(r, o), Object(F.stopCheckWebviewAlive)(d)) : "onResize" === e && (a = c[0] || {},
                h !== a.deviceOrientation && (h = a.deviceOrientation, p.__displayReporter.addOrientationChangeCount())),
                "onShow" === e ? f.triggerPageLifeTime("show", c) : "onHide" === e ? f.triggerPageLifeTime("hide", c) : "onResize" === e && f.triggerPageLifeTime("resize", c),
                j(this.__route__ + ": " + e + " have been invoked"),
                __appServiceSDK__.traceBeginEvent("LifeCycle", "Page." + e);
                var l = t.apply(this, c);
                return __appServiceSDK__.traceEndEvent(),
                Reporter.__route__ = Reporter.__method__ = "",
                l
            },
            K.forEach(function(s) {
                var c = p[s];
                p[s] = function() {
                    var e, t = c || I;
                    try {
                        for (var n = Date.now(), r = arguments.length, o = new Array(r), i = 0; i < r; i++) o[i] = arguments[i];
                        e = t.apply(this, o);
                        var a = Date.now() - n;
                        1e3 < a && Reporter.slowReport({
                            key: "pageInvoke",
                            cost: a,
                            extend: 'at "' + this.__route__ + '" page lifeCycleMethod ' + s + " function"
                        }),
                        J && J.logApiInvoke && J.log("page " + this.__route__ + " " + s + " have been invoked"),
                        __appServiceSDK__.nativeConsole.info("component page " + this.__route__ + " " + s + " have been invoked")
                    } catch(e) {
                        Reporter.thirdErrorReport({
                            source: u,
                            error: e,
                            extend: 'at "' + this.__route__ + '" page lifeCycleMethod ' + s + " function"
                        })
                    }
                    return e
                }.bind(p)
            });
            var i = "function" == typeof p.onShareAppMessage;
            i && __appServiceSDK__.showShareMenu();
            var a = "function" == typeof p.onShareTimeline;
            return i && a && __appServiceSDK__.showShareTimelineMenu(),
            {
                page: p,
                node: f
            }
        }
    },
    {
        key: "destroy",
        value: function(e) {
            __virtualDOM__.removeView(e.__wxWebviewId__)
        }
    }]),
    e
} ()

q:

var B = Object.assign,
L = n(4),
F = n(5),
W = ["onLoad", "onReady", "onShow", "onRouteEnd", "onHide", "onUnload", "onResize"],
U = function(e) {
    for (var t = 0; t < W.length; ++t) if (W[t] === e) return ! 0;
    return "data" === e
},
V = ["__wxWebviewId__", "__route__"],
G = ["route"],
z = function(e) {
    return - 1 !== V.indexOf(e)
},
H = __appServiceSDK__.getLogManager(),
q = function() {
    function h() {
        var t = this,
        c = 0 < arguments.length && void 0 !== arguments[0] ? arguments[0] : {},
        d = 1 < arguments.length ? arguments[1] : void 0,
        e = 2 < arguments.length ? arguments[2] : void 0;
        Object(g.
    default)(this, h);
        var n = {
            __wxWebviewId__: d,
            __route__: e
        };
        V.forEach(function(e) {
            Object.defineProperty(t, e, {
                set: function() {
                    x("关键字保护", "should not change the protected attribute " + e)
                },
                get: function() {
                    return n[e]
                }
            })
        });
        var r = __virtualDOM__.addView(d, e),
        o = exparser.Element.getMethodCaller(r),
        u = __virtualDOM__.getOwnerPluginAppId(o);
        this.__wxExparserNode__ = r,
        this.__wxComponentInst__ = o,
        exparser.Element.setMethodCaller(r, this),
        c.data = c.data || {},
        T(c.data) || A("Page data error", "Page.data must be an object");
        var i = JSON.stringify(c.data);
        this.data = JSON.parse(i),
        this.__viewData__ = JSON.parse(i),
        this.__displayReporter = new F.DisplayReporter(e, 1);
        var f = __appServiceSDK__.getSystemInfoSync().deviceOrientation;
        this.__callPageLifeTime__ = function(e) {
            var t = (this[e] || I).bind(this);
            Reporter.__route__ = this.__route__,
            Reporter.__method__ = e;
            for (var n, r, o, i, a, s = arguments.length,
            c = new Array(1 < s ? s - 1 : 0), u = 1; u < s; u++) c[u - 1] = arguments[u];
            "onLoad" === e && (n = this.__displayReporter).setQuery.apply(n, c),
            "onShow" === e ? (this.__displayReporter.reportShowPage(), Object(F.checkWebviewAlive)(d)) : "onReady" === e ? this.__displayReporter.setReadyTime(Date.now()) : "onHide" === e || "onUnload" === e ? (r = this.__toRoute__, o = this.__isBack__, i = this.__notReportHide__, delete this.__toRoute__, delete this.__isBack__, delete this.__notReportHide__, i || this.__displayReporter.reportHidePage(r, o), Object(F.stopCheckWebviewAlive)(d)) : "onResize" === e && (a = c[0] || {},
            f !== a.deviceOrientation && (f = a.deviceOrientation, this.__displayReporter.addOrientationChangeCount())),
            j(this.__route__ + ": " + e + " have been invoked"),
            __appServiceSDK__.traceBeginEvent("LifeCycle", "Page." + e);
            var l = t.apply(this, c);
            return __appServiceSDK__.traceEndEvent(),
            Reporter.__route__ = Reporter.__method__ = "",
            l
        },
        W.forEach(function(s) {
            t[s] = function() {
                var e, t = (c[s] || I).bind(this);
                try {
                    for (var n = Date.now(), r = arguments.length, o = new Array(r), i = 0; i < r; i++) o[i] = arguments[i];
                    e = t.apply(this, o);
                    var a = Date.now() - n;
                    1e3 < a && Reporter.slowReport({
                        key: "pageInvoke",
                        cost: a,
                        extend: "at " + this.__route__ + " page lifeCycleMethod " + s + " function"
                    }),
                    H && H.logApiInvoke && H.log("page " + this.__route__ + " " + s + " have been invoked"),
                    __appServiceSDK__.nativeConsole.info("page " + this.__route__ + " " + s + " have been invoked")
                } catch(e) {
                    Reporter.thirdErrorReport({
                        source: u,
                        error: e,
                        extend: "at " + this.__route__ + " page lifeCycleMethod " + s + " function"
                    })
                }
                return e
            }.bind(t)
        });
        for (var a in c) !
        function(a) {
            z(a) ? x("关键字保护", "Page's " + a + " is write-protected") : U(a) || ("Function" === k(c[a]) ? t[a] = function() {
                var e;
                Reporter.__route__ = this.__route__,
                Reporter.__method__ = a,
                __appServiceSDK__.traceBeginEvent("User Script", "Page." + a);
                try {
                    for (var t = Date.now(), n = arguments.length, r = new Array(n), o = 0; o < n; o++) r[o] = arguments[o];
                    e = c[a].apply(this, r);
                    var i = Date.now() - t;
                    1e3 < i && Reporter.slowReport({
                        key: "pageInvoke",
                        cost: i,
                        extend: "at " + this.__route__ + " page " + a + " function"
                    })
                } catch(e) {
                    Reporter.thirdErrorReport({
                        source: u,
                        error: e,
                        extend: "at " + this.__route__ + " page " + a + " function"
                    })
                }
                return __appServiceSDK__.traceEndEvent(),
                Reporter.__route__ = Reporter.__method__ = "",
                e
            }.bind(t) : t[a] = b(c[a]))
        } (a);
        var s = {
            route: e
        };
        G.forEach(function(e) {
            Object.prototype.hasOwnProperty.call(t, e) || (t[e] = s[e])
        });
        var l = "function" == typeof c.onShareAppMessage;
        l && __appServiceSDK__.showShareMenu();
        var p = "function" == typeof c.onShareTimeline;
        l && p && __appServiceSDK__.showShareTimelineMenu()
    }
    return Object(r.
default)(h, null, [{
        key: "create",
        value: function(e, t, n) {
            var r = new h(n, e, t),
            o = r.__wxExparserNode__;
            return delete r.__wxExparserNode__,
            {
                page: r,
                node: o
            }
        }
    },
    {
        key: "destroy",
        value: function(e) {
            __virtualDOM__.removeView(e.__wxWebviewId__)
        }
    }]),
    Object(r.
default)(h, [{
        key: "setData",
        value: function(c, e) {
            var u = this;
            try {
                var t = k(c);
                if ("Object" !== t) return void A("类型错误", "setData accepts an Object rather than some " + t);
                Object.keys(c).forEach(function(e) {
                    void 0 === c[e] && A("Page setData warning", 'Setting data field "' + e + '" to undefined is invalid.');
                    var t, n, r, o = M(e),
                    i = R(u.data, o),
                    a = i.obj,
                    s = i.key;
                    a && (a[s] = b(c[e])),
                    void 0 !== c[e] && (n = (t = R(u.__viewData__, o)).obj, r = t.key, n && (n[r] = b(c[e])))
                }),
                __appServiceSDK__.traceBeginEvent("Framework", "DataEmitter::emit"),
                this.__wxComponentInst__.setData(JSON.parse(JSON.stringify(c)), e),
                __appServiceSDK__.traceEndEvent()
            } catch(e) {
                v(e)
            }
        }
    },
    {
        key: "pageScrollTo",
        value: function(e) {
            __appServiceSDK__.publishPageScrollTo(e, [this.__wxWebviewId__])
        }
    }]),
    h
} ()

仍然没有发现 isMainTabBarPage关键字。

以上,第五步虽然没找到我们想找到的逻辑,但是发现其实 jt 函数其实和其他路由函数执行的是同一段逻辑,不影响我们对 wx.reLaunch执行过程的分析。

结论

最终,结合源码,我们得出wx.reLaunch的最终执行过程如下图所示:

image-20200607202125712
关于 wx.reLaunch的执行过程,额外提出几个点需要注意:

  1. wx.reLaunch真正的逻辑是清空路由,再打开新页面,并不是传统意义上的“重启”;
  2. wx.reLaunch只会影响小程序各生命周期(回调)的执行,全局 js 代码在小程序加载时执行,分包中的全局 js 代码在分包加载时执行;
  3. wx.reLaunch的目标页在分包内,且分包未加载过时,会先加载分包代码,再执行后续逻辑(unUnload + openNewPage);
  4. wx.reLaunch在非 iOS 设备中,如果小程序不在前台时,执行会报错,导致无法跳转。

除此之外,再额外说几个在 debug 过程中的总结的小 tips:

1、如果在代码里不太好看一些变量或表达式的值,可以复制下来贴在 watch 里。

2、发现一些 function 执行了,但是不知道函数定义的位置位置,可以先将其放进 watch 里,等其有值的时候点开会有一个路径点击开就到了函数定义的位置了。

3、不要无脑 debug,带着疑问,先在脑子里假设出你推测或者认为的一些结论,用 debug 去验证,否则中间很难发现关键信息。其实本文的成因并非是我真的想去深入了解下 wx.reLaunch的执行过程,而是因为对他的理解有偏差,在排查线上 bug 时产生了一些自己无法理解的现象。