從微信小程式開發者工具原始碼看實現原理(一)- - 小程式架構設計

wonyun發表於2019-07-11

使用微信小程式開發已經很長時間了,對小程式開發已經相當熟練了;但是作為一名對技術有追求的前端開發,僅僅熟練掌握小程式的開發感覺還是不夠的,我們應該更進一步的去理解其背後實現的原理以及對應的考量,這可能會解釋我們在開發過程中遇到的一些疑惑,比如為啥小程式不能操作dom、小程式是web技術渲染還是native技術渲染等等,另一方面對於我們個人成長也是有幫組的。

首先宣告下,文章檢視小程式開發者工具原始碼的方法僅限學習使用。

本文將從以下幾個方面來說一下小程式的實現原理

如何檢視小程式開發者工具原始碼

下面我們通過微信小程式開發者工具的原始碼來說說小程式的底層實現原理。以開發者工具版本號State v1.02.1904090的原始碼來窺探小程式的實現思路。如何檢視微信原始碼,對於mac使用者而言,檢視微信小程式開發者工具的包內容,然後進入Contents/Resources/app.nw/js/core/index.js,註釋掉如下程式碼就可以檢視開發者工具渲染後的程式碼。

// 開啟 inspect 視窗
  if (nw.App.argv.indexOf('inspect') !== -1) {
    tools.openInspectWin()
  }

然後重啟小程式開發者工具,就出現如下左側頁面,點選其中一個頁面就能看到view層的dom結構,如下圖右側。
從微信小程式開發者工具原始碼看實現原理(一)- - 小程式架構設計|從微信小程式開發者工具原始碼看實現原理(一)- - 小程式架構設計

小程式架構設計

小程式的架構設計與web技術還是有一定的差別,其吸取了web技術的一些優勢,同時也摒棄web技術中體驗等不好的地方。下面通過問題的形式來說說小程式架構中的一些設計點。

1、小程式渲染是在同一個執行緒嗎?雙執行緒機制

開發過小程式的都知道,小程式是雙執行緒設計,即檢視渲染與業務邏輯分別在執行在不同的執行緒中。這個設計主要是解決web技術中的一個痛點:

web頁面開發渲染執行緒和指令碼執行緒是互斥的,長時間的指令碼執行可能會導致頁面失去響應或者白屏,體驗糟糕。

小程式為了更好體驗,將頁面的渲染執行緒和指令碼執行緒分開設計在不同執行緒中執行,具體實現:

  • 檢視view層在webview中渲染,一個頁面對應一個webview
  • 業務邏輯Appservice層執行在同一個JSCore執行緒中,具體ios是JavaScriptCore,android是X5 JSCore,開發者工具是webview中;

這樣解決了長時間的指令碼阻塞頁面渲染的情況,但是也帶來一些新的問題:

  • 天生的延遲,執行緒間要通訊
  • 業務邏輯層因為執行在JSCore中無法訪問DOM和BOM的api;

開發者工具使用webview載入業務邏輯層的程式碼,雖然依賴的環境有DOM和BOM api,為了保持一致;小程式對所有的模組進行了區域性化處理使其不能訪問這些api。這樣雙執行緒通過native,開發者工具通過後臺websocket服務來進行二者訊息中轉。具體可以參考官網圖:
從微信小程式開發者工具原始碼看實現原理(一)- - 小程式架構設計

2、小程式是web渲染嗎?介面渲染機制

頁面渲染的方式主要有三種:

  • 純web渲染
  • 純native原生渲染
  • Hybrid渲染,即web和native渲染結合

因為小程式的宿主環境是微信,不太可能使用純native渲染,否則所有小程式需要跟微信一起編碼發版。採用純web渲染貌似是可行的,支援快速線上更新,通過加裝最新資源到本地即可渲染;但是純web渲染在一些有複雜互動的頁面上可能會面臨一些效能問題,這是因為在web技術中,UI渲染跟 JavaScript 的指令碼執行都在一個單執行緒中執行,這就容易導致一些邏輯任務搶佔UI渲染的資源。所以小程式採用Hybrid方式渲染,用官網的描述如下:

介面主要由成熟的 Web 技術渲染,輔之以大量的介面提供豐富的客戶端原生能力。同時,每個小程式頁面都是用不同的WebView去渲染,這樣可以提供更好的互動體驗,更貼近原生體驗,也避免了單個WebView的任務過於繁重。

既然採用Hybrid方式渲染,那麼頁面的渲染可能會用到原生native來渲染,什麼情況會用到原生渲染呢?

答案是使用到小程式提供的mapvideocanvastextarea等元件,頁面中原生渲染的渲染原理可以參考官網原生元件。但是在小程式開發者工具中原生元件是使用html標籤來模擬實現的。具體可以看下一節的map元件渲染結果。

3、小程式是用web的html標籤渲染嗎?Exparser元件框架

上面說到小程式主要由成熟的web技術渲染,能否直接使用html提供的標籤如div、table等組織頁面呢,答案不可以。主要考量:

  • 管控與安全:web技術可以通過指令碼獲取修改頁面敏感內容或者隨意跳轉其它頁面
  • 能力有限,會限制小程式的表現形式
  • 標籤眾多,增加理解成本

所以,小程式不能直接使用html標籤渲染頁面,其提供了10多個內建元件來收斂web標籤,並且提供一個JavaScript沙箱環境來避免js訪問任何瀏覽器api。

既然小程式不能直接使用html標籤來渲染頁面,那它提供的如viewcover-view等內建元件是否意味著最終都轉換為html提供的內建標籤來渲染呢?答案當不是。我們來看如下程式碼:

<view class="map-container">
  <map latitude='39.9088230000' style="height: 100%; width:100%;" longitude='116.3974700000' scale='16' id="id" bindregionchange="onRegionChange"></map>
  <view catchtap="onTap">test</view>
</view>

上面程式碼在開發者工具中最終渲染元素如下圖:

從微信小程式開發者工具原始碼看實現原理(一)- - 小程式架構設計

可以看出,小程式提供的元件並沒有最終轉換為為html對應的標籤來渲染,而是使用自定義的元素來渲染。這些內建元件都是由Exparser框架負責管理,它內建在小程式基礎庫中,為小程式的各種元件提供基礎的支援。

Exparser框架基於Shadow DOM模型,模型上與WebComponents的ShadowDOM高度相似,具體可以參考官網元件系統
內建元件的命名規範都是以wx-開頭的,外部引用內建元件如view,最終會呼叫底層的wx-view元件;Exparser的view元件建立方式如下:

從微信小程式開發者工具原始碼看實現原理(一)- - 小程式架構設計

4、小程式可以操作dom嗎?資料驅動

小程式為了管控與安全,提供一個JavaScript沙箱環境來執行JavaScript程式碼,js程式碼不能訪問任何瀏覽器相關的介面,那就意味著js是不能操作dom和bom的,否則可能報錯。小程式實現沙箱環境呢?即通過將業務邏輯封裝到一個區域性環境中,區域性環境修改dom和bom的相關api指向。具體封裝如下:

define("pages/xx/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,URL,DOMParser,upload,preview,build,showDecryptedInfo,syncMessage,checkProxy,showSystemInfo,openVendor,openToolsLog,showRequestInfo,help,showDebugInfoTable,closeDebug,showDebugInfo,__global,WeixinJSBridge){ 
    'use strict';
    // your code here
})

那麼問題來了,小程式是怎麼給業務程式碼加上以上封裝的呢?其實很簡單,在小程式開發者工具中有一個後臺服務,訪問小程式的每個模組的path時,後臺服務會將請求的JS檔案的內容分別包裹在define域中,具體如下:

這裡的define是小程式底層實現模組化的方法之一,還有一個是require方法;通過define來定義一個模組,require來引用一個define定義的模組。從上面小程式對業務模組程式碼的封裝可以看出:

  • define定義的模組對傳遞了跟瀏覽器相關的介面同名的API,如window、document、localStroage等等
  • require在引用模組時只傳遞require、module、exports三個引數,那麼其他引數值就為undefined,不能在業務程式碼中訪問這些介面

可以看看require定義的原始碼:
從微信小程式開發者工具原始碼看實現原理(一)- - 小程式架構設計

在實際的微信環境,業務邏輯層執行在JSCore中,其沒有瀏覽器相關的資訊,訪問dom無從談起;但是小程式開發者工具使用webview來執行業務邏輯程式碼,它有dom相關介面;所以通過上面沙箱環境來統一使js無法操作dom。

業務程式碼無法訪問dom,怎麼實現頁面動態更新呢?

答案就是採用類vue這種MVVM框架的資料驅動思想,即讓檢視狀態和檢視繫結在一起,狀態變更時,檢視也能自動變更,這樣就不用直接操作dom。

檢視的動態更新具體是採用virtual dom技術實現,virtual DOM相信大家都已有了解,大概是這麼個過程:

用JS物件模擬DOM樹 -> 比較兩棵虛擬DOM樹的差異 -> 把差異應用到真正的DOM樹上。

下面以官網的一幅圖來說檢視動態更新的過程:

 // wxml
 <view>{{msg}}</view>

// js
data: {
   msg: 'Hello World'
}

從微信小程式開發者工具原始碼看實現原理(一)- - 小程式架構設計

上面說明了檢視如何更新的,其實在資料響應的過程中,還有最重要的一環,即業務邏輯層的如何將變化的資料同步到檢視層呢,這就涉及到雙執行緒的通訊了,具體可以參考從微信小程式開發者工具原始碼看實現原理(三)- - 雙執行緒通訊

相關文章