欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

vue中template模板編譯的過程全面剖析

 更新時間:2022年04月15日 12:15:10   作者:越努力,越幸運(yùn)?。?!  
這篇文章主要介紹了vue中template模板編譯的過程全面剖析,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教

簡述過程

vue template模板編譯的過程經(jīng)過parse()生成ast(抽象語法樹),optimize對靜態(tài)節(jié)點優(yōu)化,generate()生成render字符串

之后調(diào)用new Watcher()函數(shù),用來監(jiān)聽數(shù)據(jù)的變化,render 函數(shù)就是數(shù)據(jù)監(jiān)聽的回調(diào)所調(diào)用的,其結(jié)果便是重新生成 vnode。

當(dāng)這個 render 函數(shù)字符串在第一次 mount、或者綁定的數(shù)據(jù)更新的時候,都會被調(diào)用,生成 Vnode。

如果是數(shù)據(jù)的更新,那么 Vnode 會與數(shù)據(jù)改變之前的 Vnode 做 diff,對內(nèi)容做改動之后,就會更新到 我們真正的 DOM

vue的渲染過程

在這里插入圖片描述

parse

在了解 parse 的過程之前,我們需要了解 AST,AST 的全稱是 Abstract Syntax Tree,也就是所謂抽象語法樹,用來表示代碼的數(shù)據(jù)結(jié)構(gòu)。

在Vue中我把它理解為嵌套的、攜帶標(biāo)簽名、屬性和父子關(guān)系的 JS 對象,以樹來表現(xiàn) DOM 結(jié)構(gòu)。

vue中的ast類型有以下3種

ASTElement = {  // AST標(biāo)簽元素
  type: 1;
  tag: string;
  attrsList: Array<{ name: string; value: any }>;
  attrsMap: { [key: string]: any };
  parent: ASTElement | void;
  children: Array<ASTNode>
  
  ...
}
ASTExpression = { // AST表達(dá)式 {{ }}
  type: 2;
  expression: string;
  text: string;
  tokens: Array<string | Object>;
  static?: boolean;
};
ASTText = {  // AST文本
  type: 3;
  text: string;
  static?: boolean;
  isComment?: boolean;
};

通過children字段來形成一種層層嵌套的樹狀結(jié)構(gòu)。vue中定義了許多正則(判斷標(biāo)簽開始、結(jié)束、屬性、vue指令、文本),通過對html內(nèi)容進(jìn)行遞歸正則匹配,對滿足條件的字符串進(jìn)行截取。把字符串類型的html轉(zhuǎn)換位AST結(jié)構(gòu)

parse函數(shù)的作用就是把字符串型的template轉(zhuǎn)化為AST結(jié)構(gòu)

在這里插入圖片描述

如,假設(shè)我們有一個元素

texttext,在 parse 完之后會變成如下的結(jié)構(gòu)并返回:

  ele1 = {
    type: 1,
    tag: "div",
    attrsList: [{name: "id", value: "test"}],
    attrsMap: {id: "test"},
    parent: undefined,
    children: [{
        type: 3,
        text: 'texttext'
      }
    ],
    plain: true,
    attrs: [{name: "id", value: "'test'"}]
  }

那么它具體是怎么解析、截取的呢?

舉個例子

<div>
    <p>我是{{name}}</p>
</div>

他的截取過程,主要如下

// 初始
<div>
    <p>我是{{name}}</p>
</div>
// 第一次截取剩余(包括空格)
    <p>我是{{name}}</p>
</div>
// 第二次截取
<p>我是{{name}}</p>
</div>
// 第三次截取
我是{{name}}</p>
</div>
// 第四次截取
</p>
</div>
//
            
</div>
//
</div>

那么,他的截取規(guī)則是什么呢?

vue中截取規(guī)則主要是通過判斷模板中html.indexof(’<’)的值,來確定我們是要截取標(biāo)簽還是文本.

  • 等于 0:這就代表這是注釋、條件注釋、doctype、開始標(biāo)簽、結(jié)束標(biāo)簽中的某一種
  • 大于等于 0:這就說明是文本、表達(dá)式
  • 小于 0:表示 html 標(biāo)簽解析完了,可能會剩下一些文本、表達(dá)式

若等于0

若等于0,則進(jìn)行正則匹配看是否為開始標(biāo)簽、結(jié)束標(biāo)簽、注釋、條件注釋、doctype中的一種。若是開始標(biāo)簽,則截取對應(yīng)的開始標(biāo)簽,并定義ast的基本結(jié)構(gòu),并且解析標(biāo)簽上帶的屬性(attrs, tagName)、指令等等。
當(dāng)然,這里的attrs也是通過正則匹配出來的,具體做法就是通過匹配標(biāo)簽上對應(yīng)的屬性,然后把他push到attrs里。

匹配時候的正則表達(dá)式如下。

const attribute = /^\s*([^\s"'<>\/=]+)(?:\s*(=)\s*(?:"([^"]*)"+|'([^']*)'+|([^\s"'=<>`]+)))?/
const ncname = '[a-zA-Z_][\\w\\-\\.]*'
const qnameCapture = `((?:${ncname}\\:)?${ncname})`
const startTagOpen = new RegExp(`^<${qnameCapture}`)
const startTagClose = /^\s*(\/?)>/
const endTag = new RegExp(`^<\\/${qnameCapture}[^>]*>`)
const doctype = /^<!DOCTYPE [^>]+>/i
const comment = /^<!\--/
const conditionalComment = /^<!\[/
  • 同時,需要注意的一點是,vue中還需要維護(hù)一個stack(可以理解為一個數(shù)組),用來標(biāo)記DOM的深度

關(guān)于stack

stack里的最后一項,永遠(yuǎn)是當(dāng)前正在解析的元素的parentNode。

通過stack解析器會把當(dāng)前解析的元素和stack里的最后一個元素建立父子關(guān)系。即把當(dāng)前節(jié)點push到stack的最后一個節(jié)點的children里,同時將它自身的parent設(shè)為stack的最后一個節(jié)點。

當(dāng)然,因為我們的標(biāo)簽中存在一種自閉和的標(biāo)簽(如input),這種類型的標(biāo)簽沒有子元素,所以不會push到stack中。

  • 若是結(jié)束標(biāo)簽,則需要通過這個結(jié)束標(biāo)簽的tagName從后到前匹配stack中每一項的tagName,將匹配到的那一項之后的所有項全部刪除,表示這一段已經(jīng)解析完成。
  • 若不是以上5種中的一種,則表示他是文本

等于0或大于0

若等于0且不滿足以上五種條件或大于0,則表示它是文本或表達(dá)式。

  • 此時,它會判斷它的剩余部分是否符合標(biāo)簽的格式,
  • 如果不符合,則繼續(xù)再剩余部分判斷’<'的位置,并繼續(xù)1的判斷,直到剩余部分有符合標(biāo)簽的格式出現(xiàn)。
let textEnd = html.indexOf('<')
let text, rest, next
if (textEnd >= 0) {
  rest = html.slice(textEnd)
  // 剩余部分的 HTML 不符合標(biāo)簽的格式那肯定就是文本
  // 并且還是以 < 開頭的文本
  while (
    !endTag.test(rest) &&
    !startTagOpen.test(rest) &&
    !comment.test(rest) &&
    !conditionalComment.test(rest)
  ) {
    // < in plain text, be forgiving and treat it as text
    next = rest.indexOf('<', 1)
    if (next < 0) break
    textEnd += next
    rest = html.slice(textEnd)
  }
  text = html.substring(0, textEnd)
  html = html.substring(0, textEnd)
}

關(guān)于文本的截取

文本一般分為2種

  • 實打?qū)?lt;/div>
  • 我是{{name}}</div>

如果文本中含有表達(dá)式,則需要對文本中的變量進(jìn)行解析

const expression = parseText(text, delimiters) // 對變量解析 {{name}} => _s(name)
children.push({
  type: 2,
  expression,
  text
})
// 上例中解析過后形成如下的結(jié)構(gòu)
{
  expression: "_s(name)",
  text: "我是{{name}}",
  type: 2
}

現(xiàn)在我們再來看最開始的例子

<div>
? ? <p>我是{{name}}</p>
</div>

1.首先第一次判斷<的位置,等于0,且可以匹配上開始標(biāo)簽,則截取這個標(biāo)簽。

// 第一次截取后剩余
? ? <p>我是{{name}}</p>
</div>

2.繼續(xù)判斷<的位置,大于0(因為有空格),判斷為文本,截取這個文本

// 第二次截取后剩余
<p>我是{{name}}</p>
</div>

3.繼續(xù)判斷<位置,等于0,且為開始標(biāo)簽,截取這一部分,并且維護(hù)stack,把當(dāng)前的解析的元素的parnet置為stack中的最后一項,并且在stack的最后一項的children里push當(dāng)前解析的元素

// 這里有個判斷,因為非自閉和標(biāo)簽才會有children,所以非自閉標(biāo)簽才往stack里push
if (!unary) {
? currentParent = element
? stack.push(element)
}
// 設(shè)立父子關(guān)系
currentParent.children.push(element)
element.parent = currentParent
// 此時stack
[divAst,pAst]
// ?第三次截取后剩余
我是{{name}}</p>
</div>

4.繼續(xù)判斷<的位置,大于0,判斷剩余部分是否屬于標(biāo)簽的一種,這里剩余部分可以匹配結(jié)束標(biāo)簽,則表明為文本

// 第四次截取后剩余
</p>
</div>

5.繼續(xù)判斷<的位置,等于0,且匹配為結(jié)束標(biāo)簽,此時會再stack里尋找滿足tagName和當(dāng)前標(biāo)簽名相同的最后一項,把它之后項的全部刪除。

// 此時stack
[divAst]
// 第五次截取剩余
</div>

6.繼續(xù)通過以上方式截取,直到全部截取完畢。

parse過程總結(jié)

簡單來說,template的parse過程,其實就是不斷的截取字符串并解析它們的過程。

在此過程中,如果截取到非閉合標(biāo)簽就push到stack中,如果截取道結(jié)束標(biāo)簽就把這個標(biāo)簽pop出來。

optimize優(yōu)化

optimize的作用主要是對生成的AST進(jìn)行靜態(tài)內(nèi)容的優(yōu)化,標(biāo)記靜態(tài)節(jié)點。所謂靜態(tài)內(nèi)容,指的是和數(shù)據(jù)沒有關(guān)系,不需要每次都更新的內(nèi)容。

標(biāo)記靜態(tài)節(jié)點的作用的作用是為了之后dom diff時,是否需要patch,diff算法會直接跳過靜態(tài)節(jié)點,從而減少了比較的過程,優(yōu)化了patch的性能。

  • 1.如果是表達(dá)式AST節(jié)點,直接返回 false
  • 2.如果是文本AST節(jié)點,直接返回 true
  • 3.如果元素是元素節(jié)點,階段有 v-pre 指令 ||

1.沒有任何指令、數(shù)據(jù)綁定、事件綁定等 &&

2.沒有 v-if 和 v-for &&

3.不是 slot 和 component &&

4.是 HTML 保留標(biāo)簽 &&

5.不是 template 標(biāo)簽的直接子元素并且沒有包含在 for 循環(huán)中則返回 true

簡單來說,沒有使用vue獨有的語法的節(jié)點就可以稱為靜態(tài)節(jié)點

判斷一個父級元素是靜態(tài)節(jié)點,則需要判斷它的所有子節(jié)點都是靜態(tài)節(jié)點,否則就不是靜態(tài)節(jié)點

標(biāo)記靜態(tài)節(jié)點的過程是一個不斷遞歸的過程

for (let i = 0, l = node.children.length; i < l; i++) {
  const child = node.children[i]
  markStatic(child)
  if (!child.static) {
    node.static = false
  }
}

markStatic方法是用來標(biāo)記靜態(tài)節(jié)點的方法,它會不斷的循環(huán)children,如果children還有children,則走相同的邏輯。這樣所有的節(jié)點都會被打上標(biāo)記。

在循環(huán)中會判斷,子節(jié)點是否為靜態(tài)節(jié)點,如果不是則其父節(jié)點不是靜態(tài)節(jié)點。

generate生成render函數(shù)

generate是將AST轉(zhuǎn)化成render funtion字符串的過程,他遞歸了AST,得到結(jié)果是render的字符串。

render函數(shù)的就是返回一個_c(‘tagName’,data,children)的方法

1.第一個參數(shù)是標(biāo)簽名

2.第二個參數(shù)是他的一些數(shù)據(jù),包括屬性/指令/方法/表達(dá)式等等。

3.第三個參數(shù)是當(dāng)前標(biāo)簽的子標(biāo)簽,同樣的,每一個子標(biāo)簽的格式也是_c(‘tagName’,data,children)。

generate就是通過不斷遞歸形成了這么一種樹形結(jié)構(gòu)。


在這里插入圖片描述

  • genElement:用來生成基本的render結(jié)構(gòu)或者叫createElement結(jié)構(gòu)
  • genData: 處理ast結(jié)構(gòu)上的一些屬性,用來生成data
  • genChildren:處理ast的children,并在內(nèi)部調(diào)用genElement,形成子元素的_c()方法

render字符串內(nèi)部有幾種方法

幾種內(nèi)部方法

  • _c:對應(yīng)的是 createElement 方法,顧名思義,它的含義是創(chuàng)建一個元素(Vnode)
  • _v:創(chuàng)建一個文本結(jié)點。
  • _s:把一個值轉(zhuǎn)換為字符串。(eg: {{data}})
  • _m:渲染靜態(tài)內(nèi)容
<template>
  <div id="app">
    {{val}}
    <img src="http://xx.jpg">
  </div>
</template>
{
  render: with(this) {
    return _c('div', {
      attrs: {
        "id": "app"
      }
    }, [_v("\n" + _s(val) + "\n"),
        _c('img', {
              attrs: {
                "src": ""
              }
            })
        ]
    )
  }
}

那么問題來了,_c(‘tagName’,data,children)如何拼接的,data是如何拼接的,children又是如何拼接的?

// genElement方法用來拼接每一項_c('tagName',data,children)
function genElement (el: ASTElement, state: CodegenState) {
  const data = el.plain ? undefined : genData(el, state)
  const children = el.inlineTemplate ? null : genChildren(el, state, true)
    
  let code = `_c('${el.tag}'${
    data ? `,${data}` : '' // data
  }${
    children ? `,${children}` : '' // children
  })`
  
  return code
}

線來看data的拼接邏輯

//
function genData (el: ASTElement, state: CodegenState): string {
  let data = '{'
  // key
  if (el.key) {
    data += `key:${el.key},`
  }
  // ref
  if (el.ref) {
    data += `ref:${el.ref},`
  }
  if (el.refInFor) {
    data += `refInFor:true,`
  }
  // ... 類似的還有很多種情況
  data = data.replace(/,$/, '') + '}'
  return data
}

從上面可以看出來,data的拼接過程就是不斷的判讀ast上一些屬性是否存在,然后拼在data上,最后把這個data返回。

那么children怎么拼出來呢?

function genChildren (
  el: ASTElement,
  state: CodegenState
): string | void {
  const children = el.children
  if (children.length) {
    return `[${children.map(c => genNode(c, state)).join(',')}]`
  }
}
function genNode (node: ASTNode, state: CodegenState): string {
  if (node.type === 1) {
    return genElement(node, state)
  } if (node.type === 3 && node.isComment) {
    return genComment(node)
  } else {
    return genText(node)
  }
}

最后執(zhí)行render函數(shù)就會形成虛擬DOM.

以上為個人經(jīng)驗,希望能給大家一個參考,也希望大家多多支持腳本之家。

相關(guān)文章

  • vue實現(xiàn)簡單滑塊驗證

    vue實現(xiàn)簡單滑塊驗證

    這篇文章主要為大家詳細(xì)介紹了vue實現(xiàn)簡單滑塊驗證,文中示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2021-08-08
  • vue限制輸入數(shù)字或者保留兩位小數(shù)實現(xiàn)

    vue限制輸入數(shù)字或者保留兩位小數(shù)實現(xiàn)

    這篇文章主要為大家介紹了vue限制輸入數(shù)字或者保留兩位小數(shù)實現(xiàn)示例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2023-07-07
  • vue?中使用?this?更新數(shù)據(jù)的一次問題記錄

    vue?中使用?this?更新數(shù)據(jù)的一次問題記錄

    這篇文章主要介紹了vue?中使用?this?更新數(shù)據(jù)的一次大坑?,我在 vue 實例中聲明了一個數(shù)組屬性如?books: [],在異步請求的回調(diào)函數(shù)中使用?this.books = res.data.data;?進(jìn)行數(shù)據(jù)更新,感興趣的朋友跟隨小編一起看看吧
    2022-11-11
  • vue-rx的初步使用教程

    vue-rx的初步使用教程

    這篇文章主要介紹了vue-rx的初步使用教程,小編覺得挺不錯的,現(xiàn)在分享給大家,也給大家做個參考。一起跟隨小編過來看看吧
    2018-09-09
  • Vue中transition單個節(jié)點過渡與transition-group列表過渡全過程

    Vue中transition單個節(jié)點過渡與transition-group列表過渡全過程

    這篇文章主要介紹了Vue中transition單個節(jié)點過渡與transition-group列表過渡全過程,具有很好的參考價值,希望對大家有所幫助。如有錯誤或未考慮完全的地方,望不吝賜教
    2022-04-04
  • vue中的mescroll搜索運(yùn)用及各種填坑處理

    vue中的mescroll搜索運(yùn)用及各種填坑處理

    這篇文章主要介紹了vue中的mescroll搜索運(yùn)用及各種填坑處理,文中通過代碼給大家講解了mescroll vue使用,感興趣的朋友跟隨小編一起看看吧
    2019-10-10
  • 基于Vue 2.0的模塊化前端 UI 組件庫小結(jié)

    基于Vue 2.0的模塊化前端 UI 組件庫小結(jié)

    AT-UI 是一款基于 Vue.js 2.0 的輕量級、模塊化前端 UI 組件庫,主要用于快速開發(fā) PC 網(wǎng)站產(chǎn)品。下面通過本文給大家介紹Vue 2.0的模塊化前端 UI 組件庫,需要的朋友參考下吧
    2017-12-12
  • VUE識別訪問設(shè)備是pc端還是移動端的實現(xiàn)步驟

    VUE識別訪問設(shè)備是pc端還是移動端的實現(xiàn)步驟

    經(jīng)常在項目中會有支持pc與手機(jī)端需求,并且pc與手機(jī)端是兩個不一樣的頁面,這時就要求判斷設(shè)置,下面這篇文章主要給大家介紹了關(guān)于VUE識別訪問設(shè)備是pc端還是移動端的相關(guān)資料,需要的朋友可以參考下
    2023-05-05
  • vue中keep-alive、activated的探討和使用詳解

    vue中keep-alive、activated的探討和使用詳解

    這篇文章主要介紹了vue中keep-alive、activated的探討和使用詳解,具有很好的參考價值,希望對大家有所幫助。一起跟隨小編過來看看吧
    2020-07-07
  • VUE3之Non-Props屬性的具體使用

    VUE3之Non-Props屬性的具體使用

    本文主要介紹了VUE3之Non-Props屬性的具體使用,文中通過示例代碼介紹的非常詳細(xì),具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2022-01-01

最新評論