深入淺出MV*框架源碼(三):Moon的html->code實(shí)現(xiàn)

前言

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:


Moon-compile.jpg
compile.jpg

實(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);
    }
}
  1. 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:


lexTagType.jpg

它會(huì)對(duì)模板字符串中每個(gè)字符分析直到有"/"或者">"或者" "才結(jié)束繁疤,然后生成一個(gè)tagToken咖为,push到state里,返回稠腊。在這個(gè)例子里躁染,我們的tagType是div。

1.2 lexAttributes
它負(fù)責(zé)找出標(biāo)簽token的attr麻养,例如id或class:


lexAttributes.jpg

它會(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: {}
}
lexAttributes2.jpg

在經(jīng)過一系列的查找后,lexAttributes成功地把tagToken的attrs悉數(shù)找出褥赊,然后設(shè)置到tagToken.attributes里去糕档。

  1. lexText
    它負(fù)責(zé)找出文本類型的token:


    lexText.jpg

很有趣的一點(diǎn)是:它是根據(jù)一個(gè)正則來獲取當(dāng)前文本的結(jié)尾位置的。

var tagOrCommentStartRE = /<\/?(?:[A-Za-z]+\w*)|<!--/;

在找到文本token后還分兩種情況:純文本和部分文本拌喉。如果是純文本就直接從current這個(gè)索引截取剩下的部分速那,如果是部分文本就只截取滑動(dòng)窗口的內(nèi)容。

  1. lexComment
    它負(fù)責(zé)找出注釋類型的token:


    lexComment.jpg

    實(shí)現(xiàn)方法和lexText類似尿背,只不過注釋結(jié)尾比較好找端仰,發(fā)現(xiàn)-->就行了。

  2. 結(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é)果:


lex-result.jpg

parse的實(shí)現(xiàn)

parse函數(shù)的實(shí)現(xiàn)同樣很簡(jiǎn)潔:


parse.jpg

它建立了一個(gè)root對(duì)象來存儲(chǔ)ast,每個(gè)子元素通過parseWalk得出田藐。

parseWalk

parseWalk是用來遍歷tokens的:


parseWalk-1.jpg

它會(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í)候:


parseWalk-2.jpg

它會(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ì)象:


createParseNode.jpg

parse結(jié)果0.

parse-result.jpg

generate的實(shí)現(xiàn)

generate一改之前l(fā)exer和parse的簡(jiǎn)潔仗扬,變成很難讀的樣子了:


generate.jpg

不過還好,增加點(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


generateNode1.jpg

可以看出來這里還是兵分三路:

  1. node是string類型
  2. node是slot類型
  3. 其他情況
    它們分別做了什么呢缰泡?第一種情況直接編譯模板和meta刀荒,第二種情況是對(duì)slot進(jìn)行處理,最后是一般情況對(duì)meta棘钞、prop缠借、directive、children等進(jìn)行處理宜猜,最終返回生成好的調(diào)用代碼泼返。


    generateNode2.jpg

defaultMetadata

它負(fù)責(zé)生成默認(rèn)渲染配置的對(duì)象:


defaultMetadata.jpg

generateProps

generateProps是個(gè)大函數(shù),涉及到很多小函數(shù)


generateProps.jpg

它先獲取props,然后把這個(gè)props丟進(jìn)vnode的attrs宝恶,接著處理指令符隙。接著開始生成props代碼趴捅,和指令匹配,如果匹配到就進(jìn)入beforeGenerate霹疫、afterGenerate拱绑、duringGenerate里。

  1. beforeGenerate
    這個(gè)函數(shù)是存在于各個(gè)內(nèi)置指令對(duì)象里的丽蝎,除了它之外猎拨,還有duringPropGenerate和afterGenerate方法。
    這里以m-on為例,它會(huì)獲取事件名屠阻、事件回調(diào)红省,然后編譯模板表達(dá)式,生成修飾符国觉,最后加上事件監(jiān)聽代碼:


    m-on-beforeGenerate.jpg
  2. compileTemplateExpression
    它負(fù)責(zé)編譯模板里的依賴表達(dá)式:


    compileTemplateExpression.jpg

    它會(huì)獲取模板里的dependencies吧恃,或者說{{}}里面的表達(dá)式。

  3. addEventListenerCodeToVNode
    它負(fù)責(zé)給vnode添加事件監(jiān)聽:


    addEventListenerCodeToVNode.jpg

    它會(huì)從vnode.meta獲取eventListeners麻诀,最終取出eventHandlers給它加入handler來給vnode添加上事件監(jiān)聽痕寓。

  4. compileTemplate->compileTemplateState
    compileTemplate負(fù)責(zé)編譯一個(gè)模板,具體工作交給compileTemplateState做:


    compileTemplate.jpg

    compileTemplateState對(duì)模板做了什么呢蝇闭?它會(huì)通過escapeString先進(jìn)行一些轉(zhuǎn)義工作:


    escapeString.jpg

    如果是{{}}表達(dá)式呻率,會(huì)通過scanTemplateStateUntil掃描依賴和scanTemplateStateForWhitespace去除空格:
    compileTemplateState.jpg

    這樣就得到了{(lán){}}里面的依賴,接著調(diào)用compileTemplateExpression編譯這個(gè)表達(dá)式,迭代進(jìn)行下去這個(gè)過程把dependencies收集過來。
  5. scanTemplateStateUntil
    這其實(shí)就是個(gè)掃描器呻引,返回{{}}里面真正的表達(dá)式:


    scanTemplateStateUntil.jpg
  6. scanTemplateStateForWhitespace
    它同樣是個(gè)掃描器礼仗,不過它的作用是掃描空格然后略過去:


    scanTemplateStateForWhitespace.jpg

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卢厂。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市惠啄,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌任内,老刑警劉巖撵渡,帶你破解...
    沈念sama閱讀 212,718評(píng)論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場(chǎng)離奇詭異死嗦,居然都是意外死亡趋距,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評(píng)論 3 385
  • 文/潘曉璐 我一進(jìn)店門越除,熙熙樓的掌柜王于貴愁眉苦臉地迎上來节腐,“玉大人外盯,你說我怎么就攤上這事∫砣福” “怎么了饱苟?”我有些...
    開封第一講書人閱讀 158,207評(píng)論 0 348
  • 文/不壞的土叔 我叫張陵凯力,是天一觀的道長(zhǎng)岖寞。 經(jīng)常有香客問我,道長(zhǎng)窄俏,這世上最難降的妖魔是什么狈邑? 我笑而不...
    開封第一講書人閱讀 56,755評(píng)論 1 284
  • 正文 為了忘掉前任城须,我火速辦了婚禮,結(jié)果婚禮上米苹,老公的妹妹穿的比我還像新娘糕伐。我一直安慰自己,他們只是感情好蘸嘶,可當(dāng)我...
    茶點(diǎn)故事閱讀 65,862評(píng)論 6 386
  • 文/花漫 我一把揭開白布良瞧。 她就那樣靜靜地躺著,像睡著了一般亏较。 火紅的嫁衣襯著肌膚如雪莺褒。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 50,050評(píng)論 1 291
  • 那天雪情,我揣著相機(jī)與錄音遵岩,去河邊找鬼。 笑死巡通,一個(gè)胖子當(dāng)著我的面吹牛尘执,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播宴凉,決...
    沈念sama閱讀 39,136評(píng)論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼誊锭,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來了弥锄?” 一聲冷哼從身側(cè)響起丧靡,我...
    開封第一講書人閱讀 37,882評(píng)論 0 268
  • 序言:老撾萬榮一對(duì)情侶失蹤,失蹤者是張志新(化名)和其女友劉穎籽暇,沒想到半個(gè)月后温治,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,330評(píng)論 1 303
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡戒悠,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 36,651評(píng)論 2 327
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了绸狐。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片卤恳。...
    茶點(diǎn)故事閱讀 38,789評(píng)論 1 341
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡累盗,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出突琳,到底是詐尸還是另有隱情若债,我是刑警寧澤,帶...
    沈念sama閱讀 34,477評(píng)論 4 333
  • 正文 年R本政府宣布本今,位于F島的核電站拆座,受9級(jí)特大地震影響,放射性物質(zhì)發(fā)生泄漏冠息。R本人自食惡果不足惜挪凑,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 40,135評(píng)論 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望逛艰。 院中可真熱鬧躏碳,春花似錦、人聲如沸散怖。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,864評(píng)論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽镇眷。三九已至咬最,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間欠动,已是汗流浹背永乌。 一陣腳步聲響...
    開封第一講書人閱讀 32,099評(píng)論 1 267
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留具伍,地道東北人翅雏。 一個(gè)月前我還...
    沈念sama閱讀 46,598評(píng)論 2 362
  • 正文 我出身青樓,卻偏偏與公主長(zhǎng)得像人芽,于是被迫代替她去往敵國和親望几。 傳聞我的和親對(duì)象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 43,697評(píng)論 2 351

推薦閱讀更多精彩內(nèi)容