天天看點

淺析微信小程式 App() 和 Page() 函數的内部實作

在小程式開發中,

App(...)

 和 

Page(...)

 是我們最熟悉也是最常用的兩個函數,今天我們就來分析一下它們的内部實作,以及調用時的初始化流程。

前一段時間,我們公衆号轉載了有贊技術團隊的

《從源碼看微信小程式啟動過程》 ,這篇文章記錄了小程式架構的基本代碼結構,啟動流程,以及程式執行個體化過程,非常值得反複閱讀。你也可以把本文了解為是這篇文章的讀後感或總結。

https://juejin.im/entry/5b345678518825749630f75e#%E6%A6%82%E8%A7%88 概覽

在微信開發者工具中,編譯運作你的小程式項目,然後打開控制台,輸入 

document

 并回車,就可以看到小程式運作時,WebView 加載的完整的 

page-frame.html

,如下圖:

淺析微信小程式 App() 和 Page() 函數的内部實作

通過分析這個 HTML 檔案,我們可以得到小程式的啟動執行流程大緻如下:

淺析微信小程式 App() 和 Page() 函數的内部實作

此圖來自上述文章,我們這裡不再重複贅述這些流程,下面我們來看一下其中的 

App()

Page()

 的細節。這兩個函數在小程式架構 

WAService.js

 中定義,并在 

app.js

 和每個頁面的 

page.js

 中進行調用執行個體化。

在微信開發者工具的控制台中執行 

openVendor()

 方法,可以打開小程式架構所在目錄,如下:

/Users/使用者名/Library/Application Support/微信web開發者工具/WeappVendor/基礎庫版本号目錄
           

本文以 

1.9.94

 基礎庫為例進行分析。

WAService.js

 檔案的結構如下:

;(function(global) {

    // WeixinJSBridge 的定義和加載

    // NativeBuffer 的定義和加載

    // wxConsole 的定義和加載

    // WeixinWorker 的定義和加載

    // Reporter 的定義和加載

    // __appServiceSDK__ 的定義和加載

    wx = __appServiceSDK__.wx,

    // exparser 的定義和加載

    // __virtualDOM__ 的定義和加載

    // __appServiceEngine__ 的定義和加載

    Page = __appServiceEngine__.Page,
    Component = __appServiceEngine__.Component,
    Behavior = __appServiceEngine__.Behavior,
    __webview_engine_version__ = .02,
    App = __appServiceEngine__.App,
    getApp = __appServiceEngine__.getApp,
    getCurrentPages = __appServiceEngine__.getCurrentPages,
    __createPluginGlobal = __appServiceEngine__.__createPluginGlobal,

    // __wxModule__ 的定義和加載

    definePlugin = __wxModule__.definePlugin,
    requirePlugin = __wxModule__.requirePlugin;

    // define 方法的定義

    // require 方法的定義

    global.App = App;
    global.Page = Page;
    global.Component = Component;
    global.Behavior = Behavior;
    global.__webview_engine_version__ = 0.02;
    global.getApp = getApp;
    global.getCurrentPages = getCurrentPages;
    global.wx = wx;
    global.definePlugin = __wxModule__.definePlugin;
    global.requirePlugin = __wxModule__.requirePlugin;

})(this);           

我們發現,

WAService.js

 中定義了 

WeixinJSBridge

wx

 這兩個基礎 API 集合,同時也包含的其他一些架構核心,如 

exparser

__virtualDOM__

__appServiceEngine__

 等。其中

__appServiceEngine__

 提供了架構最基本的對外接口,如 App,Page,Component,Behavior 等方法;

exparser

 提供了架構底層的能力,如執行個體化元件,資料變化監聽,View 層與邏輯層的互動等;

__virtualDOM__

 則起着連接配接 

__appServiceEngine__

exparser

 的作用,如對開發者傳入 Page 方法的對象進行格式化再傳入 

exparser

 的對應方法處理。(此段分析摘自上述文章)

由上可知,本文要分析的全局函數 

App()

Page()

 是對 

WAService.js

 中定義的 

__appServiceEngine__

 對象同名方法的引用。下面我們簡要分析一下它們的内部實作和初始化流程。

App() 和 getApp() 函數

根據微信小程式

開發文檔

App()

 函數用來注冊一個小程式,接收一個 

object

 對象參數,其指定小程式的生命周期函數等。我們從微信開發者工具的函數提示可以知道,

App()

 函數的聲明如下:

function App(options: _AppOptions): void           

對于入參 

object

 對象(_AppOptions)的屬性說明如下:

淺析微信小程式 App() 和 Page() 函數的内部實作

此外,全局的 

getApp()

 函數可以用來擷取到小程式執行個體,它的聲明如下:

function getApp(): object           

内部實作

在 

__appServiceEngine__

 對象中,對 

App

getApp

 屬性的定義如下:

// 其中的 t 就是 __appServiceEngine__ 對象

var i = n(17);

Object.defineProperty(t, "App", {

enumerable: !0,

get: function() {

return i.appHolder

}

}),

Object.defineProperty(t, "getApp", {

enumerable: !0,

get: function() {

return i.getApp

}

}),           

而這兩個屬性對應的實作分别為 

appHolder()

getApp()

 方法,定義如下:

l = void 0,

t.appHolder = (0, i.surroundByTryCatch)(function(e) {

l = new y(e)

}, "create app instance"),

t.getApp = function() {

return l

},           

由上可知,在 

appHolder()

 方法中,把外部傳入的 object 對象傳給 

y(...)

 方法進行初始化一個小程式執行個體對象,并把結果賦給變量 

l

 緩存起來,而在 

getApp()

 方法中則直接 

return l

,傳回目前小程式對象。

https://juejin.im/entry/5b345678518825749630f75e#App-%E5%AE%9E%E4%BE%8B%E5%88%9D%E5%A7%8B%E5%8C%96%E6%B5%81%E7%A8%8B App 執行個體初始化流程

在上述 

page-frame.html

 中,我們知道,在 

app.js

 被加載完後,小程式架構會立即執行 

require('app.js')

 進行注冊小程式執行個體,即對 

App()

 函數進行調用(開發者已經在 

app.js

 中定義好了入參對象),如下:

<script src="./app.js"></script>

<script>require("app.js")</script>           

App()

 函數中,最終會調用 

y(...)

 方法進行初始化,其中 

y(...)

 的定義比較長,我們這裡不再貼出代碼,詳情請自行查閱 

WAService.js

,它的處理流程如下:

  • 聲明 

    App.getCurrentPage

     方法将被廢棄,請使用 

    getCurrentPages()

     全局方法;
  • 綁定生命周期函數,即把外部入參對象定義的屬性綁定到小程式執行個體對象中,包括 

    onLaunch

    onShow

    onHide

    onUnlaunch

    onPageNotFound

  • 綁定開發者自定義的其他屬性(包括資料和方法),并校驗屬性名是否為 “getCurrentPage”,如果是則警告;
  • 根據外部是否有定義 

    onError

     屬性判斷是否注冊錯誤上報;
  • 檢查啟動參數(取自__wxConfig.appLaunchInfo)并依次調用 

    onLaunch

    onShow

     方法;
  • 注冊前背景切換回調 

    onShow

    onHide

  • 注冊找不到頁面的回調 

    onPageNotFound

  • 傳回執行個體給 

    App()

     函數進行緩存。

Page() 和 getCurrentPages() 函數

根據

文檔

Page()

 函數用來注冊一個頁面,接收一個 object 對象參數,其指定頁面的初始資料、生命周期函數、事件處理函數等。

Page()

function Page(page: PageOptions): void
           

object

 對象(PageOptions)的屬性說明如下:

淺析微信小程式 App() 和 Page() 函數的内部實作

此外,

getCurrentPages()

 函數用于擷取目前頁面棧的執行個體,以數組形式按棧的順序給出,第一個元素為首頁,最後一個元素為目前頁面。它的聲明如下:

function getCurrentPages(): object[]           

https://juejin.im/entry/5b345678518825749630f75e#%E5%86%85%E9%83%A8%E5%AE%9E%E7%8E%B0-1

同樣地,在 

__appServiceEngine__

Page

getCurrentPages

var r = n(2);

Object.defineProperty(t, "Page", {

enumerable: !0,

get: function() {

return r.pageHolder

}

}),

Object.defineProperty(t, "getCurrentPages", {

enumerable: !0,

get: function() {

return r.getCurrentPages

}

}),
           

pageHolder()

getCurrentPages()

var k = void 0, // 儲存目前顯示的頁面(棧頂)

x = [], // 儲存已加載過的頁面曆史棧數組

// 其中的 t 就是 __appServiceEngine__ 對象

t.getCurrentPage = function() {

return k

},

t.getCurrentPages = function() {

var e = [];

return x.forEach(function(t) {

e.push(t.page)

}),

e

},



M = {}, // 緩存所有已經注冊的頁面

t.pageHolder = function(e) {

if (!__wxRouteBegin) throw (0, f.error)("Page 注冊錯誤", "Please do not register multiple Pages in " + __wxRoute + ".js"),

new a.AppServiceEngineKnownError("Please do not register multiple Pages in " + __wxRoute + ".js");

__wxRouteBegin = !1;

var t = __wxRoute;

if (!A(t)) throw (0, f.error)("Page 注冊錯誤", __wxRoute + " has not been declared in app.json."),

new a.AppServiceEngineKnownError(__wxRoute + " has not been declared in app.json.");

var n = "undefined" != typeof __wxAppCode__ ? __wxAppCode__[t + ".json"] || {}: {};

if ("Object" !== (0, f.getDataType)(e)) throw (0, f.error)("Page 注冊錯誤", "Options is not object: " + JSON.stringify(e) + " in " + __wxRoute + ".js"),

new a.AppServiceEngineKnownError("Options is not object: " + JSON.stringify(e) + " in " + __wxRoute + ".js"); (0, f.info)("Register Page: " + t),

void 0 !== n.usingComponents ? (__virtualDOM__.Page(e), M[t] = exparser.Component._list[t]) : M[t] = e

},
           

分析上述代碼,我們可以總結 

pageHolder

 方法的處理流程如下:

  • 小程式在每加載一個頁面前,會先設定 

    __wxRouteBegin = true

    ,用于标記防重;
  • 判斷 

    __wxRouteBegin

     是否為 

    false

    ,如果是,則抛出多次調用 Page 注冊錯誤;
  • 設定 

    __wxRouteBegin

     為 

    false

    ,避免被後續代碼被重複執行;
  • 調用 

    A(...)

     方法檢查目前頁面是否在 

    app.json

     中定義,如果沒有,則抛出錯誤;
  • 檢查外部入參(PageOptions)是否為 Object 對象,如果不是,則抛出錯誤;
  • 判斷目前頁面是否使用了自定義元件(對于使用了自定義元件的 Page 對象會采用不同的配置),然後緩存目前 Page 的配置到 

    M

     對象中。

此外,我們可以發現,與 

App()

 不同的是,外部通過 

Page()

 函數傳入的(生命周期)代碼并不會在這裡被執行,而是等待頁面 Ready 并進入頁面進行執行個體化後才執行。

https://juejin.im/entry/5b345678518825749630f75e#%E9%A1%B5%E9%9D%A2%E5%88%9D%E5%A7%8B%E5%8C%96%E6%B5%81%E7%A8%8B 頁面初始化流程

同樣地,根據 

page-frame.html

 的加載順序,在 

app.js

 被加載并執行後,小程式之後會先依次按順序加載所有的自定義元件代碼(如果有)并自動注冊。自定義元件(Component)在小程式開發中具有重要地位,它可以豐富小程式的基礎功能,擁有的能力比 

Page

 更強大,是以實作也更加複雜,篇幅有限,我們後續再單獨寫文章進行分析。

在加載執行完自定義元件的代碼後,小程式緊接着會依次按順序加載每個頁面的代碼,并執行 

require(...)

 進行頁面注冊,如下:

<script>__wxRoute = "pages/index/index";__wxRouteBegin = true</script>

<script>__wxAppCurrentFile__ = "pages/index/index.js"</script>

<script src="./pages/index/index.js"></script>

<script>require("pages/index/index.js")</script>

<script>

if(__wxRouteBegin) {

console.group("Tue Jun 26 2018 17:53:09 GMT+0800 (CST) page 編譯錯誤")

console.error("pages/index/index.js 出現腳本錯誤或者未正确調用 Page()")

console.groupEnd()

}

</script>

<!-- 加載注冊下一個 Page -->           
  • __wxRoute

     為目前 Page 的路徑,設定 __wxRouteBegin 為 true;
  • __wxAppCurrentFile__

     為目前加載的檔案路徑;
  • 加載頁面代碼并執行進行注冊頁面(參考上述 

    pageHolder

     的處理流程);
  • __wxRouteBegin

    false

    ,來判斷該頁面是否被成功注冊(因為在 

    pageHolder

    方法中,成功執行是,會把 

    __wxRouteBegin

     置為 

    false

    );
  • 依次加載其他 Page;
  • 等待頁面 Ready 和 Page 執行個體化,page Load 由 wx.onAppRoute 事件觸發。

page-frame.html

 中,當 

head

 中的所有 JS 代碼都執行完畢後,會在 

body

 中觸發 

DOCUMENT_READY

 事件,如下:

<body>

<script>

if (document.readyState == 'complete') {

window.__global.alert('DOCUMENT_READY')

} else {

var fn = function(event) {

window.__global.alert('DOCUMENT_READY')

window.removeEventListener("load", fn)

}

window.addEventListener('load', fn)

}

</script>

</body>
           

在小程式架構 

WAService.js

 中,最終 

DOCUMENT_READY

 會轉化為 

wx.onAppRoute

 事件(邏輯待驗證),最終在 

wx.onAppRoute

 事件中進行頁面的執行個體化或者頁面切換。

PS:關于一個小程式頁面的完整初始化加載流程,我們将在下一篇文章中詳解。

https://juejin.im/entry/5b345678518825749630f75e#%E6%80%BB%E7%BB%93 總結

本文簡要地分析了 

App()

 、

getApp()

Page()

getCurrentPages()

 等幾個函數的内部實作,希望能讓你更好地了解小程式執行個體對象和頁面的加載過程,給你實際開發帶來幫助。最後,本文的内容隻是小程式底層架構的冰山一角,推薦你再細讀一下這篇文章

以及作者整理的思維導圖,相信你會有新的收獲。

文釋出時間為:2018年06月29日

原文作者:掘金

本文來源:

掘金 https://juejin.im/entry/5b2c4f426fb9a00e5f3e8d36

如需轉載請聯系原作者

繼續閱讀