微信小程序架構分析 (中)

本文探討一下小程序的 view 模塊和 service 模塊是如何構成的。 javascript

打開微信 web 開發者工具,而後輸入 openVendor() 便會打開 WeappVendor這個目錄,這裏包含了 view 模塊和 service 模塊使用的幾個核心文件:css

  • wcc 可執行程序,用於將 wxml 轉爲 view 模塊使用的 js 代碼,使用方式爲wcc xxx.wxmlhtml

  • wcsc 可執行程序,用於將 wxss 轉爲 view 模塊使用的 css 代碼,使用方式爲 wcsc xxx.wxssjava

  • WAService.js 提供 service 模塊大部分功能,下面會有詳細介紹c++

  • WAWebview.js 提供 view 模塊大部分功能,下面會有詳細介紹git

view 頁面詳解

view 頁面的 template 以下:github

<!DOCTYPE html>
<html lang="zh-CN">
<head>
  <link href="https://res.wx.qq.com/mpres/htmledition/images/favicon218877.ico" rel="Shortcut Icon">
  <meta charset="UTF-8" />
  <meta name="viewport" content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0" />

  <script>
    var __webviewId__;
  </script>

  <!-- percodes -->

  <!--{{WAWebview}}-->

  <!--{{reportSDK}}-->

  <!--{{webviewSDK}}-->

  <!--{{exparser}}-->

  <!--{{components_js}}-->

  <!--{{virtual_dom}}-->

  <!--{{components_css}}-->

  <!--{{allWXML}}-->

  <!--{{eruda}}-->

  <!--{{style}}-->

  <!--{{currentstyle}}-->

  <!--{{generateFunc}}-->
</head>

<body>
  <div></div>
</body>

</html>

其中 <!-- percodes --> 會在 dev 模式開啓後被替換爲一個時間錨點,例如:web

<script>var pageFrameStartTime = new Date();</script>

<!--{{WAWebview}}--> 會被 WAWebview.js 內代碼替換算法

<!--{{WAWebview}}--> 到 <!--{{generateFunc}}--> 之間暫時沒有被使用到小程序

<!--{{generateFunc}}--> 會被 wcc 命令生成後的 js 代碼替換

除了上面這些,頁面上還會被插入頁面和應用的 style 標籤,如:

<link rel="stylesheet" type="text/css" href="index.wxss">

這裏的 wxss 文件包含的是原始 wxss 文件轉換後的 css

以及生成 DOM 的啓動腳本:

<script>
  document.dispatchEvent(new CustomEvent("generateFuncReady", {
    detail: {
      generateFunc: $gwx('./page/index.wxml')
    }
  }))
</script>

WAWebview.js 文件中的各個模塊(行號爲 jsbeautify 以後代碼行號,開發者工具版本:092300):

  • 1-77 行: WeixinJSBridge 對象兼容層,這個大概只會在調試時用到,由於開發時和運行時頁面都會被後臺以注入的方式添加 WeixinJSBridge 這個對象。咱們能夠經過這段代碼看到它暴露的方法: invoke invokeCallbackHandleron publish subscribe subscribe subscribeHandler。

  • 78-235 行:Reporter 對象,它的做用就是發送錯誤和性能統計數據給後臺

  • 236-596 行:wx 對象,頁面的核心之一,一方面封裝 WeixinJSBridge 的 invokeMethod 方位爲易於調用的形式(例如 redirectTo, navigateTo等),另外一方面封裝 WeixinJSBridge 回調方法,調用者可使用wx.onAppDataChange(callback) 添加數據變動的回調函數,最後提供wx.publishPageEvent 發送頁面事件到後臺

  • 607-1267 行:wxparser 對象,提供 dom 到 wx element 對象之間的映射操做,提供元素操做管理和事件管理功能

  • 1268-1285 行:轉發 window 上的 animation 和 transition 相關的動畫事件到 exparser

  • 1286-1313 行:訂閱並轉發 WeixinJSBridge 提供的全局事件到 exparser

  • 1324-1345 行:轉發 window 上的 error 以及各類表單事件到 exparser

  • 1347-3744 行:使用 exparser.registerBehavior 和exparser.registerElement 方法註冊各類以 wx- 作爲標籤開頭的元素到 exparser

  • 3744-4498 行:virtual dom 渲染算法實現,提供 diff apply render 等方法,該模塊接口基本與 virtual-dom 一致,這裏特別的地方在於它所 diff 和生成的並非原生 DOM,而是各類模擬了 DOM 接口的 wx element 對象

  • 4599-4510 行:插入默認樣式到頁面

從頁面 data 到 dom 的主要流程以下:

var vtree
var rootNode

document.addEventListener("generateFuncReady", function(e) {
  var generateFunc = e.detail.generateFunc;
  wx.onAppDataChange(function(obj) {
    // 合併 data 到現有 data
    DataStore.setData(obj.data)
    // 生成 virtual dom 的 javascript plain object
    var props = generateFunc(DataStore.getData())

    // 第一次渲染
    if (obj.options.firstRender) {
      vtree = createVirtualTree(props, true)
      rootNode = vtree.render()
      rootNode.replaceDocumentElement(document.body)
      wx.initReady()
    } else {
      var other_vtree = createVirtualTree(props, false)
      var patches = vtree.diff(other_vtree)
      patches.apply(rootNode)
      vtree = other_vtree
      document.dispatchEvent(new CustomEvent("pageReRender", {}));
    }
  })
})

上面的 DataStore 對象提供合併和獲取當前頁面 data 對象的功能,其實現以下:

var DataStore = (function() {
  var data = {}
  return {
    getData: function() {
      return data
    },
    setData: function(e) {
      for (var t in e) {
        for (var n = (0, parsePath)(t), o = data, a = void 0, s = void 0, c = 0; c < n.length; c++) Number(n[c]) === n[c] && Number(n[c]) % 1 === 0 ? Array.isArray(o) || (a[s] = [], o = a[s]) : "[object Object]" !== Object.prototype.toString.call(o) && (a[s] = {}, o = a[s]), s = n[c], a = o, o = o[n[c]];
        a && (a[s] = e[t])
      }
    }
  }
})()

// 解析 key 爲 data 內對象的路徑字符串
function parsePath(e) {
  for (var t = e.length, n = [], i = "", r = 0, o = !1, a = !1, s = 0; s < t; s++) {
    var c = e[s];
    if ("\\" === c) s + 1 < t && ("." === e[s + 1] || "[" === e[s + 1] || "]" === e[s + 1]) ? (i += e[s + 1], s++) : i += "\\";
    else if ("." === c) i && (n.push(i), i = "");
    else if ("[" === c) {
      if (i && (n.push(i), i = ""), 0 === n.length) throw new Error("path can not start with []: " + e);
      a = !0, o = !1
    } else if ("]" === c) {
      if (!o) throw new Error("must have number in []: " + e);
      a = !1, n.push(r), r = 0
    } else if (a) {
      if (c < "0" || c > "9") throw new Error("only number 0-9 could inside []: " + e);
      o = !0, r = 10 * r + c.charCodeAt(0) - 48
    } else i += c
  }
  if (i && n.push(i), 0 === n.length) throw new Error("path can not be empty");
  return n
}

能夠看到,每次 data 變化以後,小程序就會開始整個頁面的 diff patch 過程。

對於原生實現的組件, exparser 會在監視到數據變化後發送對應事件到 WeixinJSBridge。

service 頁面詳解

service 頁面會被被拼接爲如下的樣子:

<!DOCTYPE html>
<html>
<head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
  <link href="https://res.wx.qq.com/mpres/htmledition/images/favicon218877.ico" rel="Shortcut Icon">
  <script>
  var __wxAppData = {}
  var __wxRoute
  var __wxRouteBegin
  </script>
  <script>var __wxConfig = {"pages":["page/index"],
  // app 相關各類配置
  }</script>
  <script src="http://70475629.appservice.open.weixin.qq.com/asdebug.js"></script>
  <script src="http://70475629.appservice.open.weixin.qq.com/WAService.js"></script>
  <script src="http://70475629.appservice.open.weixin.qq.com/app.js"></script>
  <script>
    __wxRoute = 'page/index';
    __wxRouteBegin = true
  </script>
  <script src="http://70475629.appservice.open.weixin.qq.com/page/index.js"></script>
</head>

<body>
  <script>
    window._____sendMsgToNW({
      sdkName: 'APP_SERVICE_COMPLETE'
    })
  </script>
</body>

</html>

除了配置和開發者編寫的頁面、app.js,頁面還在加載了 asdebug.js 和 WAService.js 兩個文件。

asdebug.js 文件位於 nwjs 項目目錄下,路徑爲app/dist/weapp/appservice/asdebug.js。 它包含了兩個部分,一個是 WeixinJSBridge 針對 service 模塊的實現,另外一塊是一些方便命令使用的接口, 例如:help() 會告訴你一些可用的函數:

該文件只會在開發者工具內被引入,若是小程序在微信內運行,應該會由微信底層提供 WeixinJSBridge。

WAService 負責 service 模塊的一些核心邏輯,它包含如下部分 (行號爲 jsbeautify 以後代碼行號,開發者工具版本:092300):

  • 1-78 行: 跟 WAWebview.js 同樣的 WeixinJSBridge 兼容模塊
  • 79-245 行: 跟 WAWebview.js 同樣的 Reporter 模塊
  • 246-1664 行:比 WAWebview.js 中 wx 功能更爲豐富 wx 接口模塊
  • 1665-2304 行:appServiceEngine 模塊,提供 Page,App,GetApp 接口
  • 2305-2360 行: 爲 window 對象添加 AMD 接口 require define

如今的 WAService 還有有不少地方依賴 window 對象,因此頗有可能它在微信中和開發者工具內同樣,依然運行於 webview 標籤以內。

相關文章
相關標籤/搜索