對(duì)于小程序框架實(shí)現(xiàn)原理,在支付寶小程序官方文檔上有這樣一段描述:
與傳統(tǒng)的 H5 應(yīng)用不同,小程序運(yùn)行架構(gòu)分為 webview 和 worker 兩個(gè)部分。webview 負(fù)責(zé)渲染,worker 則負(fù)責(zé)存儲(chǔ)數(shù)據(jù)和執(zhí)行業(yè)務(wù)邏輯。 1.webview 和 worker 之間的通信是異步的。這意味著當(dāng)我們調(diào)用 setData 時(shí),我們的數(shù)據(jù)并不會(huì)立即渲染,而是需要從 worker 異步傳輸?shù)?webview。 2.數(shù)據(jù)傳輸時(shí)需要序列化為字符串,然后通過 evaluateJavascript 方式傳輸,數(shù)據(jù)大小會(huì)影響性能。
概括一下,大致意思是小程序框架核心是通過2個(gè)線程來(lái)完成的,主線程負(fù)責(zé)webView的渲染工作,worker線程負(fù)責(zé)js執(zhí)行。說到這里,你是不是會(huì)產(chǎn)生一個(gè)疑問:為什么多線程通信損耗性能還要搞多線程呢?可能大多數(shù)人都知道因?yàn)閃eb技術(shù)實(shí)在是太開放了,開發(fā)者可以為所欲為。這種情況在小程序中是不允許的,不允許使用
但是卻始終無(wú)法解決一個(gè)問題:如何防止開發(fā)者做一些我們想禁用的功能。因?yàn)槭且粋€(gè)網(wǎng)頁(yè),開發(fā)者可以執(zhí)行JS,可以操作DOM,可以操作BOM,可以做一切事情。so,我們需要一個(gè)沙箱環(huán)境,來(lái)運(yùn)行我們的js,這個(gè)沙箱環(huán)境需要可以屏蔽掉所有的危險(xiǎn)動(dòng)作。說了這么多,大致想法如下:
關(guān)于UI層的渲染,有很多實(shí)現(xiàn)方式,比如通過類似VNode -> diff的自定義渲染方式來(lái)實(shí)現(xiàn)了一個(gè)簡(jiǎn)易的小程序框架:
function App (props) { const {msg} = props; return () => (
核心就是通過定義@babel/plugin-transform-react-jsx插件來(lái)轉(zhuǎn)換 jsx,生成Vnode,再交給Worker通過Diff,最后通過worker postmsg 來(lái)通知渲染進(jìn)程更新:
let index = 0;// 得到diff差異let diffData = diff(0, index, oldVnode, newVnode);// 通知渲染進(jìn)程更新self.postMessage(JSON.stringify(diffData)); 復(fù)制代碼
有點(diǎn)麻煩?能不能繼承現(xiàn)有框架能力?比如Vue、React。當(dāng)然可以,我們下面就來(lái)介紹基于Vue來(lái)實(shí)現(xiàn)的demo.
實(shí)現(xiàn)一個(gè)基于Vue的小程序框架
有了上面的知識(shí),我們先不著急寫代碼,先來(lái)捋一下我們需要什么,首先我們需要實(shí)現(xiàn)這樣一個(gè)能力:渲染層和邏輯層分離,emmm。。。大致我們的小程序是這樣的
// page.js 邏輯層export default { data: { msg: 'hello Vox', }, create() { console.log(window); setTimeout(() => { this.setData({ msg: 'setData', }) }, 1000); },}復(fù)制代碼// page.vxml.js 渲染層export default () => { return '
這里的渲染層為啥不是類似于微信或者支付寶小程序 wxml,axml這樣的呢?當(dāng)然可以,其實(shí)我只是為了方便而已,我們可以手寫一個(gè)webpack loader 來(lái)處理一下我們自定義的文件。這里有興趣的小伙伴可以嘗試一下。不是本次介紹的核心。
好了,上面是我們想要的功能,我們核心是框架,框架層要干的事核心有2個(gè):構(gòu)造worker初始化引擎;構(gòu)造渲染引擎。
// index.worker.js 構(gòu)造workerconst voxWorker = options => { const {config} = options; // Vue生命周期收集 const lifeCircleMap = { 'lifeCircle:create': [config.create], }; // 定義setData方法用于通知UI層渲染更新 self.setData = (data) => { console.log('setData called'); self.postMessage( JSON.stringify({ type: 'update', data, }) , null ); }; // worker構(gòu)建完成,通知渲染層初始化 self.postMessage( JSON.stringify({ type: 'init', data: config.data, }) , null ); // 執(zhí)行生命周期函數(shù) self.onmessage = e => { const {type} = JSON.parse(e.data); lifeCircleMap[type].forEach(lifeCircle => lifeCircle.call(self)) }}export default voxWorker;復(fù)制代碼
上面代碼核心干的事其實(shí)并不復(fù)雜,也就是:
收集需要用到的生命周期
定義setData函數(shù),提供給用戶層更新UI
定義監(jiān)聽函數(shù),處理生命周期函數(shù)執(zhí)行
通知UI進(jìn)程開啟渲染。
當(dāng)我們通知UI進(jìn)程開始渲染的時(shí)候,UI進(jìn)程也就是需要構(gòu)造Vue實(shí)例,進(jìn)行頁(yè)面render:
worker.onmessage = e => { const {type, data} = JSON.parse(e.data); if (type === 'init') { const mountNode = document.createElement('div'); document.body.appendChild(mountNode); target = new Vue({ el: mountNode, data: () => data, template: template(), created(){ worker.postMessage(JSON.stringify({ type: 'lifeCircle:create', }) , null); } }); } }復(fù)制代碼
可以看到UI線程在初始化的時(shí)候,一并初始化了 worker層傳遞來(lái)的data,并對(duì)生命周期進(jìn)行了聲明。當(dāng)生命周期函數(shù)在UI層觸發(fā)的時(shí)候,會(huì)通知 worker。在我們的例子中,create 鉤子通過setData 進(jìn)行了一個(gè)更新data的動(dòng)作。我們知道 setData 就是拿到數(shù)據(jù)進(jìn)行通知更新:
// UI線程接收到通知消息,更新UI if (type === 'update') { Object.keys(data).map(key => { target[key] = data[key]; }); }復(fù)制代碼
說到這里,似乎一切都感覺清楚多了,我們用worker執(zhí)行用戶js邏輯,worker內(nèi)無(wú)法操作dom,無(wú)法訪問window。當(dāng)我們需要更新dom,可以去通知渲染線程做更新。我們也很容易想到不斷地?cái)?shù)據(jù)傳輸對(duì)性能的損耗,所以我們當(dāng)然可以做進(jìn)一步的優(yōu)化,多個(gè)setData可以組合一起發(fā)送?就是建立一個(gè)通信。其次再看一些,我們的worker再通信傳輸數(shù)據(jù)的過程中不斷通過字符串的parse和stringify
綠色部分時(shí)原生JSON.stringify(), 關(guān)于這一塊如何提升性能一方面可以通過減少數(shù)據(jù)傳輸量,其他的優(yōu)化也可以參考這里如何提升JSON.stringify()的性能
最后你可能會(huì)問,小程序用法都是
的語(yǔ)法糖,寫一個(gè) vue組件,組件名稱叫view是不是就可以了呢?
這里為大家介紹的也只是小程序的冰山一角,內(nèi)部的容器開放jsBridge能力,離線機(jī)制,跨webview通信機(jī)制等等有興趣的可以去探索,當(dāng)然這里也只是拋磚引玉。
有興趣的可以查看源碼Vox
結(jié)語(yǔ)
引用知乎上的一段話:
其實(shí),大家對(duì)小程序的底層實(shí)現(xiàn)都是使用雙線程模型,大家對(duì)外宣稱都會(huì)說是為了:方便多個(gè)頁(yè)面之間數(shù)據(jù)共享和交互為native開發(fā)者提供更好的編碼體驗(yàn)為了性能(防止用戶的JS執(zhí)行卡住UI線程)其他好處但其實(shí)真正的原因其實(shí)是:“安全”和“管控”,其他原因都是附加上去的。因?yàn)閃eb技術(shù)是非常開放的,JavaScript可以做任何事。但在小程序這個(gè)場(chǎng)景下,它不會(huì)給開發(fā)者那么高的權(quán)限:不允許開發(fā)者把頁(yè)面跳轉(zhuǎn)到其他在線網(wǎng)頁(yè)不允許開發(fā)者直接訪問DOM不允許開發(fā)者隨意使用window上的某些未知的可能有危險(xiǎn)的API,當(dāng)然,想解決這些問題不一定非要使用雙線程模型,但雙線程模型無(wú)疑是最合適的技術(shù)方案。
經(jīng)過上面的介紹,是不是發(fā)現(xiàn)小程序其實(shí)也就那么回事,并沒有多么....這邊文章主要希望能讓你對(duì)經(jīng)常使用的框架有一個(gè)原理性的初步認(rèn)識(shí),至少我們?cè)儆玫臅r(shí)候可以規(guī)避掉一些坑,或者性能問題。 關(guān)注 文章 瀏覽量 關(guān)注 文章 瀏覽量 關(guān)注 文章 瀏覽量
發(fā)布評(píng)論請(qǐng)先 登錄
CYBT-343026-01能否使用 HFP 和 AVRCP 制作應(yīng)用程序?
Web Components實(shí)踐:如何搭建一個(gè)框架無(wú)關(guān)的AI組件庫(kù)

STM32如何移植Audio框架?
用Labview寫一個(gè)電子稱的485串口程序

AI開發(fā)框架集成介紹
OpenHarmony程序分析框架論文入選ICSE 2025

AUTOSAR通信框架的優(yōu)勢(shì) AUTOSAR通信實(shí)例與應(yīng)用場(chǎng)景
SSM框架的源碼解析與理解
SSM框架在Java開發(fā)中的應(yīng)用 如何使用SSM進(jìn)行web開發(fā)
JavaWeb框架比較
LangChain框架關(guān)鍵組件的使用方法

Dubbo源碼淺析(一)—RPC框架與Dubbo

一個(gè)socket對(duì)應(yīng)一個(gè)連接嗎
日志框架簡(jiǎn)介-Slf4j+Logback入門實(shí)踐

如何手搓一個(gè)自定義的RPC 遠(yuǎn)程過程調(diào)用框架

評(píng)論