Vue 編譯之parse的思想探討

楔子

筆者公司的前端小組掀起了Vue源碼學(xué)習(xí)小組,前后幾個(gè)月的共同學(xué)習(xí)肝劲,讓小組成員都已經(jīng)對(duì)Vue對(duì)大致框架有了個(gè)模糊對(duì)輪廓。
現(xiàn)在已經(jīng)進(jìn)入第二階段:整理。

我們將小組分為四個(gè)部分碧库,vue對(duì)整理也分為三個(gè)大模塊:數(shù)據(jù)綁定、從template到vnode巧勤、vnode轉(zhuǎn)化為dom對(duì)patch嵌灰。

筆者對(duì)小組被分到了template到vnode對(duì)部分,拿到手后颅悉,感覺內(nèi)容比較多沽瞭,就先將內(nèi)容根據(jù)源碼對(duì)布局分為兩小塊:parse 和 render過程。

再者剩瓶,古人云:兵馬未動(dòng)驹溃,糧草先行。筆者打算先介紹思想和思路儒搭,然后在具體到一些細(xì)枝末節(jié)吠架。

本文準(zhǔn)備到就是parse部分思想。

正文

一搂鲫、整個(gè)編譯過程的目的

我們都知道使用vue到時(shí)候傍药,我們使用定制樣式到幾種方式大致為:

  • 1.options template。
  • 2.el魂仍。(掛載的節(jié)點(diǎn))
  • 3.render 方法拐辽。

我們需要明白到是無論是哪種方式,我們最終的目的都是
生成以vnode為單位vdom樹

而生成的vdom樹擦酌,最終是用于patch過程中俱诸,生成真實(shí)dom節(jié)點(diǎn)。

所以我們的編譯的最終目的是獲得:

Virtue dom赊舶。

形式大致如下圖:

vnode的樣式

暫時(shí)不用管每個(gè)屬性的作用睁搭,我們已經(jīng)知道我們的目標(biāo)是怎樣的了。那么還需要知道的是 起始點(diǎn)和過程笼平。

正常情況下园骆,如果我們這樣初始化的話:

new Vue({
      el: '#el',
      template: `
        <div>

        <div v-for="(item,index) in options" :key="item.id">
          {{item.id}}
          <div>{{item.text}}</div>
        </div>
        
      </div>
      `,
      data: {
        name:"dinglei",
        options: [
          { id: 1, text: 'Hello' },
          { id: 2, text: 'World' }
        ]
      }
    })

那我們的初始化的狀態(tài)則是 template的 一串 String。

當(dāng)然初始化為String的寫法還是滿多的寓调。 比如:

  • 不傳template锌唾,直接使用使用$el。
  • template,使用一個(gè)模版晌涕。

所以我們從的轉(zhuǎn)換過程滋捶,就是一串String 到 virtue dom的過程。

但是值得一提的是余黎,vue 從模版到vdom的過程并非是直接一次性到位的過程重窟。可能是因?yàn)橛却蟮脑O(shè)計(jì)的vnode和原生的dom屬性差距過大惧财,直接編譯成vnode不好完成亲族。其次是考慮到性能優(yōu)化等方面。

所以vue的編譯過程其實(shí)是分為三個(gè)過程:

  • parse
  • optimize
  • codegen for render And render

分別對(duì)應(yīng)三個(gè)過程:

  • 從模版到 astElement
  • 優(yōu)化添加標(biāo)記staticRoot可缚,當(dāng)然這個(gè)層面的static是用于codegen里面的。
  • codegen 生成 render函數(shù)斋枢,render 綁定實(shí)例后執(zhí)行生成vnode帘靡。

接下來我們將進(jìn)入本文的主題

二、parse瓤帚、optimize描姚、codegen的核心思想解讀。

1)parse解讀

首先需要明白astElement包括哪些屬性戈次。在vue源碼 flow文件目錄下的compiler.js可以找到astElement的模型

declare type ASTElement = {
  type: 1;
  tag: string;
  attrsList: Array<ASTAttr>;
  attrsMap: { [key: string]: any };
  rawAttrsMap: { [key: string]: ASTAttr };
  parent: ASTElement | void;
  children: Array<ASTNode>;

  start?: number;
  end?: number;

  processed?: true;

  static?: boolean;
  staticRoot?: boolean;
  staticInFor?: boolean;
  staticProcessed?: boolean;
  hasBindings?: boolean;

  text?: string;
  attrs?: Array<ASTAttr>;
  dynamicAttrs?: Array<ASTAttr>;
  props?: Array<ASTAttr>;
  plain?: boolean;
  pre?: true;
  ns?: string;

  component?: string;
  inlineTemplate?: true;
  transitionMode?: string | null;
  slotName?: ?string;
  slotTarget?: ?string;
  slotTargetDynamic?: boolean;
  slotScope?: ?string;
  scopedSlots?: { [name: string]: ASTElement };

  ref?: string;
  refInFor?: boolean;

  if?: string;
  ifProcessed?: boolean;
  elseif?: string;
  else?: true;
  ifConditions?: ASTIfConditions;

  for?: string;
  forProcessed?: boolean;
  key?: string;
  alias?: string;
  iterator1?: string;
  iterator2?: string;

  staticClass?: string;
  classBinding?: string;
  staticStyle?: string;
  styleBinding?: string;
  events?: ASTElementHandlers;
  nativeEvents?: ASTElementHandlers;

  transition?: string | true;
  transitionOnAppear?: boolean;

  model?: {
    value: string;
    callback: string;
    expression: string;
  };

  directives?: Array<ASTDirective>;

  forbidden?: true;
  once?: true;
  onceProcessed?: boolean;
  wrapData?: (code: string) => string;
  wrapListeners?: (code: string) => string;

  // 2.4 ssr optimization
  ssrOptimizability?: number;

  // weex specific
  appendAsTree?: boolean;
};

整個(gè)流程我們需要的歸納為:

  • 識(shí)別器(parseHTML) (1)
  • 存儲(chǔ)棧(stack) (2)
  • 創(chuàng)建函數(shù)(createASTElement) (3)
  • 上下文(currentParent)(4)

整個(gè)工作方式:

  • 1.就是識(shí)別器(1)利用正則從前到后識(shí)別所有敏感字段轩勘。如(標(biāo)簽、事件怯邪、迭代绊寻、數(shù)據(jù)綁定、插槽等等)
  • 2.open標(biāo)簽 如<div>{{test}}</div>的<div>悬秉,識(shí)別器識(shí)別到此類標(biāo)簽澄步,就會(huì)將div 存放到stack當(dāng)中,利用創(chuàng)建函數(shù)(3)創(chuàng)建createASTElement并處理大部分非組件屬性(具體如v-model和泌、v-if村缸、v-for)。需要注意到是:
    • 1.一元標(biāo)簽如:img等武氓,會(huì)直接走3處理梯皿。
    • p標(biāo)簽會(huì)做特殊處理,目的是為了和瀏覽器等識(shí)別方式達(dá)到目標(biāo)一致县恕。具體點(diǎn)擊此處
  • 3.識(shí)別到閉合標(biāo)簽东羹,則處理下原生屬性如:id 、 class等等弱睦、還有ref百姓、slot、component况木、key等等垒拢,并跟上下文(4)建立起父子關(guān)系旬迹。最終形成了樹的結(jié)構(gòu)。

其中的細(xì)節(jié)特別多求类,如果有興趣請(qǐng)關(guān)注我們的動(dòng)態(tài)奔垦,我們會(huì)在下期給出詳細(xì)過程講解。

2) optimize思想解讀尸疆。

我們?cè)谏厦嬉呀?jīng)給出了astElement的詳細(xì)屬性椿猎,其中有兩個(gè)屬性叫做staticRoot 和 staticInFor。而optimize 的過程寿弱,就是給astElement打上這兩個(gè)標(biāo)記犯眠。這里是為了讓這類靜態(tài)節(jié)點(diǎn),在render過程症革,能夠走緩存的方式筐咧,只渲染一次。

好處很明顯噪矛,能夠減少重復(fù)對(duì)比和渲染的過程量蕊,提高性能。

3)code generate解讀

這個(gè)過程其實(shí)還沒有生成vnode艇挨,而是生成一個(gè)執(zhí)行函數(shù)残炮,且包含了this的執(zhí)行code,其格式如下:

with(this) { _c('div'...xxx...xxx) }

我們?cè)趶?qiáng)調(diào)下缩滨,這次又是從astElement直接到另一個(gè)String势就。
上面astElement是個(gè)樹狀結(jié)構(gòu)。

image

然后在這個(gè)過程脉漏,基本一個(gè)astElement就對(duì)應(yīng)一個(gè)短函數(shù)蛋勺。
最基本短函數(shù)是createElement 也就是_c。
最終的樹狀結(jié)構(gòu)鸠删,會(huì)以函數(shù)的形式表現(xiàn)處理函數(shù)如下

_c(
    'div',
    {
        key:'xxx',
        ref:'xx',
        pre:'xxx',
        domPro:xxx,
        ....
    },
    [ // chidren
        _v(_s('ding')),
        _c('p',{model:'isshow',}, [ ...xxx ])
    ]
)

可以清晰的看到抱完,最終形成的string,依然是一個(gè)樹狀形式刃泡,是以function形式展示的樹狀巧娱,而且所有屬性都已經(jīng)抽離成createElement的第二個(gè)參數(shù)。

一句話概括下code generate做的事情就是:

生成vnode的前置工作烘贴,抽離astElement所有的屬性禁添,形成短函數(shù)鏈。

短函數(shù)對(duì)應(yīng)大致如下:

export function installRenderHelpers (target: any) {
  target._o = markOnce // v-once
  target._n = toNumber
  target._s = toString
  target._l = renderList // v-for
  target._t = renderSlot // slot
  target._q = looseEqual
  target._i = looseIndexOf
  target._m = renderStatic // static 
  target._f = resolveFilter 
  target._k = checkKeyCodes
  target._b = bindObjectProps
  target._v = createTextVNode
  target._e = createEmptyVNode
  target._u = resolveScopedSlots // scopeSlot
  target._g = bindObjectListeners // linsners
  target._d = bindDynamicKeys
  target._p = prependModifier
}

4) render 函數(shù)桨踪。

這個(gè)過程是直接執(zhí)行老翘,就會(huì)得到vnode,其細(xì)節(jié)包括了component的處理,會(huì)有單獨(dú)一節(jié)去介紹铺峭。

這里展示下vnode的格式墓怀。

declare interface VNodeData {
  key?: string | number;
  slot?: string;
  ref?: string;
  is?: string;
  pre?: boolean;
  tag?: string;
  staticClass?: string;
  class?: any;
  staticStyle?: { [key: string]: any };
  style?: string | Array<Object> | Object;
  normalizedStyle?: Object;
  props?: { [key: string]: any };
  attrs?: { [key: string]: string };
  domProps?: { [key: string]: any };
  hook?: { [key: string]: Function };
  on?: ?{ [key: string]: Function | Array<Function> };
  nativeOn?: { [key: string]: Function | Array<Function> };
  transition?: Object;
  show?: boolean; // marker for v-show
  inlineTemplate?: {
    render: Function;
    staticRenderFns: Array<Function>;
  };
  directives?: Array<VNodeDirective>;
  keepAlive?: boolean;
  scopedSlots?: { [key: string]: Function };
  model?: {
    value: any;
    callback: Function;
  };
};

總結(jié)

  • 1.編譯四大過程parse、optimize卫键、codegen傀履、render。
  • 2.用戶自定義的render其實(shí)是忽略了前面三個(gè)步驟莉炉,直接定制的方式钓账。
  • 3.vdom 最終是以vnode為節(jié)點(diǎn)的樹狀結(jié)構(gòu),在patch過程絮宁,會(huì)以新老vdom進(jìn)行對(duì)比梆暮,來決定哪些dom是需要更新或添加、刪除的绍昂。
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末惕蹄,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子治专,更是在濱河造成了極大的恐慌,老刑警劉巖遭顶,帶你破解...
    沈念sama閱讀 218,386評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件张峰,死亡現(xiàn)場(chǎng)離奇詭異,居然都是意外死亡棒旗,警方通過查閱死者的電腦和手機(jī)喘批,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來铣揉,“玉大人饶深,你說我怎么就攤上這事」涔埃” “怎么了敌厘?”我有些...
    開封第一講書人閱讀 164,704評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵,是天一觀的道長(zhǎng)朽合。 經(jīng)常有香客問我俱两,道長(zhǎng),這世上最難降的妖魔是什么曹步? 我笑而不...
    開封第一講書人閱讀 58,702評(píng)論 1 294
  • 正文 為了忘掉前任宪彩,我火速辦了婚禮,結(jié)果婚禮上讲婚,老公的妹妹穿的比我還像新娘尿孔。我一直安慰自己,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,716評(píng)論 6 392
  • 文/花漫 我一把揭開白布活合。 她就那樣靜靜地躺著雏婶,像睡著了一般。 火紅的嫁衣襯著肌膚如雪芜辕。 梳的紋絲不亂的頭發(fā)上尚骄,一...
    開封第一講書人閱讀 51,573評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音侵续,去河邊找鬼倔丈。 笑死,一個(gè)胖子當(dāng)著我的面吹牛状蜗,可吹牛的內(nèi)容都是我干的需五。 我是一名探鬼主播,決...
    沈念sama閱讀 40,314評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼轧坎,長(zhǎng)吁一口氣:“原來是場(chǎng)噩夢(mèng)啊……” “哼宏邮!你這毒婦竟也來了?” 一聲冷哼從身側(cè)響起缸血,我...
    開封第一講書人閱讀 39,230評(píng)論 0 276
  • 序言:老撾萬榮一對(duì)情侶失蹤蜜氨,失蹤者是張志新(化名)和其女友劉穎,沒想到半個(gè)月后捎泻,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體飒炎,經(jīng)...
    沈念sama閱讀 45,680評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,873評(píng)論 3 336
  • 正文 我和宋清朗相戀三年笆豁,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了郎汪。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,991評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡闯狱,死狀恐怖煞赢,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情哄孤,我是刑警寧澤照筑,帶...
    沈念sama閱讀 35,706評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站瘦陈,受9級(jí)特大地震影響朦肘,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜双饥,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,329評(píng)論 3 330
  • 文/蒙蒙 一媒抠、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧咏花,春花似錦趴生、人聲如沸阀趴。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,910評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽刘急。三九已至,卻和暖如春浸踩,著一層夾襖步出監(jiān)牢的瞬間叔汁,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 33,038評(píng)論 1 270
  • 我被黑心中介騙來泰國(guó)打工检碗, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留据块,地道東北人。 一個(gè)月前我還...
    沈念sama閱讀 48,158評(píng)論 3 370
  • 正文 我出身青樓折剃,卻偏偏與公主長(zhǎng)得像另假,于是被迫代替她去往敵國(guó)和親。 傳聞我的和親對(duì)象是個(gè)殘疾皇子怕犁,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,941評(píng)論 2 355

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