動(dòng)態(tài)化
Web 應(yīng)用具有天然的動(dòng)態(tài)化特性,即在應(yīng)用上線后,可通過配置數(shù)據(jù)接口,實(shí)時(shí)更改頁面布局及交互:
- HTML 可以通過 Ajax 獲取后,使用 DOM API 變更 DOM 實(shí)時(shí)生效;
- CSS 可以通過動(dòng)態(tài)添加 style 標(biāo)簽實(shí)時(shí)更新;
- JS 既可以通過添加 script 標(biāo)簽拉取執(zhí)行,也可以通過接口獲取 JS 文本,再使用 eval/Function 等 API 執(zhí)行。
小程序動(dòng)態(tài)化存在的問題:
- Runtime 接口缺失(沒有開放 eval/Function 功能),導(dǎo)致無法直接執(zhí)行交互腳本。
- 橋的缺失,導(dǎo)致無法直接操作更新視圖元素。
為了解決這兩個(gè)問題,需要開發(fā):
- JSVM —— 在小程序端能夠執(zhí) JS AST 的 runtime;
- 渲染模板引擎 —— 能夠?qū)魅氲哪0宀季峙渲眠€原成小程序頁面。
架構(gòu)圖

處理過程解析
1. 動(dòng)態(tài)組件
(1)動(dòng)態(tài)組件設(shè)置case屬性后,通過接口獲取云端代碼(在遠(yuǎn)端編寫的組件代碼),dom即wxml-ast, jscode即js-ast;
(2) 使用內(nèi)置jsvm 解析 js-ast:
(3) 解析wxml-ast,將傳入的模板布局配置還原成小程序頁面。參照上圖需要解決兩個(gè)問題:遞歸渲染vnode,綁定交互事件。
2.如何遞歸渲染,綁定交互事件
動(dòng)態(tài)組件其內(nèi)部有一個(gè)模板,把所有標(biāo)簽動(dòng)寫成一個(gè)template。例如:<template is="button">.當(dāng)拿到遠(yuǎn)端的dom的ast后,遞歸渲染每一個(gè)標(biāo)簽。樣式是通過style添加到每個(gè)標(biāo)簽上的。
3.內(nèi)置jsvm
抽象語法樹是由編譯器(complier)在語法分析階段產(chǎn)生(由 parser 解析生成)。參考ref2。
ESTree:js-ast 使用的AST標(biāo)準(zhǔn);
@babel/parser : 將js 轉(zhuǎn)為AST;
jsvm:解釋執(zhí)行AST;
主要代碼流程:
dynamic組件設(shè)置case屬性后,會(huì)觸發(fā)observer,調(diào)用init方法:
init {
const vm = getVm(); // 獲取虛擬機(jī)對(duì)象
const vdom = new VDOM(); // 初始化虛擬dom對(duì)象
_init: getData( 獲取云端代碼) => bindScope( 綁定js執(zhí)行上下文) => evalData( 解釋執(zhí)行js) => setData(執(zhí)行diff, 更新視圖層)
reload: getData => evalData => setData
bindScope {
scope = this[SCOPE] = getScope(context); // 生成一個(gè)新作用域,包含context中的所有對(duì)象;
}
evalData {
comp[TPL] = dom; // 緩存云端wxml-ast
vm.runInScope(_scope, jscode.type ? jscode : (data.jscode = inflate(jscode))); // 在提供的Scope下解釋執(zhí)行AST代碼~
}
setData {
const setData = (obj ? : Partial < DynamicContext > , callback ? : any) => {
Object.assign(context.data || (context.data = {}), obj); // 更新當(dāng)前的context
injectIntoScope(scope, obj); // 更新當(dāng)前模板的作用域
?
callback && cachedActions.push(callback); // 緩存callback隊(duì)列
flush(); // flush setData
comp[V_DOM] = vdom; // 緩存本次vdom
};
flush {
vdom.calc(comp[TPL], scope); // 依據(jù)模板和當(dāng)前作用域,數(shù)據(jù)內(nèi)聯(lián)后生成虛擬dom
const diffNode = diff(vdom.vnodes, comp.data.config, 'config'); // diff算法:comp.data.config是上一次的vdom,生成diffnode
comp.setData(diffNode, callBackFun); // 調(diào)用微信的setData方法,diffnode會(huì)在小程序渲染層解析最后更新視圖,這部分代碼參考core.wxml
}
}
}