從原始碼看微信小程式啟動過程

有贊技術發表於2018-05-14

一、寫作背景

接觸小程式一年多,真實體驗就是小程式開發門檻相對而言確實比較低。不過小程式的開發方式,一直是開發者吐槽的,如習慣了 Vue,React 開發的開發者經常會吐槽小程式一個 Page 必須由多個檔案組成,元件化支援不完善或者說不能非常愉快的開發元件。在以前小專案中沒太大感覺,從加入有贊,參與有贊微商城小程式的開發,是真切的體會到對於大型小程式專案開發的複雜性。

有贊從微信小程式內測就開始開發小程式,在不支援自定義元件的時代,只能通過 import 的形式拆分模組或實現元件。在業務複雜的頁面,可能會 import 非常多的模組,而相應的 wxss 也需要 import 樣式,除了操作繁瑣,有時候也難免遺漏。

作為開發者,我們當然希望可以讓工作更簡單,更愉快,也希望改善我們的開發方式。所以希望能夠更瞭解微信小程式框架,減少不必要的試錯,於是有了一次對小程式框架的 debug 之旅。(基礎庫 1.9.93)

通過三週空餘時間的 debug,也算對小程式框架有了一些淺顯的認識,達到了最初的目的;對小程式啟動,例項,執行等有了真切的體會。這篇文章記錄了小程式框架的基本程式碼結構,啟動流程,以及程式例項化過程。

本文的目的是希望把我看到的分享給對小程式感興趣或者正在開發小程式的讀者,主要解答“框架對傳入的物件等到底做了什麼”。

二、從啟動流程一窺小程式框架細節

在開發者工具中使用 help() 方法,可以檢視一些指令和方法。使用其中的 openVendor 方法可以開啟微信開發者工具在小程式框架所在目錄。其中以包括以基礎庫命名的目錄和其他幫助檔案,如其中有兩個工具 wcc,wcsc。wcc 可把 wxml 轉換為對應的 JS 函式 —— $gwx(path, global),wcsc 可將 wxss 轉換為 css。而基礎庫目錄包括 WAService.js 和 WAWebview.js 檔案。小程式框架在開發者工具中以 WAService.js 命名(WAWebview.js 不知其作用,聽說在真機環境使用該檔案)。

在開發中工具命令列使用 document.head 可以檢視到小程式的啟動流程大致如下:

從原始碼看微信小程式啟動過程
以小節的方式分別介紹這些流程,小程式是如何處理的(小節編號與圖中編號相同)。

1、初始化全域性變數

下圖是小程式啟動是初始化的一些全域性的變數:

從原始碼看微信小程式啟動過程

那些使用“__”開頭,未在文件中提及可使用變數是不建議使用的,__wxAppCode__ 在開發者工具中分為兩類值,json 型別和 wxml 型別。以 .json 結尾的,其 key 值為開發者程式碼中對應的 json 檔案的內容,.wxml 結尾的,其 key 值為通過呼叫 $gwx('./pages/example/index.wxml') 將得到一個可執行函式,通過呼叫這個函式可得到一個標識節點關係的 JSON 樹。

從原始碼看微信小程式啟動過程

2、載入框架(WAService.js)

使用工具對 WAService.js 進行格式化後進行 debug。可以發現小程式框架大致由: WeixinJSBridgeNativeBufferwxConsoleWeixinWorkerJavaScript 相容(這部分為猜測)、 Reporterwxexparser__virtualDOM____appServiceEngine__ 幾部分組成。

其中除了 wxWeixinJSBridge 這兩個基礎 API 集合,exparser, __virtualDOM__, __appServiceEngine__ 這三部分作為框架的核心,__appServiceEngine__ 提供了框架最基本的介面如 App,Page,Component;exparser 提供了框架底層的能力,如例項化元件,資料變化監聽,view 層與邏輯層的互動等;而 __virtualDOM__ 則起著連結 __appServiceEngine__exparser 的作用,如對開發者傳入 Page 方法的物件進行格式化再傳入 exparser 的對應方法處理。

框架對外暴露了以下API:Behavior,App,Page,Component,getApp,getCurrentPages,definePlugin,requirePlugin,wx。

3、業務程式碼的載入

在小程式中,開發者的 JavaScript 程式碼會被打包為

define('xxx.js', function(require, module, exports, window, document, frames, self, location, navigator, localStorage, history, Caches, screen, alert, confirm, prompt, fetch, XMLHttpRequest, WebSocket, webkit, WeixinJSCore, Reporter, print, WeixinJSBridge) {
  'use strict';

  // your code
})
複製程式碼

這裡的 define 是在框架中定義的方法,在框架中提供了兩個方法:require 和 define 用來定義和使用業務程式碼。其方式有些像 AMD 規範介面,通過 define 定義一個模組,使用 require 來應用一個模組。但是也有很大區別,首先 define 限制了模組可使用的其他模組,如 window,document;其次 require 在使用模組時只會傳入 require 和 module,也就是說引數中的其他模組在定義的模組中都是 undefined,這也是不能在開發者工具中獲取一些瀏覽器環境物件的原因。

在小程式中,JavaScript 程式碼的載入方式和在瀏覽器中也有些不同,其載入順序是首先載入專案中其他 js 檔案(非註冊程式和註冊頁面的 js 檔案),其次是註冊程式的 app.js,然後是自定義元件 js 檔案,最後才是註冊頁面的 js 程式碼。而且小程式對於在 app.js 以及註冊頁面的 js 程式碼都會載入完成後立即使用 require 方法執行模組中的程式。其他的程式碼則需要在程式中使用 require 方法才會被執行。

下面詳細介紹了 app.js,自定義元件,頁面 js 程式碼的處理流程。

4、載入 app.js 與註冊程式

在 app.js 載入完成後,小程式會使用 require('app.js') 註冊程式,即對 App 方法進行呼叫,App 方法是對 __appServiceEngine__.App 方法的引用。

下圖是框架對於 App 方法呼叫時的處理流程:

從原始碼看微信小程式啟動過程

App 方法根據傳入的物件例項化一個 app 例項,其生命週期函式 onLaunch 和 onShow 因為使用不同的方式獲取 options的引數。在有些需要根據場景值來實現需求的,或許使用 onShow 中的場景值更合適。

在實際開發過程中發現,在微信頂部喚起小程式和在小程式列表喚起的 options 也是不一樣的。在該案例中通過點選分享的小程式進入後,關閉小程式,再通過不同方式進入小程式,通過頂部喚起的還是 options 的 path 屬性還是分享出來的 path,但是通過列表中開啟直接回到了首頁,這裡 App 中的 onShow 就會獲取到不同的 options。

5、載入自定義元件程式碼以及註冊自定義元件

自定義元件在 app.js 之後被載入,小程式會在這個過程中載入完所有的自定義元件(分包中自定義元件沒有有測試過),並且是載入完成後自動註冊,只有註冊完成後才會載入下一個自定義元件的程式碼。

下圖是框架對於 Component 方法處理流程:

從原始碼看微信小程式啟動過程

圖中介紹了框架如何對傳入 Component 方法的物件的處理,其後面還有很多深入的對於元件例項化的步驟沒有在圖中表示出來,具體可以在文章最後的附件中檢視。

自定義元件在小程式中越來越完善,其擁有的能力也比 Page 更強大,而後面會提到在使用自定義元件的 Page 中,Page 例項也會使用和自定義元件一樣的例項化方式,也就是說,他擁有和自定義元件一樣的能力。

6、載入頁面程式碼和註冊頁面

載入頁面程式碼的處理流程和載入自定義元件一樣,都是載入完成後先註冊頁面,然後才會載入下一個頁面。

下圖是註冊一個頁面時框架對於 Page 方法的處理流程:

Page 執行流程

Page 方法會根據是否使用自定義元件做不同的處理。使用自定義元件的 page 物件會被處理為和自定義元件的結構,並在頁面例項化時使用不同的處理流程進行例項化。當然對於開發而言沒任何不同。

從圖中可以發現 Page 傳入的(生命週期)程式碼並不會在這裡被執行,可以通過下面小節瞭解 Page 例項化的詳細過程。

7、等待頁面 Ready 和 Page 例項化

還記得上面介紹的啟動流程中最後一步等待頁面 Ready?嚴格來講是等待瀏覽器 Ready,小程式雖然有部分原生的元件,不過本質上還是一個 web 程式。

在小程式中切換頁面或開啟頁面時會觸發 onAppRoute 事件,小程式框架通過 wx.onAppRoute 註冊頁面切換的處理程式,在所有程式就緒後,以 entryPagePath 作為入口使用 appLaunch 的方式進入頁面。

下圖是處理導航的程式流程:

從原始碼看微信小程式啟動過程

從圖中可以看出頁面的例項化是在進入頁面時進行,下圖是具體的例項化過程:

從原始碼看微信小程式啟動過程

下圖是最終可得到 Page 例項:

從原始碼看微信小程式啟動過程

可以發現其中多了 onRouteEnd API,實際該介面不會被呼叫。其中以 component 標記的表示只有在使用了自定義元件時才會有的方法和屬性。在前面第 5 小節提到了對於使用自定義元件的頁面會按照自定義元件方式解析,這些屬性和方法與自定義元件表現一致。

8、關於 setData

小程式框架是一個以資料驅動的框架,當然不能少了對他如何實現資料繫結的探索,下圖是 Page 例項的 setData 執行流程:

從原始碼看微信小程式啟動過程

其中 component:setData 表示使用自定義元件的 Page 例項的 setData 方法。

三、寫在最後

這是一次不完全的小程式框架探索,是在微信開發工具中 debug 的結果。雖然對於實際開發沒有什麼太大的幫助,但是對框架如何對開發的 js 程式碼進行處理有了一個很明確的認識,在使用一些 js 特性時可以有明確的感知。如果你還疑惑“小程式框架對傳入的物件等到底做了什麼”那一定是我表達能力太差,說聲對不起。

通過這一次 debug ,也給我引入了新的問題,還希望能夠有更多的討論:

  • 自定義元件太多啟動時會耗時處理自定義元件
  • 檔案太多會耗時讀檔案
  • 合理的設計分包很重要

一份在除錯過程中的筆記 小程式框架不完全分析.xmind,如果看官有興趣可以下載看看。當然最後對於框架中已有的能力,還是分層希望微信可以開放更多穩定的介面,並在文件中告知開發者,讓開發變得簡單一些。

從原始碼看微信小程式啟動過程

相關文章