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

前言

可以說,MV*框架最核心的三個點就是

  1. 模板怎么轉(zhuǎn)化成代碼的虱而?
  2. 代碼又是怎么轉(zhuǎn)化成模板的丸冕?
  3. 模板的依賴和代碼中的數(shù)據(jù)是怎么響應(yīng)式關(guān)聯(lián)起來的?

這篇文章我們一起來探究一下第二個點:code->html薛窥,因為這個步驟Moon提供了現(xiàn)成的api:render胖烛。

render的使用

我們先來看看官方的用例:

new Moon({
  render: function(m) {
    return m('h1', {attrs: {}}, {shouldRender: false}, [m("#text", {shouldRender: false}, "Hello Moon!")]);
    // same as <h1>Hello Moon!</h1>
  }
});

可以看出,m(...)將會和在HTML里直接寫<h1>Hello Moon!</h1>造成一樣的效果

m函數(shù)的實現(xiàn)

Yeah诅迷,我們現(xiàn)在知道關(guān)鍵就在于這個m函數(shù)了佩番,其實它和Vue中的h函數(shù)也是類似的。
由于JS運算符優(yōu)先級的規(guī)則罢杉,會先調(diào)用m("#text", {shouldRender: false}, "Render Moon!"):

render-m.jpg

m函數(shù)傳入tag, attrs, meta, children四個參數(shù)趟畏,可以看出'#text'對應(yīng)type、{shouldRender: false}對應(yīng)attrs滩租、"Render Mooin"對應(yīng)meta赋秀、undefined對應(yīng)children利朵,返回一個createElement函數(shù)調(diào)用的結(jié)果,我們再看注釋猎莲,發(fā)現(xiàn)這個createElement函數(shù)調(diào)用完后返回的其實就是一個VNode绍弟。

同理我們可以推測出在內(nèi)層m函數(shù)執(zhí)行完之后,外層m函數(shù)調(diào)用時'h1'對應(yīng)type著洼、attrs: {}對應(yīng)attrs樟遣、{shouldRender: false}對應(yīng)meta、[m("#text", {shouldRender: false}, "Render Moon!")]對應(yīng)children身笤。

所以豹悬,這個m函數(shù)負責把code轉(zhuǎn)成VNode。

createElement

讓我們再進入createElement函數(shù):


render-createElement.jpg

它很簡單有木有~就是返回了只有五個鍵的一個對象液荸,而這個對象就是我們俗稱的VNode瞻佛!

從這個過程中,我們發(fā)現(xiàn)m函數(shù)其實還有一個解析組件的流程娇钱,它的判斷條件很耐人尋味:

(component = components[tag]) !== undefined
//等價于
(component = components[tag]) && component !== undefined

這里涉及到一個全局變量:components伤柄,它是在index.js里聲明的,用來存儲當前實例的組件忍弛,所以其實這里就是判斷render函數(shù)渲染的標簽是不是我們自己定義的。

如果是就會創(chuàng)建一個函數(shù)式的組件或者讓meta的component屬性引用這個組件考抄,至于細節(jié)细疚,等我們以后看到組件部分再分析。

總之川梅,m函數(shù)非常關(guān)鍵疯兼,作者也給了注釋:無論怎么樣m函數(shù)都會返回一個VNode的數(shù)據(jù):

{
       type: 'h1', <= nodename
       props: {
         attrs: {'id': 'someId'}, <= regular attributes
         dom: {'textContent': 'some text content'} <= only for DOM properties added by directives,
         directives: {'m-mask': ''} <= any directives
       },
       meta: {}, <= metadata used internally
       children: [], <= any child nodes
}

調(diào)用m的前奏

了解完m函數(shù)的實現(xiàn)細節(jié)后,我們執(zhí)行代碼贫途,程序會先進入Moon里定義$render:

//defineProperty其實就是this[$render]=options.render
defineProperty(this, "$render", options.render, noop);

繼續(xù)走下去吧彪,我們就進入了init和mount函數(shù),可以看出它設(shè)置了當前實例的$el丢早、$destroyed姨裸、$template、$render屬性(可選)怨酝,然后調(diào)用build函數(shù)和觸發(fā)mounted鉤子傀缩。


render-mount.jpg

在build函數(shù)里調(diào)用render和m

到build里就是進入正片了:


render-build.png

render實際上是調(diào)用$render(也就是用戶傳進去的render):


render-VNode.jpg

dom變量存儲了render生成的(實際上是m生成的)新VNode、old存儲當前的Node(可能是原生DOM節(jié)點也有可能是VNode)农猬,接著調(diào)用patch函數(shù)對兩個node做一些事情赡艰。

patch:vnode->html

進入patch函數(shù),可以發(fā)現(xiàn)old對應(yīng)#app4這個DOM節(jié)點斤葱,vnode對應(yīng)render生成的VNode節(jié)點慷垮,parent是body元素揖闸。


render-patch.jpg

因為old沒有meta屬性,所以不是VNode料身。(VNode有meta屬性)于是接著判斷old是不是Node類型汤纸。

很明顯,是hydrate函數(shù)把VNode變成一個原生DOM的newNode的惯驼。

之后還對比了newNode和old蹲嚣,從例子中可以看出顯然h1和app4不一樣,這時當前實例的$el就被替換成了vnode中的元素祟牲,當前moon實例也被替換掉了隙畜。原先#app4的位置就變成了h1元素。

再看看沒進入的那個流程:即old有meta屬性時说贝,也就是old也是一個VNode時(DOM樹上的node沒有meta屬性)议惰,這個時候會直接使用一個createNodeFromVNode和replaceChild方法更改html元素,關(guān)于這兩個方法乡恕,我們后面會提到言询。

hydrate:真正的執(zhí)行者

hydrate函數(shù)的執(zhí)行流程非常復雜,所以我畫了個流程圖:


render-hydrate.png

這里的node即之前patch函數(shù)中的old節(jié)點傲宜,vnode即render函數(shù)生成的節(jié)點运杭,parent是實例的根元素。

可以看出它是先獲取了node的nodeName函卒,然后進入一個非常復雜的判斷:這一系列的判斷目的只有一個辆憔,那就是把vnode的內(nèi)容轉(zhuǎn)化到瀏覽器DOM上去。

在我們的例子中报嵌,node是原生的#app4DOM節(jié)點虱咧,vnode是render生成的h1虛擬節(jié)點,所以需要調(diào)用createNodeFromVNode函數(shù)锚国。

node和vnode的hydrate

這個過程會涉及到createNodeFromVNode腕巡、diffProps、replaceChild血筑、Moon.destroy绘沉、Moon.off、callHook豺总、extractAttrs梆砸、addEventListeners、removeChild等方法

  1. createNodeFromVNode根據(jù)vnode對象創(chuàng)建一個真實node:


    createNodeFromVNode.jpg

    先根據(jù)vnode的類型創(chuàng)建一個元素,類型是文本或SVG的話就創(chuàng)建文本或SVG元素园欣。
    如果只有一個子元素的時候帖世,直接混入。有多個子元素的時候需要迭代調(diào)用appendChild函數(shù)并把子VNode也通過createNodeFromVNode轉(zhuǎn)化成DOM的Node。
    最后根據(jù)vnode.meta.eventListeners給這個Node添加事件監(jiān)聽邏輯日矫、通過diffProps設(shè)置屬性(包括attr赂弓、prop、directive)哪轿、Hydrate(也就是把node作為vnode.meta的一個屬性盈魁,方便兩者同步更新)。
    可以說這個hydrate讓vnode和node你中有我窃诉,我中有你了杨耙。

  2. diffProps
    接下來讓我們看看diffProps是如何設(shè)置屬性的:

    diffProps.jpg

    進入以后我們發(fā)現(xiàn)其實是對attrs、props進行diff并且執(zhí)行相關(guān)的directive飘痛,關(guān)于attr和props的區(qū)別不明白的讀者可以自行搜索一下珊膜,簡單來說就是attr存在于html元素上,props存在于DOM元素上宣脉。
    2.1. 對于attr(作者注釋為Node Props)车柠,vnode只是一個參照物,始終操縱node塑猖,如果node有vnode沒有就remove這個attr竹祷,如果node沒有vnode有就add這個attr。
    2.2. 對于prop(作者注釋為DOM Props)羊苟,會從vnode.props.dom中拿出prop去匹配node塑陵,如果沒有匹配到就給node加上。
    2.3. 對于directive蜡励,會從vnode.props.directives拿出directive去匹配directives令花,如果匹配到了就執(zhí)行相關(guān)指令。

  3. replaceChild
    這個函數(shù)會替換一個子元素巍虫,其實就是封裝了dom原生提供的replaceChild彭则。

    replaceChild.jpg

    它會先銷毀當前moon的實例(componentInstance)鳍刷,然后用newNode替換oldNode占遥,最后通過createComponentFromVNode對里面存在的組件進行轉(zhuǎn)化(如果有的話)。
    3.1 destroy
    順便看下銷毀實例的函數(shù):
    destroy.jpg

    里面非常簡潔:移除事件監(jiān)聽->移除dom引用->$destroy標志位置為真->調(diào)用destroy鉤子
    3.2 off
    再來看看怎么移除事件監(jiān)聽的:
    off.jpg

    產(chǎn)生了三條分支:事件名參數(shù)為空->移除所有事件输瓜;回調(diào)名參數(shù)為空->移除所有事件回調(diào)瓦胎;參數(shù)都存在->移除這個特定的事件回調(diào)

  4. callHook
    順理成章地會走調(diào)用鉤子的函數(shù)


    callHook.jpg

    兩步搞定:獲取這個鉤子->當前實例調(diào)用這個鉤子方法

  5. extractAttrs


    extractAttrs.jpg

    把當前node的attrs抽離出來,返回一個attrs對象尤揣。

  6. addEventListeners
    這個需要在render函數(shù)的meta參數(shù)里傳入一個eventListeners對象才可以觸發(fā):
    eventListeners: { "click": [function() { console.log("click") },function() { console.log("click2") } ], "dblclick": [function() { console.log("double") }] }

    addEventListeners.jpg

    它首先會先聲明一個addHandler函數(shù)搔啊,這是個閉包函數(shù),然后遍歷eventListeners逐個調(diào)用這個addHandler函數(shù)北戏。
    在addHandler里面又聲明了一個handle函數(shù)负芋,它的目的就是把某個具體事件相關(guān)的回調(diào)全部調(diào)用一遍。至于這些回調(diào)嗜愈,都存儲在handle.handlers里旧蛾。
    這些原來聲明的回調(diào)數(shù)組就轉(zhuǎn)化成了一個handle函數(shù)莽龟,最后把這個handle函數(shù)才是node的實際事件回調(diào)。

  7. removeChild
    這個函數(shù)和replaceChild類似锨天,都是封裝了原生的同名api:


    removeChild.jpg

    因為原來的node有子元素毯盈,所以就需要移除。hydrate的后半部分也是圍繞子元素展開的:首先把vnode的child和node的child獲取到病袄,然后廣度遍歷node的child和vchild搂赋,逐個和vchild進行hydrate


    hydrate-child.jpg

總結(jié)

之后,調(diào)用棧就沿著hydrate->patch->build->mount->init一步步回退益缠,結(jié)束了整個過程脑奠。也就是說,code->html轉(zhuǎn)開就是code->vnode->node->html的過程左刽。

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末捺信,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子欠痴,更是在濱河造成了極大的恐慌迄靠,老刑警劉巖,帶你破解...
    沈念sama閱讀 212,718評論 6 492
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件喇辽,死亡現(xiàn)場離奇詭異掌挚,居然都是意外死亡,警方通過查閱死者的電腦和手機菩咨,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 90,683評論 3 385
  • 文/潘曉璐 我一進店門吠式,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人抽米,你說我怎么就攤上這事胖齐§偶樱” “怎么了?”我有些...
    開封第一講書人閱讀 158,207評論 0 348
  • 文/不壞的土叔 我叫張陵,是天一觀的道長谚攒。 經(jīng)常有香客問我止毕,道長牌芋,這世上最難降的妖魔是什么乎完? 我笑而不...
    開封第一講書人閱讀 56,755評論 1 284
  • 正文 為了忘掉前任,我火速辦了婚禮亡容,結(jié)果婚禮上嗤疯,老公的妹妹穿的比我還像新娘。我一直安慰自己闺兢,他們只是感情好茂缚,可當我...
    茶點故事閱讀 65,862評論 6 386
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般脚囊。 火紅的嫁衣襯著肌膚如雪帖汞。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 50,050評論 1 291
  • 那天凑术,我揣著相機與錄音翩蘸,去河邊找鬼。 笑死淮逊,一個胖子當著我的面吹牛催首,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播泄鹏,決...
    沈念sama閱讀 39,136評論 3 410
  • 文/蒼蘭香墨 我猛地睜開眼郎任,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了备籽?” 一聲冷哼從身側(cè)響起舶治,我...
    開封第一講書人閱讀 37,882評論 0 268
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎车猬,沒想到半個月后霉猛,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 44,330評論 1 303
  • 正文 獨居荒郊野嶺守林人離奇死亡珠闰,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 36,651評論 2 327
  • 正文 我和宋清朗相戀三年惜浅,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片伏嗜。...
    茶點故事閱讀 38,789評論 1 341
  • 序言:一個原本活蹦亂跳的男人離奇死亡坛悉,死狀恐怖,靈堂內(nèi)的尸體忽然破棺而出承绸,到底是詐尸還是另有隱情裸影,我是刑警寧澤,帶...
    沈念sama閱讀 34,477評論 4 333
  • 正文 年R本政府宣布军熏,位于F島的核電站轩猩,受9級特大地震影響,放射性物質(zhì)發(fā)生泄漏羞迷。R本人自食惡果不足惜界轩,卻給世界環(huán)境...
    茶點故事閱讀 40,135評論 3 317
  • 文/蒙蒙 一画饥、第九天 我趴在偏房一處隱蔽的房頂上張望衔瓮。 院中可真熱鬧,春花似錦抖甘、人聲如沸热鞍。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,864評論 0 21
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽薇宠。三九已至偷办,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間澄港,已是汗流浹背椒涯。 一陣腳步聲響...
    開封第一講書人閱讀 32,099評論 1 267
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留回梧,地道東北人废岂。 一個月前我還...
    沈念sama閱讀 46,598評論 2 362
  • 正文 我出身青樓,卻偏偏與公主長得像狱意,于是被迫代替她去往敵國和親湖苞。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 43,697評論 2 351

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