亚洲激情专区-91九色丨porny丨老师-久久久久久久女国产乱让韩-国产精品午夜小视频观看

溫馨提示×

溫馨提示×

您好,登錄后才能下訂單哦!

密碼登錄×
登錄注冊×
其他方式登錄
點擊 登錄注冊 即表示同意《億速云用戶服務條款》

Vue3.0靜態節點提升是什么

發布時間:2022-02-24 16:03:11 來源:億速云 閱讀:186 作者:iii 欄目:開發技術

今天小編給大家分享一下Vue3.0靜態節點提升是什么的相關知識點,內容詳細,邏輯清晰,相信大部分人都還太了解這方面的知識,所以分享這篇文章給大家參考一下,希望大家閱讀完這篇文章后有所收獲,下面我們一起來了解一下吧。

前言

「靜態節點提升」是「Vue3」針對 VNode 更新過程性能問題而提出的一個優化點。眾所周知,在大型應用場景下,「Vue2.x」 的 patchVNode 過程,即 diff 過程是非常緩慢的,這是一個十分令人頭疼的問題。

雖然,對于面試常問的 diff 過程在一定程度上是減少了對 DOM 的直接操作。但是,「這個減少是有一定成本的」。因為,如果是復雜應用,那么就會存在父子關系非常復雜的 VNode,而這也就是 diff 的痛點,它會不斷地遞歸調用 patchVNode,不斷堆疊而成的幾毫秒,最終就會造成 VNode 更新緩慢。

也因此,這也是為什么我們所看到的大型應用諸如阿里云之類的采用的是基于「React」的技術棧的原因之一。所以,「Vue3」也是痛改前非,重寫了整個 Compiler 過程,提出了靜態提升、靶向更新等優化點,來提高 patchVNode 過程。

那么,回到今天的正題,我們從源碼角度看看在整個編譯過程「Vue3」靜態節點提升究竟是「何許人也」

什么是 patchFlag

由于,在 compile 過程的 transfrom 階段會提及 AST Element 上的 patchFlag 屬性。所以,在正式認識 complie 之前,我們先搞清楚一個概念,什么是 patchFlag

patchFlagcomplier 時的 transform 階段解析 AST Element 打上的「優化標識」。并且,顧名思義 patchFlagpatch 一詞表示著它會為 runtime時的 patchVNode 提供依據,從而實現靶向更新 VNode 的效果。因此,這樣一來一往,也就是耳熟能詳的 Vue3 巧妙結合 runtimecompiler 實現靶向更新和靜態提升。

而在源碼中 patchFlag 被定義為一個「數字枚舉類型」

并且,值得一提的是整體上 patchFlag 的分為兩大類:

  • patchFlag 的值「大于」 0 時,代表所對應的元素在 patchVNode 時或 render 時是可以被優化生成或更新的。

  • patchFlag 的值「小于」 0 時,代表所對應的元素在 patchVNode 時,是需要被 full diff,即進行遞歸遍歷 VNode tree 的比較更新過程。

其實,還有兩類特殊的 flagshapeFlagslogFlag,這里我就不對此展開,有興趣的同學可以自行去了解。

Compile 編譯過程

對比 Vue2.x 編譯過程

了解過「Vue2.x」源碼的同學,我想應該都知道在「Vue2.x」中的 Compile 過程會是這樣:

  • parse 編譯模板生成原始 AST。

  • optimize 優化原始 AST,標記 AST Element 為靜態根節點或靜態節點。

  • generate 根據優化后的 AST,生成可執行代碼,例如 _c_l 之類的。

而在「Vue3」中,整體的 Compile 過程仍然是三個階段,但是不同于「Vue2.x」的是,第二個階段換成了正常編譯器都會存在的階段 transform

在源碼中,它對應的偽代碼會是這樣:

export function baseCompile(
  template: string | RootNode,
  options: CompilerOptions = {}
): CodegenResult {
  ...
  const ast = isString(template) ? baseParse(template, options) : template
  ...
  transform(
    ast,
    extend({}, options, {....})
  )


  return generate(
    ast,
    extend({}, options, {
      prefixIdentifiers
    })
  )
}

那么,我想這個時候大家可能會問為什么會是 transform?它的職責是什么?

通過簡單的對比「Vue2.x」編譯過程的第二階段的 optimize,很明顯,transform并不是「無米之炊」,它仍然有著「優化」原始 AST 的作用,而具體職責會表現在:

  • 對所有 AST Element 新增 codegen 屬性來幫助 generate 更準確地生成「最優」的可執行代碼。

  • 對靜態 AST Element 新增 hoists 屬性來實現靜態節點的「單獨創建」

  • ...

此外,transform 還標識了諸如 isBlockhelpers 等屬性,來生成最優的可執行代碼,這里我們就不細談,有興趣的同學可以自行了解。

baseParse 構建原始抽象語法樹(AST)

baseParse 顧名思義起著「解析」的作用,它的表現和「Vue2.x」的 parse 相同,都是解析模板 tempalte 生成「原始 AST」

假設,此時我們有一個這樣的模板 template

<div><div>hi vue3</div><div>{{msg}}</div></div>

那么,它在經過 baseParse 處理后生成的 AST 看起來會是這樣:

{
  cached: 0,
  children: [{…}],
  codegenNode: undefined,
  components: [],
  directives: [],
  helpers: [],
  hoists: [],
  imports: [],
  loc: {start: {…}, end: {…}, source: "<div><div>hi vue3</div><div>{{msg}}</div></div>"},
  temps: 0,
  type: 0
}

如果,了解過「Vue2.x」編譯過程的同學應該對于上面這顆 AST 的大部分屬性不會陌生。AST 的本質是通過用對象來描述「DSL」(特殊領域語言),例如:

  • children 中存放的就是最外層 div 的后代。

  • loc 則用來描述這個 AST Element 在整個字符串(template)中的位置信息。

  • type 則是用于描述這個元素的類型(例如 5 為插值、2 為文本)等等。

并且,可以看到的是不同于「Vue2.x」的 AST,這里我們多了諸如 helperscodegenNodehoists 等屬性。而,這些屬性會在 transform 階段進行相應地賦值,進而幫助 generate 階段生成「更優的」可執行代碼。

transfrom 優化原始抽象語法樹(AST)

對于 transform 階段,如果了解過「編譯器」的工作流程的同學應該知道,一個完整的編譯器的工作流程會是這樣:

  • 首先,parse 解析原始代碼字符串,生成抽象語法樹 AST。

  • 其次,transform 轉化抽象語法樹,讓它變成更貼近目標「DSL」的結構。

  • 最后,codegen 根據轉化后的抽象語法樹生成目標「DSL」的可執行代碼。

而在「Vue3」采用 Monorepo 的方式管理項目后,compile 對應的能力就是一個編譯器。所以,transform 也是整個編譯過程的重中之重。換句話說,如果沒有 transformAST 做諸多層面的轉化,「Vue」仍然會掛在 diff 這個「飽受詬病」的過程。

相比之下,「Vue2.x」的編譯階段沒有完整的 transform,只是 optimize 優化了一下 AST,可以想象在「Vue」設計之初尤大也沒想到它以后會「這么地流行」

那么,我們來看看 transform 函數源碼中的定義:

function transform(root: RootNode, options: TransformOptions) {
  const context = createTransformContext(root, options)
  traverseNode(root, context)
  if (options.hoistStatic) {
    hoistStatic(root, context)
  }
  if (!options.ssr) {
    createRootCodegen(root, context)
  }
  // finalize meta information
  root.helpers = [...context.helpers]
  root.components = [...context.components]
  root.directives = [...context.directives]
  root.imports = [...context.imports]
  root.hoists = context.hoists
  root.temps = context.temps
  root.cached = context.cached
}

可以說,transform 函數做了什么,在它的定義中是「一覽無余」。這里我們提一下它對靜態提升其決定性作用的兩件事:

  • 將原始 AST 中的靜態節點對應的 AST Element 賦值給根 AST 的 hoists 屬性。

  • 獲取原始 AST 需要的 helpers 對應的鍵名,用于 generate 階段的生成可執行代碼的獲取對應函數,例如 createTextVNodecreateStaticVNoderenderList 等等。

并且,在 traverseNode 函數中會對 AST Element 應用具體的 transform 函數,大致可以分為兩類:

  • 靜態節點 transform 應用,即節點不含有插值、指令、props、動態樣式的綁定等。

  • 動態節點 transform 應用,即節點含有插值、指令、props、動態樣式的綁定等。

那么,我們就來看看對于靜態節點 transform 是如何應用的?

靜態節點 transform 應用

這里,對于上面我們說到的這個栗子,靜態節點就是這個部分:

<div>hi vue3</div>

而它在沒有進行 transform 應用之前,它對應的 AST 會是這樣:

{
  children: [{
    content: "hi vue3"
    loc: {start: {…}, end: {…}, source: "hi vue3"}
    type: 2
  }],
  codegenNode: undefined,
  isSelfClosing: false,
  loc: {start: {…}, end: {…}, source: "<div>hi vue3</div>"},
  ns: 0,
  props: [],
  tag: "div",
  tagType: 0,
  type: 1
}

可以看出,此時它的 codegenNodeundefined。而在源碼中各類 transform函數被定義為 plugin,它會根據 baseParse 生成的 AST 「遞歸應用」對應的 plugin。然后,創建對應 AST Element 的 codegen 對象。

所以,此時我們會命中 transformElementtransformText 兩個 plugin的邏輯。

「transformText」

transformText 顧名思義,它和「文本」相關。很顯然,此時的 AST Element 所屬的類型就是 Text。那么,我們先來看一下 transformText 函數對應的偽代碼:

export const transformText: NodeTransform = (node, context) => {
  if (
    node.type === NodeTypes.ROOT ||
    node.type === NodeTypes.ELEMENT ||
    node.type === NodeTypes.FOR ||
    node.type === NodeTypes.IF_BRANCH
  ) {
    return () => {
      const children = node.children
      let currentContainer: CompoundExpressionNode | undefined = undefined
      let hasText = false


      for (let i = 0; i < children.length; i++) { // {1}
        const child = children[i]
        if (isText(child)) {
          hasText = true
          ...
        }
      }
      if (
        !hasText ||
        (children.length === 1 &&
          (node.type === NodeTypes.ROOT ||
            (node.type === NodeTypes.ELEMENT &&
              node.tagType === ElementTypes.ELEMENT)))
      ) { // {2}
        return
      }
      ...
    }
  }
}

可以看到,這里我們會命中 「{2}」 的邏輯,即如果對于「節點含有單一文本」 transformText 并不需要進行額外的處理,即該節點仍然在這里仍然保留和「Vue2.x」版本一樣的處理方式。

transfromText 真正發揮作用的場景是當模板中存在這樣的情況:

<div>ab {a} {b}</div>

此時 transformText 需要將兩者放在一個「單獨的」 AST Element 下,在源碼中它被稱為「Compound Expression」,即「組合的表達式」。這種組合的目的是為了 patchVNode 這類 VNode 時做到「更好地定位和實現 DOM 的更新」。反之,如果是一個文本節點和插值動態節點的話,在 patchVNode 階段同樣的操作需要進行兩次,例如對于同一個 DOM 節點操作兩次。

「transformElement」

transformElement 是一個所有 AST Element 都會被執行的一個 plugin,它的核心是為 AST Element 生成最基礎的 codegen 屬性。例如標識出對應 patchFlag,從而為生成 VNode 提供依據,例如 dynamicChildren

而對于靜態節點,同樣是起到一個初始化它的 codegenNode 屬性的作用。并且,從上面介紹的 patchFlag 的類型,我們可以知道它的 patchFlag 為默認值 0。所以,它的 codegenNode 屬性值看起來會是這樣:

{
  children: {
    content: "hi vue3"
    loc: {start: {…}, end: {…}, source: "hi vue3"}
    type: 2
  },
  directives: undefined,
  disableTracking: false,
  dynamicProps: undefined,
  isBlock: false,
  loc: {start: {…}, end: {…}, source: "<div>hi vue3</div>"},
  patchFlag: undefined,
  props: undefined,
  tag: ""div"",
  type: 13
}

generate 生成可執行代碼

generatecompile 階段的最后一步,它的作用是將 transform 轉換后的 AST 生成對應的「可執行代碼」,從而在之后 Runtime 的 Render 階段時,就可以通過可執行代碼生成對應的 VNode Tree,然后最終映射為真實的 DOM Tree 在頁面上。

同樣地,這一階段在「Vue2.x」也是由 generate 函數完成,它會生成是諸如 _l_c 之類的函數,這本質上是對 _createElement 函數的封裝。而相比較「Vue2.x」版本的 generate,「Vue3」改變了很多,其 generate 函數對應的偽代碼會是這樣:

export function generate(
  ast: RootNode,
  options: CodegenOptions & {
    onContextCreated?: (context: CodegenContext) => void
  } = {}
): CodegenResult {
  const context = createCodegenContext(ast, options)
  if (options.onContextCreated) options.onContextCreated(context)
  const {
    mode,
    push,
    prefixIdentifiers,
    indent,
    deindent,
    newline,
    scopeId,
    ssr
  } = context
  ...
  genFunctionPreamble(ast, context)
  ...


  if (!ssr) {
    ...
    push(`function render(_ctx, _cache${optimizeSources}) {`)
  }
  ....


  return {
    ast,
    code: context.code,
    // SourceMapGenerator does have toJSON() method but it's not in the types
    map: context.map ? (context.map as any).toJSON() : undefined
  }
}

所以,接下來,我們就來「一睹」帶有靜態節點對應的 AST 生成的可執行代碼的過程會是怎樣。

CodegenContext 代碼生成上下文

從上面 generate 函數的偽代碼可以看到,在函數的開始調用了 createCodegenContext 為當前 AST 生成了一個 context。在整個 generate 函數的執行過程「都依托」于一個 CodegenContext 「生成代碼上下文」(對象)的能力,它是通過 createCodegenContext 函數生成。而 CodegenContext 的接口定義會是這樣:

interface CodegenContext
  extends Omit {
  source: string
  code: string
  line: number
  column: number
  offset: number
  indentLevel: number
  pure: boolean
  map?: SourceMapGenerator
  helper(key: symbol): string
  push(code: string, node?: CodegenNode): void
  indent(): void
  deindent(withoutNewLine?: boolean): void
  newline(): void
}

可以看到 CodegenContext 對象中有諸如 pushindentnewline 之類的方法。而它們的作用是在根據 AST 來生成代碼時用來「實現換行」「添加代碼」「縮進」等功能。從而,最終形成一個個可執行代碼,即我們所認知的 render 函數,并且,它會作為 CodegenContextcode 屬性的值返回。

下面,我們就來看下靜態節點的可執行代碼生成的核心,它被稱為 Preamble 前導。

genFunctionPreamble 生成前準備

整個靜態提升的可執行代碼生成就是在 genFunctionPreamble 函數部分完成的。并且,大家仔細「斟酌」一番靜態提升的字眼,靜態二字我們可以不看,但是「提升二字」,直抒本意地表達出它(靜態節點)被「提高了」

為什么說是提高了?因為在源碼中的體現,確實是被提高了。在前面的 generate 函數,我們可以看到 genFunctionPreamble 是先于 render 函數加入context.code 中,所以,在 Runtime 時的 Render 階段,它會先于 render 函數執行。

geneFunctionPreamble 函數(偽代碼):

function genFunctionPreamble(ast: RootNode, context: CodegenContext) {
  const {
    ssr,
    prefixIdentifiers,
    push,
    newline,
    runtimeModuleName,
    runtimeGlobalName
  } = context
  ...
  const aliasHelper = (s: symbol) => `${helperNameMap[s]}: _${helperNameMap[s]}`
  if (ast.helpers.length > 0) {
    ...
    if (ast.hoists.length) {
      const staticHelpers = [
        CREATE_VNODE,
        CREATE_COMMENT,
        CREATE_TEXT,
        CREATE_STATIC
       ]
        .filter(helper => ast.helpers.includes(helper))
        .map(aliasHelper)
        .join(', ')
      push(`const { ${staticHelpers} } = _Vue\n`)
    }
  }
  ...
  genHoists(ast.hoists, context)
  newline()
  push(`return `)
}

可以看到,這里會對前面我們在 transform 函數提及的 hoists 屬性的長度進行判斷。顯然,對于前面說的這個栗子,它的 ast.hoists.length 長度是大于 0 的。所以,這里就會根據 hoists 中的 AST 生成對應的可執行代碼。因此,到這里,生成的可執行代碼會是這樣:

const _Vue = Vue
const { createVNode: _createVNode } = _Vue
// 靜態提升部分
const _hoisted_1 = _createVNode("div", null, "hi vue3", -1 /* HOISTED */)
// render 函數會在這下面

以上就是“Vue3.0靜態節點提升是什么”這篇文章的所有內容,感謝各位的閱讀!相信大家閱讀完這篇文章都有很大的收獲,小編每天都會為大家更新不同的知識,如果還想學習更多的知識,請關注億速云行業資訊頻道。

向AI問一下細節

免責聲明:本站發布的內容(圖片、視頻和文字)以原創、轉載和分享為主,文章觀點不代表本網站立場,如果涉及侵權請聯系站長郵箱:is@yisu.com進行舉報,并提供相關證據,一經查實,將立刻刪除涉嫌侵權內容。

vue
AI

突泉县| 清流县| 奉化市| 邵阳县| 巫山县| 高淳县| 深泽县| 武胜县| 满洲里市| 皋兰县| 旬邑县| 浦江县| 普宁市| 通州区| 嵊州市| 许昌县| 云南省| 涞水县| 江阴市| 石首市| 石泉县| 板桥市| 柳林县| 高阳县| 迁西县| 扶风县| 开江县| 平江县| 巴彦县| 台中市| 重庆市| 玉溪市| 巩义市| 崇明县| 凤冈县| 兴国县| 皮山县| 华宁县| 抚顺市| 衡山县| 大港区|