動(dòng)態(tài)化
Web 應(yīng)用具有天然的動(dòng)態(tài)化特性,即在應(yīng)用上線后捷绑,可通過(guò)配置數(shù)據(jù)接口躬贡,實(shí)時(shí)更改頁(yè)面布局及交互:
- HTML 可以通過(guò) Ajax 獲取后,使用 DOM API 變更 DOM 實(shí)時(shí)生效绰咽;
- CSS 可以通過(guò)動(dòng)態(tài)添加 style 標(biāo)簽實(shí)時(shí)更新菇肃;
- JS 既可以通過(guò)添加 script 標(biāo)簽拉取執(zhí)行,也可以通過(guò)接口獲取 JS 文本取募,再使用 eval/Function 等 API 執(zhí)行琐谤。
小程序動(dòng)態(tài)化存在的問(wèn)題:
- Runtime 接口缺失(沒(méi)有開(kāi)放 eval/Function 功能),導(dǎo)致無(wú)法直接執(zhí)行交互腳本玩敏。
- 橋的缺失斗忌,導(dǎo)致無(wú)法直接操作更新視圖元素叉瘩。
為了解決這兩個(gè)問(wèn)題权埠,需要開(kāi)發(fā):
- JSVM —— 在小程序端能夠執(zhí) JS AST 的 runtime;
- 渲染模板引擎 —— 能夠?qū)魅氲哪0宀季峙渲眠€原成小程序頁(yè)面。
架構(gòu)圖
處理過(guò)程解析
1. 動(dòng)態(tài)組件
(1)動(dòng)態(tài)組件設(shè)置case屬性后填帽,通過(guò)接口獲取云端代碼(在遠(yuǎn)端編寫(xiě)的組件代碼)砰粹,dom即wxml-ast唧躲, jscode即js-ast;
(2) 使用內(nèi)置jsvm 解析 js-ast:
(3) 解析wxml-ast碱璃,將傳入的模板布局配置還原成小程序頁(yè)面弄痹。參照上圖需要解決兩個(gè)問(wèn)題:遞歸渲染vnode,綁定交互事件嵌器。
2.如何遞歸渲染界酒,綁定交互事件
動(dòng)態(tài)組件其內(nèi)部有一個(gè)模板,把所有標(biāo)簽動(dòng)寫(xiě)成一個(gè)template嘴秸。例如:<template is="button">
.當(dāng)拿到遠(yuǎn)端的dom的ast后毁欣,遞歸渲染每一個(gè)標(biāo)簽庇谆。樣式是通過(guò)style添加到每個(gè)標(biāo)簽上的。
3.內(nèi)置jsvm
抽象語(yǔ)法樹(shù)是由編譯器(complier)在語(yǔ)法分析階段產(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
}
}
}