解析從小程序開(kāi)發(fā)者工具源碼看原理實(shí)現(xiàn)
如何查看小程序開(kāi)發(fā)者工具源碼
下面我們通過(guò)微信小程序開(kāi)發(fā)者工具的源碼來(lái)說(shuō)說(shuō)小程序的底層實(shí)現(xiàn)原理。以開(kāi)發(fā)者工具版本號(hào)State v1.02.1904090的源碼來(lái)窺探小程序的實(shí)現(xiàn)思路。如何查看微信源碼,對(duì)于mac用戶而言,查看微信小程序開(kāi)發(fā)者工具的包內(nèi)容,然后進(jìn)入Contents/Resources/app.nw/js/core/index.js,注釋掉如下代碼就可以查看開(kāi)發(fā)者工具渲染后的代碼。
// 打開(kāi) inspect 窗口
if (nw.App.argv.indexOf('inspect') !== -1) {
tools.openInspectWin()
}
然后重啟小程序開(kāi)發(fā)者工具,就出現(xiàn)如下左側(cè)頁(yè)面,點(diǎn)擊其中一個(gè)頁(yè)面就能看到view層的dom結(jié)構(gòu),如下圖右側(cè)。


小程序架構(gòu)設(shè)計(jì)
1、小程序渲染是在同一個(gè)線程嗎?雙線程機(jī)制
開(kāi)發(fā)過(guò)小程序的都知道,小程序是雙線程設(shè)計(jì),即視圖渲染與業(yè)務(wù)邏輯分別在運(yùn)行在不同的線程中。這個(gè)設(shè)計(jì)主要是解決web技術(shù)中的一個(gè)痛點(diǎn):
web頁(yè)面開(kāi)發(fā)渲染線程和腳本線程是互斥的,長(zhǎng)時(shí)間的腳本運(yùn)行可能會(huì)導(dǎo)致頁(yè)面失去響應(yīng)或者白屏,體驗(yàn)糟糕。
小程序?yàn)榱烁皿w驗(yàn),將頁(yè)面的渲染線程和腳本線程分開(kāi)設(shè)計(jì)在不同線程中執(zhí)行,具體實(shí)現(xiàn):
- 視圖view層在webview中渲染,一個(gè)頁(yè)面對(duì)應(yīng)一個(gè)webview
- 業(yè)務(wù)邏輯Appservice層運(yùn)行在同一個(gè)JSCore線程中,具體ios是JavaScriptCore,android是X5 JSCore,開(kāi)發(fā)者工具是webview中;
這樣解決了長(zhǎng)時(shí)間的腳本阻塞頁(yè)面渲染的情況,但是也帶來(lái)一些新的問(wèn)題:
- 天生的延遲,線程間要通信
- 業(yè)務(wù)邏輯層因?yàn)檫\(yùn)行在JSCore中無(wú)法訪問(wèn)DOM和BOM的api;
開(kāi)發(fā)者工具使用webview加載業(yè)務(wù)邏輯層的代碼,雖然依賴的環(huán)境有DOM和BOM api,為了保持一致;小程序?qū)λ械哪K進(jìn)行了局部化處理使其不能訪問(wèn)這些api。這樣雙線程通過(guò)native,開(kāi)發(fā)者工具通過(guò)后臺(tái)websocket服務(wù)充當(dāng)二者消息中轉(zhuǎn)媒介,并且提供一些基礎(chǔ)功能。具體可以參考官網(wǎng)圖:

2、小程序是web渲染嗎?界面渲染機(jī)制
頁(yè)面渲染的方式主要有三種:
- 純web渲染
- 純native原生渲染
- Hybrid渲染,即web和native渲染結(jié)合
因?yàn)樾〕绦虻乃拗鳝h(huán)境是微信,不太可能使用純native渲染,否則所有小程序需要跟微信一起編碼發(fā)版。采用純web渲染貌似是可行的,支持快速在線更新,通過(guò)加裝最新資源到本地即可渲染;但是純web渲染在一些有復(fù)雜交互的頁(yè)面上可能會(huì)面臨一些性能問(wèn)題,這是因?yàn)樵趙eb技術(shù)中,UI渲染跟 JavaScript 的腳本執(zhí)行都在一個(gè)單線程中執(zhí)行,這就容易導(dǎo)致一些邏輯任務(wù)搶占UI渲染的資源。所以小程序采用Hybrid方式渲染,用官網(wǎng)的描述如下:
界面主要由成熟的 Web 技術(shù)渲染,輔之以大量的接口提供豐富的客戶端原生能力。
同時(shí),每個(gè)小程序頁(yè)面都是用不同的WebView去渲染,這樣可以提供更好的交互體驗(yàn),更貼近原生體驗(yàn),也避免了單個(gè)WebView的任務(wù)過(guò)于繁重。
既然采用Hybrid方式渲染,那么頁(yè)面的渲染可能會(huì)用到原生native來(lái)渲染,什么情況會(huì)用到原生渲染呢?
答案是使用到小程序提供的map、video、canvas、textarea等組件,頁(yè)面中原生渲染的渲染原理可以參考官網(wǎng)原生組件。但是在小程序開(kāi)發(fā)者工具中原生組件是使用html標(biāo)簽來(lái)模擬實(shí)現(xiàn)的。具體可以看下一節(jié)的map組件渲染結(jié)果。
3、小程序是用web的html標(biāo)簽渲染嗎?Exparser組件框架
上面說(shuō)到小程序主要由成熟的web技術(shù)渲染,能否直接使用html提供的標(biāo)簽如div、table等組織頁(yè)面呢,答案不可以。主要考量:
- 管控與安全:web技術(shù)可以通過(guò)腳本獲取修改頁(yè)面敏感內(nèi)容或者隨意跳轉(zhuǎn)其它頁(yè)面
- 能力有限,會(huì)限制小程序的表現(xiàn)形式
- 標(biāo)簽眾多,增加理解成本
所以,小程序不能直接使用html標(biāo)簽渲染頁(yè)面,其提供了10多個(gè)內(nèi)置組件來(lái)收斂web標(biāo)簽,并且提供一個(gè)JavaScript沙箱環(huán)境來(lái)避免js訪問(wèn)任何瀏覽器api。
既然小程序不能直接使用html標(biāo)簽來(lái)渲染頁(yè)面,那它提供的如view、cover-view等內(nèi)置組件是否意味著最終都轉(zhuǎn)換為html提供的內(nèi)置標(biāo)簽來(lái)渲染呢?答案當(dāng)不是。我們來(lái)看如下代碼:
<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>
上面代碼在開(kāi)發(fā)者工具中最終渲染元素如下圖:

可以看出,小程序提供的組件并沒(méi)有最終轉(zhuǎn)換為為html對(duì)應(yīng)的標(biāo)簽來(lái)渲染,而是使用自定義的元素來(lái)渲染。這些內(nèi)置組件都是由Exparser框架負(fù)責(zé)管理,它內(nèi)置在小程序基礎(chǔ)庫(kù)中,為小程序的各種組件提供基礎(chǔ)的支持。
Exparser框架基于Shadow DOM模型,模型上與WebComponents的ShadowDOM高度相似,具體可以參考官網(wǎng)組件系統(tǒng)。
內(nèi)置組件的命名規(guī)范都是以wx-開(kāi)頭的,外部引用內(nèi)置組件如view,最終會(huì)調(diào)用底層的wx-view組件;Exparser的view組件創(chuàng)建方式如下:

4、小程序可以操作dom嗎?數(shù)據(jù)驅(qū)動(dòng)
小程序?yàn)榱斯芸嘏c安全,提供一個(gè)JavaScript沙箱環(huán)境來(lái)運(yùn)行JavaScript代碼,js代碼不能訪問(wèn)任何瀏覽器相關(guān)的接口,那就意味著js是不能操作dom和bom的,否則可能報(bào)錯(cuò)。小程序?qū)崿F(xiàn)沙箱環(huán)境呢?即通過(guò)將業(yè)務(wù)邏輯封裝到一個(gè)局部環(huán)境中,局部環(huán)境修改dom和bom的相關(guān)api指向。具體封裝形式如下:

那么問(wèn)題來(lái)了,小程序是怎么給業(yè)務(wù)代碼加上以上封裝的呢?其實(shí)很簡(jiǎn)單,在小程序開(kāi)發(fā)者工具中有一個(gè)后臺(tái)服務(wù),訪問(wèn)小程序的每個(gè)模塊的path時(shí),后臺(tái)服務(wù)會(huì)調(diào)用wrapSourceCodeInDefine方法將請(qǐng)求的JS文件的內(nèi)容分別包裹在define域中,方法的代碼如下圖所示:

這里的define是小程序底層實(shí)現(xiàn)模塊化的方法之一,還有一個(gè)是require方法;通過(guò)define來(lái)定義一個(gè)模塊,require來(lái)引用一個(gè)define定義的模塊。從上面小程序?qū)I(yè)務(wù)模塊代碼的封裝可以看出:
define定義的模塊對(duì)傳遞了跟瀏覽器相關(guān)的接口同名的API,如window、document、localStroage等等
可能有人會(huì)說(shuō)通過(guò)Function('return this')()來(lái)訪問(wèn)全局作用域window對(duì)象,但是小程序堵死了這條路,重寫(xiě)了Function,eval重置為undefined。例如下圖:

require在引用模塊時(shí)只傳遞require、module、exports三個(gè)參數(shù),那么其他參數(shù)值就為undefined,不能在業(yè)務(wù)代碼中訪問(wèn)這些接口
可以看看require定義的源碼:

在實(shí)際的微信環(huán)境,業(yè)務(wù)邏輯層運(yùn)行在JSCore中,其沒(méi)有瀏覽器相關(guān)的信息,訪問(wèn)dom無(wú)從談起;但是小程序開(kāi)發(fā)者工具使用webview來(lái)運(yùn)行業(yè)務(wù)邏輯代碼,它有dom相關(guān)接口;所以通過(guò)上面沙箱環(huán)境來(lái)統(tǒng)一使js無(wú)法操作dom。
業(yè)務(wù)代碼無(wú)法訪問(wèn)dom,怎么實(shí)現(xiàn)頁(yè)面動(dòng)態(tài)更新呢?
答案就是采用類(lèi)vue這種MVVM框架的數(shù)據(jù)驅(qū)動(dòng)思想,即讓視圖狀態(tài)和視圖綁定在一起,狀態(tài)變更時(shí),視圖也能自動(dòng)變更,這樣就不用直接操作dom。
視圖的動(dòng)態(tài)更新具體是采用virtual dom技術(shù)實(shí)現(xiàn),virtual DOM相信大家都已有了解,大概是這么個(gè)過(guò)程如下圖:

實(shí)際處理可以簡(jiǎn)單描述如下:
用JS對(duì)象模擬DOM樹(shù) -> 比較兩棵虛擬DOM樹(shù)的差異 -> 把差異應(yīng)用到真正的DOM樹(shù)上。
其中,virtual dom是通過(guò)內(nèi)置的wcc可以將wxml轉(zhuǎn)換為js對(duì)象形式,以此來(lái)表示DOM樹(shù)結(jié)構(gòu)。
下面以官網(wǎng)的一幅圖來(lái)說(shuō)視圖動(dòng)態(tài)更新的過(guò)程:
// wxml
<view>{{msg}}</view>
// js
data: {
msg: 'Hello World'
}

上面說(shuō)明了視圖如何更新的,其實(shí)在數(shù)據(jù)響應(yīng)的過(guò)程中,還有最重要的一環(huán),即業(yè)務(wù)邏輯層的如何將變化的數(shù)據(jù)同步到視圖層呢,這就涉及到雙線程的通信了
5、小程序基礎(chǔ)庫(kù)作用到底是什么?
我們?cè)陂_(kāi)發(fā)者工具開(kāi)發(fā)小程序時(shí),一般都會(huì)選擇一個(gè)基礎(chǔ)庫(kù),如小程序開(kāi)發(fā)者工具選擇界面:

小程序基礎(chǔ)庫(kù)是用JavaScript寫(xiě)的,但是我們并沒(méi)有在我們的小程序中直接引用,那么我們是怎么使用基礎(chǔ)庫(kù)提供功能的呢?答案是:
微信宿主環(huán)境會(huì)提前內(nèi)置基礎(chǔ)庫(kù),打開(kāi)小程序時(shí)會(huì)自動(dòng)將基礎(chǔ)庫(kù)注入到小程序的視圖層和業(yè)務(wù)邏輯層中,小程序開(kāi)發(fā)者工具則是由底層HTTP服務(wù)負(fù)責(zé)注入。
下圖是小程序底層HTTP服務(wù)通過(guò)script腳本注入的相關(guān)代碼:

小程序基礎(chǔ)庫(kù)功能包括兩個(gè)部分視圖層的WAWebview.js和業(yè)務(wù)邏輯層的WAService.js。下面就簡(jiǎn)單說(shuō)下對(duì)應(yīng)功能:
WAService為業(yè)務(wù)邏輯層提供基礎(chǔ)功能
下看看一下WAService.js源碼內(nèi)容縮略圖:

從源碼可以看出基礎(chǔ)庫(kù)提供的WAService.js有很多功能,主要包括以下幾部分
- WeixinJSBridge:消息通信的統(tǒng)一封裝易于調(diào)用,主要微信環(huán)境與native,開(kāi)發(fā)環(huán)境與開(kāi)發(fā)者工具后臺(tái)服務(wù)的通信。
- wx: wx對(duì)象下面的api方法封裝
- appServiceEngine:定義了全局的方法如define,require, App,Page,Component,getApp,getCurrentPages等
- virtualDOM: VirtualDOM,Diff和Render UI實(shí)現(xiàn)
- expraser: expraser框架組件的方法定義,這意味著邏輯層也具有一定的組件樹(shù)組織能力。
- Reporter: 小程序日志組件
WAWebview為視圖層提供基礎(chǔ)功能
小程序基礎(chǔ)庫(kù)為視圖層提供的基礎(chǔ)功能有些與WAService相同,主要功能如下:
- 消息通信封裝為WeixinJSBridge
- 日志組件Reporter封裝
- wx對(duì)象下的api,跟WAService里的不同的是其大部分都是處理UI顯示相關(guān)的方法
- 小程序Expraser組件框架的實(shí)現(xiàn)和內(nèi)置組件的注冊(cè)
- VirtualDOM,Diff和Render UI實(shí)現(xiàn)
- 定義頁(yè)面相關(guān)事件觸發(fā)
以上就是解析從小程序開(kāi)發(fā)者工具源碼看原理實(shí)現(xiàn)的詳細(xì)內(nèi)容,更多關(guān)于從小程序開(kāi)發(fā)者工具源碼看原理實(shí)現(xiàn)的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!
相關(guān)文章
JS 實(shí)現(xiàn) ajax 異步瀏覽器兼容問(wèn)題
本文通過(guò)實(shí)例代碼給大家講解了js實(shí)現(xiàn)ajax異步瀏覽器兼容問(wèn)題,代碼簡(jiǎn)單易懂,非常不錯(cuò),具有參考借鑒價(jià)值,需要的朋友參考下吧2017-01-01
Js和JQuery獲取鼠標(biāo)指針坐標(biāo)的實(shí)現(xiàn)代碼分享
這篇文章主要介紹了Js和JQuery獲取鼠標(biāo)指針坐標(biāo)的實(shí)現(xiàn)代碼分享,本文直接給出實(shí)現(xiàn)的代碼,需要的朋友可以參考下2015-05-05
利用JS實(shí)現(xiàn)簡(jiǎn)單的瀑布流加載圖片效果
今天學(xué)習(xí)了一個(gè)瀑布流加載效果,很多網(wǎng)站都有瀑布流效果,下面通過(guò)本文給大家分享利用JS實(shí)現(xiàn)簡(jiǎn)單的瀑布流加載圖片效果,需要的朋友參考下吧2017-04-04
基于JS實(shí)現(xiàn)的消消樂(lè)游戲的示例代碼
這篇文章主要為大家介紹了如何利用JS實(shí)現(xiàn)消消樂(lè)小游戲,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下2022-04-04
JavaScript控制網(wǎng)頁(yè)層收起和展開(kāi)效果的方法
這篇文章主要介紹了JavaScript控制網(wǎng)頁(yè)層收起和展開(kāi)效果的方法,涉及javascript操作網(wǎng)頁(yè)元素動(dòng)態(tài)效果的技巧,非常具有實(shí)用價(jià)值,需要的朋友可以參考下2015-04-04
Bootstrap Paginator分頁(yè)插件使用方法詳解
這篇文章主要為大家詳細(xì)介紹了Bootstrap Paginator分頁(yè)插件使用方法,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-05-05

