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

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

首先聲明下,文章查看小程序開發者工具源碼的方法僅限學習使用。html

本文將從如下幾個方面來講一下小程序的實現原理前端

如何查看小程序開發者工具源碼

下面咱們經過微信小程序開發者工具的源碼來講說小程序的底層實現原理。以開發者工具版本號State v1.02.1904090的源碼來窺探小程序的實現思路。如何查看微信源碼,對於mac用戶而言,查看微信小程序開發者工具的包內容,而後進入Contents/Resources/app.nw/js/core/index.js,註釋掉以下代碼就能夠查看開發者工具渲染後的代碼。vue

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

而後重啓小程序開發者工具,就出現以下左側頁面,點擊其中一個頁面就能看到view層的dom結構,以下圖右側。
|java

小程序架構設計

小程序的架構設計與web技術仍是有必定的差異,其吸收了web技術的一些優點,同時也摒棄web技術中體驗等很差的地方。下面經過問題的形式來講說小程序架構中的一些設計點。android

一、小程序渲染是在同一個線程嗎?雙線程機制

開發太小程序的都知道,小程序是雙線程設計,即視圖渲染與業務邏輯分別在運行在不一樣的線程中。這個設計主要是解決web技術中的一個痛點:ios

web頁面開發渲染線程和腳本線程是互斥的,長時間的腳本運行可能會致使頁面失去響應或者白屏,體驗糟糕。git

小程序爲了更好體驗,將頁面的渲染線程和腳本線程分開設計在不一樣線程中執行,具體實現:web

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

這樣解決了長時間的腳本阻塞頁面渲染的狀況,可是也帶來一些新的問題:canvas

  • 天生的延遲,線程間要通訊
  • 業務邏輯層由於運行在JSCore中沒法訪問DOM和BOM的api;

開發者工具使用webview加載業務邏輯層的代碼,雖然依賴的環境有DOM和BOM api,爲了保持一致;小程序對全部的模塊進行了局部化處理使其不能訪問這些api。這樣雙線程經過native,開發者工具經過後臺websocket服務來進行兩者消息中轉。具體能夠參考官網圖:

二、小程序是web渲染嗎?界面渲染機制

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

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

由於小程序的宿主環境是微信,不太可能使用純native渲染,不然全部小程序須要跟微信一塊兒編碼發版。採用純web渲染貌似是可行的,支持快速在線更新,經過加裝最新資源到本地便可渲染;可是純web渲染在一些有複雜交互的頁面上可能會面臨一些性能問題,這是由於在web技術中,UI渲染跟 JavaScript 的腳本執行都在一個單線程中執行,這就容易致使一些邏輯任務搶佔UI渲染的資源。因此小程序採用Hybrid方式渲染,用官網的描述以下:

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

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

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

三、小程序是用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組件建立方式以下:

四、小程序能夠操做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時,後臺服務會調用wrapSourceCodeInDefine方法將請求的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'
}

上面說明了視圖如何更新的,其實在數據響應的過程當中,還有最重要的一環,即業務邏輯層的如何將變化的數據同步到視圖層呢,這就涉及到雙線程的通訊了,具體能夠參考從微信小程序開發者工具源碼看實現原理(三)- - 雙線程通訊

相關文章
相關標籤/搜索