小程序的主要開發語言是 JavaScript ,雖然有與網頁開發有類似性可是還有必定的區別javascript
渲染非原生組件以及腳本執行環境的區別以下css
運行環境 | 邏輯層 | 渲染層 |
---|---|---|
Android | V8 | Chromium定製內核 |
iOS | JSCore | WKWebView |
小程序開發者工具 | NWJS | Chrome WebView |
在前言中提到小程序的宿主環境爲微信客戶端,因此藉助宿主環境提供的能力,能夠完成許多普通網頁沒法完成的功能html
首先,咱們來簡單瞭解下小程序的運行環境。小程序的運行環境分紅渲染層和邏輯層,其中 WXML 模板和 WXSS 樣式工做在渲染層,JS 腳本工做在邏輯層。java
小程序的渲染層和邏輯層分別由2個線程管理:渲染層的界面使用了 WebView 進行渲染;邏輯層採用 JsCore 線程運行 JS 腳本。一個小程序存在多個界面,因此渲染層存在多個 WebView 線程,這兩個線程的通訊會經由微信客戶端作中轉,邏輯層發送網絡請求也經由 Native 轉發,小程序的通訊模型下圖所示。node
多 WebView 模式下,每個 WebView 都有一個獨立的 JSContext,那視圖和邏輯是如何進行通信?以下圖雙線程生命週期所示。git
相對於瀏覽器雙線程模型github
當咱們對 View 層進行事件操做後,會經過 WeixinJSBridge 將數據傳遞到 Native 系統層。Native 系統層決定是否要用 native 處理,而後丟給邏輯層進行用戶的邏輯代碼處理。邏輯層處理後將數據經過 WeixinJSBridge 返給 View 層。View 渲染更新視圖,以下圖所示。web
微信開者工具模擬器運行的代碼是通過本地預處理、本地編譯,才能看見的頁面。而微信客戶端運行的代碼是額外通過服務器編譯的。只有通過編譯才能識別並運行小程序的代碼。咱們先來看一下小程序文件的基本結構npm
.wxml
頁面結構.wxss
頁面樣式.js
頁面邏輯.json
頁面相關配置按照【約定優於配置】的原則接下來咱們打開小程序開發者工具,點擊左上角微信開發者工具 >> 調試 >> 調試微信開發者工具,看到以下界面(官網組件demo)json
能夠看到渲染層和邏輯層是兩個 webview,第一個對應的 webview 是渲染層,每一個頁面都有一個webview,而邏輯層的 appservice 是隻有一個。
而後咱們接着往下看 webview 中究竟是什麼?打開 webview 發現 iframe 標籤是空的,但咱們想要查看怎麼辦,打開調試面板(console)下面輸入
// 第一步先找到全部的webview
document.getElementsByTagName('webview')
// 第二步找到第一個渲染層頁面用開發工具命令打開
document.getElementsByTagName('webview')[0].showDevTools(true,null)
複製代碼
這個時候咱們會看到以下窗口,這個頁面就是咱們渲染層的頁面結構了。
既然能看到每個渲染層,確定也能看到邏輯層代碼,在開發者工具控制檯中輸入 document
,出現以下頁面
接下來咱們看一下微信小程序使用的基礎庫文件。在開發者工具控制檯中輸入 openVendor()
就會打開本地小程序的 WeappVendor
目錄,有幾個重要的文件
wcc
編譯器負責將 wxml
編譯成 js
文件wcsc
編譯器負責將 wxss
文件編譯成 js
文件xxx.wxvpkg
是不一樣版本的小程序基礎庫,主要包含小程序基礎庫 WAService
和 WAWebview
,這塊後續分析。一、新建一個名爲 compiler.js
的文件,並寫入如下代碼
const fs = require("fs");
const miniprogramCompiler = require("miniprogram-compiler");
const path = require("path");
let JsCompiler = miniprogramCompiler.wxmlToJs(path.join(__dirname));
let cssCompiler = miniprogramCompiler.wxssToJs(path.join(__dirname));
fs.writeFileSync("wxml.js", JsCompiler);
fs.writeFileSync("wxcss.js", cssCompiler);
複製代碼
二、執行 npm install miniprogram-compiler
三、執行 node compiler.js
,會在同目錄生成 wxml 和 wxcss 兩個 JS 文件 四、新建 index.wxml 並寫入以下代碼
<view class="box">
<text class="box-text">{{ text }}</text>
</view>
複製代碼
五、新建 index.html 引入文件 wxml.js,以下代碼
<script src="./wxml.js"></script>
<script> const res = $gwx("index.wxml"); const virtualTree = res({ text: 'data數據' }); console.log(virtualTree); </script>
複製代碼
用瀏覽器打開 index.html 控制檯會輸出相似 Virtual DOM 的對象
{
"tag":"wx-page",
"children":[{
"tag":"wx-view",
"attr":{ "class":"box"},
"children":[{
"tag":"wx-text",
"attr":{ "class":"box-text" },
"children":[ "data數據" ],
"raw":{},
"generics":{}
}],
"raw":{},
"generics":{}
}]
}
複製代碼
wcc的做用就是:
path
(頁面 wxml 路徑)和 global
(頂層對象)上面提到過開發者工具中輸入openVendor
以後會看到不少 .wxvpkg
文件,這是小程序的基礎庫,主要有兩部分組成 WAService
和 WAWebview
微信小程序基礎庫版本是不斷更新的,當前我本地的最高版本爲 2.14.1.wxvpkg ,咱們能夠利用unwxapkg 解壓 2.14.1.wxvpkg。解壓後的目錄爲
├── WAAutoService.js
├── WAAutoWebview.js
├── WAGame.js
├── WAGameSubContext.js
├── WAGameVConsole.html
├── WAGfxEmsc.js
├── WAGfxEmsc.wasm
├── WAPageFrame.html
├── WAPerf.js
├── WARemoteDebug.js
├── WAService.js
├── WAServiceMainContext.js
├── WASourceMap.js
├── WASubContext.js
├── WAVConsole.js
├── WAVersion.json
├── WAWKWorker.html
├── WAWebview.js
├── WAWidget.js
└── WAWorker.js
複製代碼
其餘文件先忽略,咱們先看一下 webview 引用的兩個基礎文件源碼概覽 WAWebview 和 WAService
其中,WAWebview 最主要的幾個部分:
Foundation
:基礎模塊(發佈訂閱、通訊橋樑 ready 事件)WeixinJSBridge
:消息通訊模塊(js 和 native 通信) Webview 和 Service都有相同的一套exparser
:組件系統模塊,實現了一套自定義的組件模型,好比實現了 wx-view__virtualDOM__
:虛擬 Dom 模塊__webViewSDK__
:WebView SDK 模塊Reporter
:日誌上報模塊(異常和性能統計數據)其中,WAService 最主要的幾個部分:
Foundation
:基礎模塊WeixinJSBridge
:消息通訊模塊(js 和 native 通信) Webview 和 Service都有相同的一套WeixinNativeBuffer
:原生緩衝區WeixinWorker
:Worker 線程JSContext
:JS Engine ContextProtect
:JS 保護的對象__subContextEngine__
:提供 App、Page、Component、Behavior、getApp、getCurrentPages 等方法基礎模塊提供環境變量 env、發佈訂閱 EventEmitter、配置/基礎庫/通訊橋 Ready 事件。
微信小程序的組件組織框架,內置在小程序基礎庫中,爲小程序的各類組件提供基礎的支持。小程序內的全部組件,包括內置組件和自定義組件,都由 Exparser 組織管理。Exparser 的組件模型與 WebComponents 標準中的 ShadowDOM 高度類似。Exparser 會維護整個頁面的節點樹相關信息,包括節點的屬性、事件綁定等,至關於一個簡化版的 Shadow DOM 實現。
生成 wx-element
對象,和 virtual-dom 相似
提供了視圖層 JS 與 Native、視圖層與邏輯層之間消息通訊的機制,以下圖幾個方法:
經過上面這些主體結構和函數,對小程序應該有大體的瞭解,那麼咱們把上面串起來看一下首次渲染的流程
document.getElementsByTagName('webview')[0].showDevTools(true)
上圖中最後標註的代碼
var decodeName = decodeURI("./pages/index/index.wxml");
var generateFunc = $gwx(decodeName);
if (generateFunc) {
var CE = (typeof __global === 'object') ? (window.CustomEvent || __global.CustomEvent) : window.CustomEvent;
document.dispatchEvent(new CE("generateFuncReady", {
detail: {
generateFunc: generateFunc
}
})) __global.timing.addPoint('PAGEFRAME_GENERATE_FUNC_READY', Date.now())
} else {
document.body.innerText = decodeName + " not found"console.error(decodeName + " not found")
};
複製代碼
因此初次渲染流程以下
c = function() {
setTimeout(function() {
! function() {
var e = arguments;
r(function() {
WeixinJSBridge.publish.apply(WeixinJSBridge, o(e))
})
}("GenerateFuncReady", {})
}, 20)
}
document.addEventListener("generateFuncReady", c)
複製代碼
小程序邏輯層和渲染層的通訊會由 Native (微信客戶端)作中轉,邏輯層發送網絡請求也經由 Native 轉發。
內置組件中有部分組件(map、video等)是利用到客戶端原生提供的能力,那是怎麼通訊的呢?iOS 是利用了 WKWebView 的提供 messageHandlers 特性,而在安卓則是往 WebView 的 window 對象注入一個原生方法,最終會封裝成 WeiXinJSBridge 這樣一個兼容層,主要提供了調用(invoke)和監聽(on)這兩種方法。
iOS 平臺往 JavaScripCore 框架注入一個全局的原生方法,而安卓方面則是跟渲染層一致。 不論視圖層(部分組件)仍是邏輯層,開發者都是間接地調用客戶端原生底層的能力。
最後,推薦一套TS全系列的教程吧。近期在提高TS,收藏了一套很不錯的教程,無償分享給xdm www.yidengxuetang.com/pub-page/in…