最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

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

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

簡(jiǎn)述過程

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

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

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

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

vue的渲染過程

在這里插入圖片描述

parse

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

在Vue中我把它理解為嵌套的、攜帶標(biāo)簽名、屬性和父子關(guān)系的 JS 對(duì)象,以樹來(lái)表現(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字段來(lái)形成一種層層嵌套的樹狀結(jié)構(gòu)。vue中定義了許多正則(判斷標(biāo)簽開始、結(jié)束、屬性、vue指令、文本),通過對(duì)html內(nèi)容進(jìn)行遞歸正則匹配,對(duì)滿足條件的字符串進(jìn)行截取。把字符串類型的html轉(zhuǎn)換位AST結(jié)構(gòu)

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

在這里插入圖片描述

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

texttext,在 parse 完之后會(huì)變成如下的結(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'"}]
  }

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

舉個(gè)例子

<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(’<’)的值,來(lái)確定我們是要截取標(biāo)簽還是文本.

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

若等于0

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

匹配時(shí)候的正則表達(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 = /^<!\[/
  • 同時(shí),需要注意的一點(diǎn)是,vue中還需要維護(hù)一個(gè)stack(可以理解為一個(gè)數(shù)組),用來(lái)標(biāo)記DOM的深度

關(guān)于stack

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

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

當(dāng)然,因?yàn)槲覀兊臉?biāo)簽中存在一種自閉和的標(biāo)簽(如input),這種類型的標(biāo)簽沒有子元素,所以不會(huì)push到stack中。

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

等于0或大于0

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

  • 此時(shí),它會(huì)判斷它的剩余部分是否符合標(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種

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

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

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

現(xiàn)在我們?cè)賮?lái)看最開始的例子

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

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

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

2.繼續(xù)判斷<的位置,大于0(因?yàn)橛锌崭?,判斷為文本,截取這個(gè)文本

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

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

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

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

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

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

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

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

parse過程總結(jié)

簡(jiǎn)單來(lái)說(shuō),template的parse過程,其實(shí)就是不斷的截取字符串并解析它們的過程。

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

optimize優(yōu)化

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

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

  • 1.如果是表達(dá)式AST節(jié)點(diǎn),直接返回 false
  • 2.如果是文本AST節(jié)點(diǎn),直接返回 true
  • 3.如果元素是元素節(jié)點(diǎn),階段有 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

簡(jiǎn)單來(lái)說(shuō),沒有使用vue獨(dú)有的語(yǔ)法的節(jié)點(diǎn)就可以稱為靜態(tài)節(jié)點(diǎn)

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

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

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方法是用來(lái)標(biāo)記靜態(tài)節(jié)點(diǎn)的方法,它會(huì)不斷的循環(huán)children,如果children還有children,則走相同的邏輯。這樣所有的節(jié)點(diǎn)都會(huì)被打上標(biāo)記。

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

generate生成render函數(shù)

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

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

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

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

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

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


在這里插入圖片描述

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

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

幾種內(nèi)部方法

  • _c:對(duì)應(yīng)的是 createElement 方法,顧名思義,它的含義是創(chuàng)建一個(gè)元素(Vnode)
  • _v:創(chuàng)建一個(gè)文本結(jié)點(diǎn)。
  • _s:把一個(gè)值轉(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": ""
              }
            })
        ]
    )
  }
}

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

// genElement方法用來(lái)拼接每一項(xiàng)_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
}

線來(lái)看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
}

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

那么children怎么拼出來(lái)呢?

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ù)就會(huì)形成虛擬DOM.

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

相關(guān)文章

  • vue Tab切換以及緩存頁(yè)面處理的幾種方式

    vue Tab切換以及緩存頁(yè)面處理的幾種方式

    這篇文章主要介紹了vue Tab切換以及緩存頁(yè)面處理的幾種方式,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-11-11
  • Vue路由跳轉(zhuǎn)的5種方式及擴(kuò)展

    Vue路由跳轉(zhuǎn)的5種方式及擴(kuò)展

    這篇文章主要給大家介紹了關(guān)于Vue路由跳轉(zhuǎn)的5種方式及擴(kuò)展,在Vue中路由是一種用于導(dǎo)航和管理頁(yè)面之間跳轉(zhuǎn)的機(jī)制,Vue Router是Vue官方提供的路由管理器,需要的朋友可以參考下
    2023-11-11
  • Element中Upload組件上傳功能實(shí)現(xiàn)(圖片和文件的默認(rèn)上傳及自定義上傳)

    Element中Upload組件上傳功能實(shí)現(xiàn)(圖片和文件的默認(rèn)上傳及自定義上傳)

    這篇文章主要介紹了Element中Upload組件上傳功能實(shí)現(xiàn)包括圖片和文件的默認(rèn)上傳及自定義上傳,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友參考下吧
    2024-01-01
  • 一文詳解Vue3響應(yīng)式原理

    一文詳解Vue3響應(yīng)式原理

    這篇文章主要介紹了一文詳解Vue3響應(yīng)式原理,文章通過與vue2.x?的響應(yīng)式做對(duì)比詳解展現(xiàn)出了Vue3響應(yīng)式原理詳情,感興趣的小伙伴可以參考一下
    2022-06-06
  • Vue模板內(nèi)引入的組件樣式覆蓋失效原因及解決

    Vue模板內(nèi)引入的組件樣式覆蓋失效原因及解決

    這篇文章主要介紹了Vue模板內(nèi)引入的組件樣式覆蓋失效原因及解決方案,具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-04-04
  • vue.js開發(fā)環(huán)境安裝教程

    vue.js開發(fā)環(huán)境安裝教程

    這篇文章主要為大家詳細(xì)介紹了vue.js開發(fā)環(huán)境的安裝教程,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2017-03-03
  • 使用Vue父子組件通信實(shí)現(xiàn)todolist的功能示例代碼

    使用Vue父子組件通信實(shí)現(xiàn)todolist的功能示例代碼

    這篇文章主要給大家介紹了關(guān)于如何使用Vue父子組件通信實(shí)現(xiàn)todolist的功能的相關(guān)資料,文中通過示例代碼介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者使用Vue具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-04-04
  • vue 獲取視頻時(shí)長(zhǎng)的實(shí)例代碼

    vue 獲取視頻時(shí)長(zhǎng)的實(shí)例代碼

    這篇文章主要介紹了vue 獲取視頻時(shí)長(zhǎng)的實(shí)例代碼,代碼簡(jiǎn)單易懂,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2019-08-08
  • Vue實(shí)現(xiàn)彈出框點(diǎn)擊空白頁(yè)彈框消失效果

    Vue實(shí)現(xiàn)彈出框點(diǎn)擊空白頁(yè)彈框消失效果

    這篇文章主要介紹了VUE實(shí)現(xiàn)彈出框點(diǎn)擊空白頁(yè)彈框消失,實(shí)現(xiàn)方法可以在Vue中實(shí)現(xiàn)彈出框然后通過點(diǎn)擊空白頁(yè)面來(lái)讓彈窗隱藏,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),需要的朋友可以參考下
    2023-12-12
  • vue3中reactive和ref函數(shù)及對(duì)比分析

    vue3中reactive和ref函數(shù)及對(duì)比分析

    這篇文章主要介紹了vue3中reactive和ref函數(shù)及對(duì)比,本文通過實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2024-01-01

最新評(píng)論

隆化县| 邯郸市| 顺平县| 招远市| 灵寿县| 松桃| 长春市| 木兰县| 房山区| 华池县| 台安县| 沧州市| 玛纳斯县| 富锦市| 浮梁县| 盐城市| 肥乡县| 酉阳| 白河县| 丰都县| 宜兴市| 伊吾县| 清涧县| 沁阳市| 常山县| 农安县| 玉田县| 青海省| 曲靖市| 安泽县| 麻城市| 淮北市| 文昌市| 山东省| 高台县| 那坡县| 许昌县| 永仁县| 通州市| 南通市| 云梦县|