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

一文帶你吃透Vue3編譯原理

 更新時(shí)間:2023年02月09日 10:26:03   作者:mick  
一直對編譯原理的東西都有一種恐懼感,感覺太難了,看不懂,打開vue3源碼看到編譯相關(guān)的代碼,直接嚇退。不要擔(dān)心,小編今天帶你一文吃透Vue3編譯原理

一直對編譯原理的東西都有一種恐懼感,感覺太難了,看不懂,打開vue3源碼看到編譯相關(guān)的代碼,直接嚇退。直到我學(xué)習(xí)了大崔哥的mini-vue,so ga ~

主要流程

現(xiàn)在我們就來一起分析一個(gè)簡易的vue3的編譯原理。一句話概括一下我們想要實(shí)現(xiàn)的功能,那就是將template模板生成我們想要的render函數(shù)即可。簡單的一句話卻蘊(yùn)含著大量的知識。

<div>hi, {{message}}</div> 

最后生成

import { toDisplayString as _toDisplayString, openBlock as _openBlock, createElementBlock as _createElementBlock } from "vue"

export function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createElementBlock("div", null, "hi, " + _toDisplayString(_ctx.message), 1 /* TEXT */))
}

首先template會通過詞法分析、語法分析解析成AST(抽象語法樹),然后利用transformAST進(jìn)行優(yōu)化,最后通過generate模塊生成我們想要的render函數(shù)。

vue3的源碼中主要分成了3個(gè)部分(以下是簡化后的源碼)

export function baseCompile(template){
  const ast = baseParse(template)
  transform(ast)
  return generate(ast)
}
  • 通過parsetemplate生成ast
  • 通過transform優(yōu)化ast
  • 通過generate生成render函數(shù)

由于這3個(gè)部分牽扯的東西比較多,我們這篇文章主要來講解一下parse的實(shí)現(xiàn)(友情提示:為了讓大家剛好的理解,本文的代碼全部都是精簡過得哦)

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

我們就拿一個(gè)簡單的例子入手

<div><p>hi</p>{{message}}</div>

看似一個(gè)簡單的例子,其實(shí)3種類型:element、text、插值。我們將這三種類型用枚舉定義一下。

const enum NodeTypes {
  ROOT,
  INTERPOLATION,
  SIMPLE_EXPRESSION,
  ELEMENT,
  TEXT
}

ROOT類型表示根節(jié)點(diǎn),SIMPLE_EXPRESSION類型表示插值的內(nèi)容。最后我們想要通過parse生成一個(gè)ast。

{
    type: NodeTypes.ROOT
    children: [
        {
          type: NodeTypes.ELEMENT,
          tag: "div",
          children: [
            {
              type: NodeTypes.ELEMENT,
              tag: "p",
              children: [
                {
                  type: NodeTypes.TEXT,
                  content: "hi"
                }
              ]
            },
            {
              type: NodeTypes.INTERPOLATION,
              content: {
                type: NodeTypes.SIMPLE_EXPRESSION,
                content: "message"
              }
            }
          ]
        }
    ]
}

基于源碼我們可以知道ast是由函數(shù)baseParse生成。那我們就從這個(gè)函數(shù)入手。

baseParse

export function baseParse(content: string) {
  const context = createParseContext(content)
  return createRoot(parserChildren(context, []))
}

function createParseContext(content: string) {
  return {
    source: content
  }
}

function createRoot(children) {
  return {
    children,
    type: NodeTypes.ROOT
  }
}

首先創(chuàng)建一個(gè)全局的上下文對象context,并且存儲了source。source就是我們傳入的模板內(nèi)容。接著創(chuàng)建根節(jié)點(diǎn),包含了typechildren。而children是由parseChildren創(chuàng)建。

parseChildren

function parseChildren(context, ancestors) {
  const nodes: any = []

  while (!isEnd(context, ancestors)) {
    const s = context.source
    let node
    if (s.startsWith("{{")) {
      node = parseInterpolation(context)
    } else if (s[0] === "<") {
      if (/[a-z]/i.test(s[1])) {
        node = parseElement(context, ancestors)
      }
    } else {
      node = parseText(context)
    }
    nodes.push(node)
  }
  return nodes
}

parseChildren是負(fù)責(zé)解析子節(jié)點(diǎn)并創(chuàng)建ast節(jié)點(diǎn)數(shù)組。parseChildren是自頂向下分析各個(gè)子節(jié)點(diǎn)的,對于模板內(nèi)容要從左到右依次解析。每當(dāng)碰到一個(gè)element節(jié)點(diǎn)都要遞歸的調(diào)用parseChildren去解析它的子節(jié)點(diǎn)。當(dāng)碰到{{則認(rèn)為需要處理的是插值節(jié)點(diǎn),當(dāng)碰到<則認(rèn)為需要處理的是element節(jié)點(diǎn),其余的則統(tǒng)一認(rèn)為處理的是text節(jié)點(diǎn)。每處理完一個(gè)節(jié)點(diǎn)都會生成nodepushnodes中,最后返回nodes當(dāng)做是父ast節(jié)點(diǎn)的children屬性。

當(dāng)然從左到右依次循環(huán)解析就一定要有一個(gè)退出循環(huán)的條件isEnd

function isEnd(context, ancestors) {
  const s = context.source

  if (s.startsWith("</")) {
    for (let i = 0; i < ancestors.length; i++) {
      const tag = ancestors[i]
      if (startsWithEndTagOpen(s, tag)) {
        return true
      }
    }
  }

  return !s
}
function startsWithEndTagOpen(source, tag) {
  return (
    source.startsWith("</") &&
    source.slice(2, 2 + tag.length).toLowerCase() === tag.toLowerCase()
  )
}

ancestors表示element標(biāo)簽的集合,大致的意思就是當(dāng)碰到了結(jié)束標(biāo)識符</,并且結(jié)束標(biāo)簽(source.slice(2, 2 + tag.length))和element標(biāo)簽的集合中的標(biāo)簽匹配則說明當(dāng)前的element節(jié)點(diǎn)處理完畢,則退出循環(huán)

下面我們就來看一下插值節(jié)點(diǎn)parseInterpolation、element節(jié)點(diǎn)parseElement和文本節(jié)點(diǎn)parseText分別是怎么處理的

parseInterpolation

function parseInterpolation(context) {
  const openDelimiter = "{{"
  const closeDelimiter = "}}"

  const closeIndex = context.source.indexOf(
    closeDelimiter,
    openDelimiter.length
  )

  advanceBy(context, openDelimiter.length)

  const rawContentLength = closeIndex - openDelimiter.length

  const rawContent = parseTextData(context, rawContentLength)

  const content = rawContent.trim()
  advanceBy(context, closeDelimiter.length)

  return {
    type: NodeTypes.INTERPOLATION,
    content: {
      type: NodeTypes.SIMPLE_EXPRESSION,
      content
    }
  }
}

function advanceBy(context: any, length: number) {
  context.source = context.source.slice(length)
}

function parseTextData(context: any, length) {
  const content = context.source.slice(0, length)

  advanceBy(context, content.length)
  return content
}

我們主要是為了獲取插值的內(nèi)容然后返回一個(gè)插值對象即可。closeIndex表示“}}”所在的位置。advanceBy函數(shù)的功能是推進(jìn)。比如"{{"是不需要處理的,那么就直接把它截取掉。rawContentLength代表“{{”和“}}”中間內(nèi)容的長度,通過parseTextData獲取“{{”和“}}”中間的內(nèi)容,并返回。然后把中間內(nèi)容的部分做推進(jìn)。由于我們寫代碼習(xí)慣可能會給內(nèi)容的前后做留白,所以需要用trim做處理。然后把最后的“}}”推進(jìn),返回一個(gè)插值類型的對象即可。

parseElement

function parseElement(context, ancestors) {
  const element: any = parseTag(context, TagType.Start)
  ancestors.push(element)
  element.children = parseChildren(context, ancestors)
  ancestors.pop()

  if (startsWithEndTagOpen(context.source, element.tag)) {
    parseTag(context, TagType.End)
  } else {
    throw new Error(`缺少結(jié)束標(biāo)簽: ${element.tag}`)
  }

  return element
}

function parseTag(context: any, type: TagType) {
  const match: any = /^<\/?([a-z]*)/i.exec(context.source)
  const tag = match[1]
  advanceBy(context, match[0].length)
  advanceBy(context, 1)

  if (type === TagType.End) return

  return {
    type: NodeTypes.ELEMENT,
    tag
  }
}

function startsWithEndTagOpen(source, tag) {
  return (
    source.startsWith("</") &&
    source.slice(2, 2 + tag.length).toLowerCase() === tag.toLowerCase()
  )
}

parseElement第二個(gè)參數(shù)ancestors是一個(gè)數(shù)組來收集標(biāo)簽的(作用在上面的isEnd已經(jīng)提到了)。通過parseTag獲取標(biāo)簽名,parseTag通過正則拿到標(biāo)簽名然后返回一個(gè)標(biāo)簽對象,處理過的內(nèi)容繼續(xù)做推進(jìn)。如果是結(jié)束標(biāo)簽則什么都不做。然后通過parseChildren遞歸的處理element的子節(jié)點(diǎn)。然后對結(jié)束標(biāo)簽進(jìn)行處理,startsWithEndTagOpen判斷是夠存在結(jié)束標(biāo)簽,如果不存在則報(bào)錯(cuò)。

parseText

function parseText(context: any): any {
  let endIndex = context.source.length
  let endToken = ["<", "{{"]

  for (let i = 0; i < endToken.length; i++) {
    const index = context.source.indexOf(endToken[i])
    if (index !== -1 && endIndex > index) {
      endIndex = index
    }
  }

  const content = parseTextData(context, endIndex)

  return {
    type: NodeTypes.TEXT,
    content
  }
}

endIndex表示內(nèi)容長度(此時(shí)內(nèi)容的長度是已經(jīng)推進(jìn)過的字符到最后一個(gè)字符的長度)。比如

<div>hi,{{message}}</div> 

能夠進(jìn)入到parseText函數(shù)中說明開始標(biāo)簽已經(jīng)處理過了,所以context.source應(yīng)該是

hi,{{message}}</div>

所以endIndex的長度應(yīng)該是上面代碼的長度。當(dāng)碰到”<“或者”{{“的時(shí)候,則我們需要改變endIndex的值,比如上面的代碼,我們想要拿到的文本內(nèi)容應(yīng)該是hi,,所以當(dāng)碰到”{{“時(shí),改變endIndex然后通過parseTextData拿到文本內(nèi)容,返回一個(gè)文本對象。

總結(jié)

parse的作用就是將template生成ast對象。則需要對template從左到右依次處理,處理過了則進(jìn)行推進(jìn),碰到element標(biāo)簽還需要遞歸處理,并把添加到element.children上,最終返回一個(gè)ast抽象語法樹。

以上就是一文帶你吃透Vue3編譯原理的詳細(xì)內(nèi)容,更多關(guān)于Vue3編譯原理的資料請關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • webpack 3 + Vue2 使用dotenv配置多環(huán)境的步驟

    webpack 3 + Vue2 使用dotenv配置多環(huán)境的步驟

    這篇文章主要介紹了webpack 3 + Vue2 使用dotenv配置多環(huán)境,env文件在配置文件都可以用, vue頁面用的時(shí)候需要在 webpack.base.conf.js 重新配置,需要的朋友可以參考下
    2023-11-11
  • vue 單元測試的推薦插件和使用示例

    vue 單元測試的推薦插件和使用示例

    單元測試是軟件開發(fā)非常基礎(chǔ)的一部分。單元測試會封閉執(zhí)行最小化單元的代碼,使得添加新功能和追蹤問題更容易。Vue 的單文件組件使得為組件撰寫隔離的單元測試這件事更加直接。它會讓你更有信心地開發(fā)新特性而不破壞現(xiàn)有的實(shí)現(xiàn),并幫助其他開發(fā)者理解你的組件的作用。
    2021-06-06
  • 在Vue3項(xiàng)目中使用Jest配置生成測試報(bào)告的示例詳解

    在Vue3項(xiàng)目中使用Jest配置生成測試報(bào)告的示例詳解

    在Vue 3項(xiàng)目中使用Jest進(jìn)行單元測試是一種常見的做法,它可以幫助我們驗(yàn)證代碼的正確性和穩(wěn)定性,本文將介紹如何在Vue 3項(xiàng)目中配置Jest,以生成測試報(bào)告,需要的朋友可以參考下
    2023-09-09
  • ?用Vue?Demi?構(gòu)建同時(shí)兼容Vue2與Vue3組件庫

    ?用Vue?Demi?構(gòu)建同時(shí)兼容Vue2與Vue3組件庫

    這篇文章主要介紹了?用Vue?Demi?構(gòu)建同時(shí)兼容Vue2與Vue3組件庫,我們通過考慮其功能、工作原理以及如何開始使用它來了解?Vue?Demi,下面我們一起進(jìn)入文章學(xué)起來吧
    2022-02-02
  • vue中本地靜態(tài)圖片路徑寫法

    vue中本地靜態(tài)圖片路徑寫法

    這篇文章給大家介紹了vue中本地靜態(tài)圖片路徑寫法及Vue.js中引用圖片路徑的方式,需要的朋友參考下吧
    2018-03-03
  • vue實(shí)現(xiàn)倒計(jì)時(shí)獲取驗(yàn)證碼效果

    vue實(shí)現(xiàn)倒計(jì)時(shí)獲取驗(yàn)證碼效果

    這篇文章主要為大家詳細(xì)介紹了vue實(shí)現(xiàn)倒計(jì)時(shí)獲取驗(yàn)證碼效果,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2019-09-09
  • vue實(shí)現(xiàn)文章評論和回復(fù)列表

    vue實(shí)現(xiàn)文章評論和回復(fù)列表

    這篇文章主要為大家詳細(xì)介紹了vue實(shí)現(xiàn)文章評論和回復(fù)列表,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2022-04-04
  • Vue實(shí)現(xiàn)登錄功能全套超詳細(xì)講解(含封裝axios)

    Vue實(shí)現(xiàn)登錄功能全套超詳細(xì)講解(含封裝axios)

    這篇文章主要給大家介紹了關(guān)于Vue實(shí)現(xiàn)登錄功能(含封裝axios)的相關(guān)資料,Vue是現(xiàn)在前端最流行的框架之一,作為前端開發(fā)人員應(yīng)該要熟練的掌握它,需要的朋友可以參考下
    2023-10-10
  • Vue源碼學(xué)習(xí)之響應(yīng)式是如何實(shí)現(xiàn)的

    Vue源碼學(xué)習(xí)之響應(yīng)式是如何實(shí)現(xiàn)的

    最近接觸了vue.js,一度非常好奇vue.js是如何監(jiān)測數(shù)據(jù)更新并且重新渲染頁面,這篇文章主要給大家介紹了關(guān)于Vue源碼學(xué)習(xí)之響應(yīng)式是如何實(shí)現(xiàn)的相關(guān)資料,需要的朋友可以參考下
    2021-10-10
  • vue+elemen實(shí)現(xiàn)el-tooltip在文本超出區(qū)域后浮現(xiàn)

    vue+elemen實(shí)現(xiàn)el-tooltip在文本超出區(qū)域后浮現(xiàn)

    el-tooltip組件本身就是懸浮提示功能,在對它進(jìn)行二次封裝時(shí),實(shí)現(xiàn)超出的文本浮現(xiàn),本文就來介紹一下vue+elemen實(shí)現(xiàn)el-tooltip在文本超出區(qū)域后浮現(xiàn),感興趣的可以了解一下
    2023-12-12

最新評論

伊宁市| 惠东县| 名山县| 扶绥县| 石狮市| 永登县| 利辛县| 玛多县| 葫芦岛市| 武邑县| 孝昌县| 喀什市| 牙克石市| 宜宾县| 尼木县| 武强县| 沛县| 紫金县| 年辖:市辖区| 商水县| 辰溪县| 察隅县| 雷波县| 通江县| 岳阳市| 衡东县| 萍乡市| 安溪县| 宝山区| 介休市| 基隆市| 南涧| 双辽市| 云霄县| 邢台市| 宽甸| 三穗县| 潜江市| 长海县| 乌鲁木齐市| 磴口县|