前言
MV*框架中模板轉(zhuǎn)化成代碼的的過程涉及到編譯原理治泥,把這個(gè)html->code和第二篇文章的code->html連接起來就是完整的一個(gè)流程圆雁。
詞法分析和語法分析
html其實(shí)就是一系列的字符串炮温,這個(gè)將字符串轉(zhuǎn)化成可執(zhí)行代碼的過程一般就叫做編譯或者轉(zhuǎn)譯涛碑,如果會(huì)轉(zhuǎn)成機(jī)器代碼就是編譯蚪腋,否則就是轉(zhuǎn)譯即寒。
很顯然,MV*框架做的事情也是轉(zhuǎn)譯憨降。而這個(gè)轉(zhuǎn)譯又主要分為詞法分析和語法分析兩個(gè)過程父虑。
詞法分析
詞法分析就是將一個(gè)完整的句子分割成各個(gè)獨(dú)立的單元的過程,這些單元被稱為tokens授药。
例如一個(gè)表達(dá)式是“3+2”士嚎,那么tokens就是3、+悔叽、2莱衩。
同理一段html代碼<p>Some HTML</p>的tokens就是<p>、Some HTML娇澎、</p>
語法分析
語法分析是在詞法分析之后的笨蚁,它的目的是將這個(gè)tokens組織起來變成可執(zhí)行的結(jié)構(gòu)。
在MV*框架里通常就是將tokens轉(zhuǎn)換成一顆AST(抽象語法樹)九火。
compile的使用
Moon的官網(wǎng)提及了Moon.compile的使用:
Moon.compile("<p>Some HTML</p>");
它將會(huì)被轉(zhuǎn)成如下code:
function anonymous(m) {
var instance = this;
return m("p", {attrs: {}}, {"shouldRender": false}, [m("#text", {"shouldRender": false}, "Some HTML")]);
}
也就是我們第二篇文章中render函數(shù)用到的m函數(shù)赚窃!之后大家可以自行推測(cè)出后續(xù)過程了吧~
compile的實(shí)現(xiàn)
我們重新new一個(gè)Moon實(shí)例,打一個(gè)斷點(diǎn)跟到compile:
實(shí)現(xiàn)很簡(jiǎn)潔岔激,有沒有大吃一驚勒极?
其實(shí)這就是一個(gè)template通過詞法分析器lex生成tokens,tokens又通過語法分析器生成ast虑鼎,最后通過ast生成node的過程辱匿。
lex
lex的實(shí)現(xiàn):
var lex = function(input) {
var state = {
input: input,
current: 0,
tokens: []
}
lexState(state);
return state.tokens;
}
它構(gòu)造了一個(gè)state對(duì)象交給lexState分析键痛,分析完后直接拿結(jié)果返回。
lexState
它其實(shí)也就是對(duì)input進(jìn)行分流處理匾七,有三條分支:不以<開頭的轉(zhuǎn)到lexText絮短、以<!--開頭的轉(zhuǎn)到lexComment、其余全部給lexTag處理昨忆。
也就是說它把tokens分成三種類型:文本丁频、注釋、標(biāo)簽邑贴。
var lexState = function(state) {
var input = state.input;
var len = input.length;
while (state.current < len) {
// Check if it is text
if (input.charAt(state.current) !== "<") {
lexText(state);
continue;
}
// Check if it is a comment
if (input.substr(state.current, 4) === "<!--") {
lexComment(state);
continue;
}
// It's a tag
lexTag(state);
}
}
- lexTag
var lexTag = function(state) {
var input = state.input;
var len = input.length;
// Lex Starting of Tag
var isClosingStart = input.charAt(state.current + 1) === "/";
state.current += isClosingStart === true ? 2 : 1;
// Lex type and attributes
var tagToken = lexTagType(state);
lexAttributes(tagToken, state);
// Lex ending tag
var isClosingEnd = input.charAt(state.current) === "/";
state.current += isClosingEnd === true ? 2 : 1;
// Check if Closing Start
if (isClosingStart === true) {
tagToken.closeStart = true;
}
// Check if Closing End
if (isClosingEnd === true) {
tagToken.closeEnd = true;
}
}
lexTag會(huì)通過lexTagType生成一個(gè)tagToken席里,然后通過lexAttributes給tagToken補(bǔ)充attr,最后通過isClosingStart判斷它是一個(gè)開始標(biāo)簽還是一個(gè)結(jié)束標(biāo)簽拢驾。也就是把<p>Some HTML</p>的<p>和</p>這兩個(gè)token找出來奖磁。
1.1 lexTagType
它負(fù)責(zé)找出標(biāo)簽token的type,例如是p或div:
它會(huì)對(duì)模板字符串中每個(gè)字符分析直到有"/"或者">"或者" "才結(jié)束繁疤,然后生成一個(gè)tagToken咖为,push到state里,返回稠腊。在這個(gè)例子里躁染,我們的tagType是div。
1.2 lexAttributes
它負(fù)責(zé)找出標(biāo)簽token的attr麻养,例如id或class:
它會(huì)使用雙索引滑動(dòng)窗口方法來判斷一個(gè)字符串是否是attr,期間聲明了一個(gè)incrementChar函數(shù)用來滑動(dòng)窗口褐啡,在窗口中檢測(cè)到>或者">就說明這是一個(gè)標(biāo)簽的結(jié)尾,并且忽略空格,從=拆分鳖昌,左邊一個(gè)attr名,右邊是一個(gè)attr值低飒。然后使用attributes對(duì)象來存儲(chǔ)所有的attr许昨,每個(gè)attr是一個(gè)attrValue對(duì)象:
{
name: attrName,
value: "",
meta: {}
}
在經(jīng)過一系列的查找后,lexAttributes成功地把tagToken的attrs悉數(shù)找出褥赊,然后設(shè)置到tagToken.attributes里去糕档。
-
lexText
它負(fù)責(zé)找出文本類型的token:
很有趣的一點(diǎn)是:它是根據(jù)一個(gè)正則來獲取當(dāng)前文本的結(jié)尾位置的。
var tagOrCommentStartRE = /<\/?(?:[A-Za-z]+\w*)|<!--/;
在找到文本token后還分兩種情況:純文本和部分文本拌喉。如果是純文本就直接從current這個(gè)索引截取剩下的部分速那,如果是部分文本就只截取滑動(dòng)窗口的內(nèi)容。
-
lexComment
它負(fù)責(zé)找出注釋類型的token:
實(shí)現(xiàn)方法和lexText類似尿背,只不過注釋結(jié)尾比較好找端仰,發(fā)現(xiàn)-->就行了。
- 結(jié)果
最初的html:
<div id="app" class="container">
<h2 class="text-center" m-on:click="haha()">{{msg}}</h2>
<h2 class="text-center" m-on:click="haha()">{{computeData}}</h2>
<!-- 這是注釋 -->
</div>
最后得到這個(gè)結(jié)果:
parse的實(shí)現(xiàn)
parse函數(shù)的實(shí)現(xiàn)同樣很簡(jiǎn)潔:
它建立了一個(gè)root對(duì)象來存儲(chǔ)ast,每個(gè)子元素通過parseWalk得出田藐。
parseWalk
parseWalk是用來遍歷tokens的:
它會(huì)構(gòu)造token荔烧、previousToken吱七、nextToken三個(gè)指針,然后聲明了一個(gè)move函數(shù)用來移動(dòng)這三個(gè)指針鹤竭,有點(diǎn)類似鏈表的操作踊餐。
在當(dāng)前token的類型是文本或者注釋的時(shí)候,會(huì)執(zhí)行move臀稚,只不過一個(gè)返回previousToken.value一個(gè)返回null吝岭,因?yàn)槲谋镜那懊婵隙ㄊ菢?biāo)簽,而注釋就沒有處理的意義了吧寺。下面進(jìn)入正題:type是tag的時(shí)候:
它會(huì)先獲取tagType苍碟、closeStart、closeEnd撮执,還會(huì)判斷這個(gè)標(biāo)簽是不是SVG或者空元素微峰,之后通過createParseNode創(chuàng)建一個(gè)parseNode(也就是ast樹的一個(gè)node),接著又兵分三路抒钱,只重點(diǎn)處理非空非svg無closeStart的標(biāo)簽蜓肆。
這里有個(gè)難琢磨的地方就是遞歸parseWalk生成子節(jié)點(diǎn),這里需要自己多跟一下代碼谋币。
createParseNode
其實(shí)就是創(chuàng)建一個(gè)ast的node對(duì)象:
parse結(jié)果0.
generate的實(shí)現(xiàn)
generate一改之前l(fā)exer和parse的簡(jiǎn)潔仗扬,變成很難讀的樣子了:
不過還好,增加點(diǎn)耐心就沒事蕾额。
可以看出ast這顆樹實(shí)際上是掛載在root的children上早芭,這里聲明了一個(gè)state對(duì)象,里面包括了attr诅蝶、directive退个、dep,接著通過generateNode生成若干個(gè)m函數(shù)调炬,用dependencies把dep拿出來生成dependenciesCode最終拼接成最終的執(zhí)行代碼语盈,然后返回根據(jù)這段代碼生成的render函數(shù)。
generateNode
這個(gè)函數(shù)負(fù)責(zé)生成vnode
可以看出來這里還是兵分三路:
- node是string類型
- node是slot類型
-
其他情況
它們分別做了什么呢缰泡?第一種情況直接編譯模板和meta刀荒,第二種情況是對(duì)slot進(jìn)行處理,最后是一般情況對(duì)meta棘钞、prop缠借、directive、children等進(jìn)行處理宜猜,最終返回生成好的調(diào)用代碼泼返。
defaultMetadata
它負(fù)責(zé)生成默認(rèn)渲染配置的對(duì)象:
generateProps
generateProps是個(gè)大函數(shù),涉及到很多小函數(shù)
它先獲取props,然后把這個(gè)props丟進(jìn)vnode的attrs宝恶,接著處理指令符隙。接著開始生成props代碼趴捅,和指令匹配,如果匹配到就進(jìn)入beforeGenerate霹疫、afterGenerate拱绑、duringGenerate里。
-
beforeGenerate
這個(gè)函數(shù)是存在于各個(gè)內(nèi)置指令對(duì)象里的丽蝎,除了它之外猎拨,還有duringPropGenerate和afterGenerate方法。
這里以m-on為例,它會(huì)獲取事件名屠阻、事件回調(diào)红省,然后編譯模板表達(dá)式,生成修飾符国觉,最后加上事件監(jiān)聽代碼:
-
compileTemplateExpression
它負(fù)責(zé)編譯模板里的依賴表達(dá)式:
它會(huì)獲取模板里的dependencies吧恃,或者說{{}}里面的表達(dá)式。
-
addEventListenerCodeToVNode
它負(fù)責(zé)給vnode添加事件監(jiān)聽:
它會(huì)從vnode.meta獲取eventListeners麻诀,最終取出eventHandlers給它加入handler來給vnode添加上事件監(jiān)聽痕寓。
-
compileTemplate->compileTemplateState
compileTemplate負(fù)責(zé)編譯一個(gè)模板,具體工作交給compileTemplateState做:
compileTemplateState對(duì)模板做了什么呢蝇闭?它會(huì)通過escapeString先進(jìn)行一些轉(zhuǎn)義工作:
如果是{{}}表達(dá)式呻率,會(huì)通過scanTemplateStateUntil掃描依賴和scanTemplateStateForWhitespace去除空格:
這樣就得到了{(lán){}}里面的依賴,接著調(diào)用compileTemplateExpression編譯這個(gè)表達(dá)式,迭代進(jìn)行下去這個(gè)過程把dependencies收集過來。 -
scanTemplateStateUntil
這其實(shí)就是個(gè)掃描器呻引,返回{{}}里面真正的表達(dá)式:
-
scanTemplateStateForWhitespace
它同樣是個(gè)掃描器礼仗,不過它的作用是掃描空格然后略過去:
generateMeta和generateEventlisteners
它們分別生成meta執(zhí)行代碼和事件執(zhí)行代碼,在generateMeta中對(duì)meta里的eventListeners進(jìn)行特殊處理進(jìn)到generateEventlisteners里去逻悠。
var generateMeta = function(meta) {
var metaCode = "{";
for (var key in meta) {
if (key === "eventListeners") {
metaCode += generateEventlisteners(meta[key])
} else {
metaCode += "\"" + key + "\": " + (meta[key]) + ", ";
}
}
metaCode = metaCode.substring(0, metaCode.length - 2) + "}, ";
return metaCode;
}
var generateEventlisteners = function(eventListeners) {
var eventListenersCode = "\"eventListeners\": {";
for (var type in eventListeners) {
var handlers = eventListeners[type];
eventListenersCode += "\"" + type + "\": [";
for (var i = 0; i < handlers.length; i++) {
eventListenersCode += (handlers[i]) + ", ";
}
eventListenersCode = eventListenersCode.substring(0, eventListenersCode.length - 2) + "], ";
}
eventListenersCode = eventListenersCode.substring(0, eventListenersCode.length - 2) + "}, ";
return eventListenersCode;
}
總結(jié)
html->code總體經(jīng)歷了三個(gè)階段:lex語法分析元践、compile詞法分析、generate生成可執(zhí)行代碼蹂风,實(shí)際過程就是html->tokens->ast->code卢厂。