溫馨提示×

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

密碼登錄×
登錄注冊(cè)×
其他方式登錄
點(diǎn)擊 登錄注冊(cè) 即表示同意《億速云用戶服務(wù)條款》

vue3編譯優(yōu)化的內(nèi)容有哪些

發(fā)布時(shí)間:2022-12-27 14:14:29 來源:億速云 閱讀:164 作者:iii 欄目:web開發(fā)

這篇“vue3編譯優(yōu)化的內(nèi)容有哪些”文章的知識(shí)點(diǎn)大部分人都不太理解,所以小編給大家總結(jié)了以下內(nèi)容,內(nèi)容詳細(xì),步驟清晰,具有一定的借鑒價(jià)值,希望大家閱讀完這篇文章能有所收獲,下面我們一起來看看這篇“vue3編譯優(yōu)化的內(nèi)容有哪些”文章吧。

vue3編譯優(yōu)化有:1、引入了 patchFlag,用來標(biāo)記動(dòng)態(tài)內(nèi)容;在編譯過程中會(huì)根據(jù)不同的屬性類型打上不同的標(biāo)識(shí),從而實(shí)現(xiàn)了快速diff算法。2、Block Tree。3、靜態(tài)提升,是將靜態(tài)的節(jié)點(diǎn)或者屬性提升出去。4、預(yù)解析字符串化,當(dāng)連續(xù)靜態(tài)節(jié)點(diǎn)超過10個(gè)時(shí),會(huì)將靜態(tài)節(jié)點(diǎn)序列化為字符串。5、函數(shù)緩存;開啟cacheHandlers選項(xiàng)后,函數(shù)會(huì)被緩存起來,后續(xù)可直接使用。

本文主要來分析 Vue3.0 編譯階段做的優(yōu)化,在 patch 階段是如何利用這些優(yōu)化策略來減少比對(duì)次數(shù)。 由于組件更新時(shí)依然需要遍歷該組件的整個(gè) vnode 樹,比如下面這個(gè)模板:

<template>
  <div id="container">
    <p class="text">static text</p>
    <p class="text">static text</p>
    <p class="text">{{ message }}</p>
    <p class="text">static text</p>
    <p class="text">static text</p>
  </div>
</template>

整個(gè) diff 過程如圖所示:

vue3編譯優(yōu)化的內(nèi)容有哪些

可以看到,因?yàn)檫@段代碼中只有一個(gè)動(dòng)態(tài)節(jié)點(diǎn),所以這里有很多 diff 和遍歷其實(shí)都是不需要的,這就會(huì)導(dǎo)致 vnode 的性能跟模版大小正相關(guān),跟動(dòng)態(tài)節(jié)點(diǎn)的數(shù)量無關(guān),當(dāng)一些組件的整個(gè)模版內(nèi)只有少量動(dòng)態(tài)節(jié)點(diǎn)時(shí),這些遍歷都是性能的浪費(fèi)。對(duì)于上述例子,理想狀態(tài)只需要 diff 這個(gè)綁定 message 動(dòng)態(tài)節(jié)點(diǎn)的 p 標(biāo)簽即可。

Vue.js 3.0 通過編譯階段對(duì)靜態(tài)模板的分析,編譯生成了 Block tree。

Block tree 是一個(gè)將模板基于動(dòng)態(tài)節(jié)點(diǎn)指令切割的嵌套區(qū)塊,每個(gè)區(qū)塊內(nèi)部的節(jié)點(diǎn)結(jié)構(gòu)是固定的,而且每個(gè)區(qū)塊只需要以一個(gè) Array 來追蹤自身包含的動(dòng)態(tài)節(jié)點(diǎn)。借助 Block treeVue.js 將 vnode 更新性能由與模版整體大小相關(guān)提升為與動(dòng)態(tài)內(nèi)容的數(shù)量相關(guān),這是一個(gè)非常大的性能突破。

PatchFlag

由于 diff 算法無法避免新舊虛擬 DOM 中無用的比較操作,Vue.js 3.0 引入了 patchFlag,用來標(biāo)記動(dòng)態(tài)內(nèi)容。在編譯過程中會(huì)根據(jù)不同的屬性類型打上不同的標(biāo)識(shí),從而實(shí)現(xiàn)了快速 diff 算法。PatchFlags 的所有枚舉類型如下所示:

export const enum PatchFlags {
  TEXT = 1, // 動(dòng)態(tài)文本節(jié)點(diǎn)
  CLASS = 1 << 1, // 動(dòng)態(tài)class
  STYLE = 1 << 2, // 動(dòng)態(tài)style
  PROPS = 1 << 3, // 除了class、style動(dòng)態(tài)屬性
  FULL_PROPS = 1 << 4, // 有key,需要完整diff
  HYDRATE_EVENTS = 1 << 5, // 掛載過事件的
  STABLE_FRAGMENT = 1 << 6, // 穩(wěn)定序列,子節(jié)點(diǎn)順序不會(huì)發(fā)生變化
  KEYED_FRAGMENT = 1 << 7, // 子節(jié)點(diǎn)有key的fragment
  UNKEYED_FRAGMENT = 1 << 8, // 子節(jié)點(diǎn)沒有key的fragment
  NEED_PATCH = 1 << 9, // 進(jìn)行非props比較, ref比較
  DYNAMIC_SLOTS = 1 << 10, // 動(dòng)態(tài)插槽
  DEV_ROOT_FRAGMENT = 1 << 11, 
  HOISTED = -1, // 表示靜態(tài)節(jié)點(diǎn),內(nèi)容變化,不比較兒子
  BAIL = -2 // 表示diff算法應(yīng)該結(jié)束
}

Block Tree

vue3編譯優(yōu)化的內(nèi)容有哪些

左側(cè)的 template 經(jīng)過編譯后會(huì)生成右側(cè)的 render 函數(shù),里面有 _openBlock_createElementBlock、_toDisplayString_createElementVNode(createVnode) 等輔助函數(shù)。

let currentBlock = null
function _openBlock() {
  currentBlock = [] // 用一個(gè)數(shù)組來收集多個(gè)動(dòng)態(tài)節(jié)點(diǎn)
}
function _createElementBlock(type, props, children, patchFlag) {
  return setupBlock(createVnode(type, props, children, patchFlag));
}

export function createVnode(type, props, children = null, patchFlag = 0) {
  const vnode = {
    type,
    props,
    children,
    el: null, // 虛擬節(jié)點(diǎn)上對(duì)應(yīng)的真實(shí)節(jié)點(diǎn),后續(xù)diff算法
    key: props?.["key"],
    __v_isVnode: true,
    shapeFlag,
    patchFlag 
  };
  ...

  if (currentBlock && vnode.patchFlag > 0) {
    currentBlock.push(vnode);
  }
  return vnode;
}

function setupBlock(vnode) {
  vnode.dynamicChildren = currentBlock;
  currentBlock = null;
  return vnode;
}

function _toDisplayString(val) {
  return isString(val)
    ? val
    : val == null
    ? ""
    : isObject(val)
    ? JSON.stringify(val)
    : String(val);
}

此時(shí)生成的 vnode 如下:

vue3編譯優(yōu)化的內(nèi)容有哪些

此時(shí)生成的虛擬節(jié)點(diǎn)多出一個(gè) dynamicChildren 屬性,里面收集了動(dòng)態(tài)節(jié)點(diǎn) span

節(jié)點(diǎn) diff 優(yōu)化策略:

我們之前分析過,在 patch 階段更新節(jié)點(diǎn)元素的時(shí)候,會(huì)執(zhí)行 patchElement 函數(shù),我們?cè)賮砘仡櫼幌滤膶?shí)現(xiàn):

const patchElement = (n1, n2) => { // 先復(fù)用節(jié)點(diǎn)、在比較屬性、在比較兒子
  let el = n2.el = n1.el;
  let oldProps = n1.props || {}; // 對(duì)象
  let newProps = n2.props || {}; // 對(duì)象
  patchProps(oldProps, newProps, el);

  if (n2.dynamicChildren) { // 只比較動(dòng)態(tài)元素
    patchBlockChildren(n1, n2);
  } else {
    patchChildren(n1, n2, el); // 全量 diff
  }
}

我們?cè)谇懊娼M件更新的章節(jié)分析過這個(gè)流程,在分析子節(jié)點(diǎn)更新的部分,當(dāng)時(shí)并沒有考慮到優(yōu)化的場(chǎng)景,所以只分析了全量比對(duì)更新的場(chǎng)景。

而實(shí)際上,如果這個(gè) vnode 是一個(gè) Block vnode,那么我們不用去通過 patchChildren 全量比對(duì),只需要通過 patchBlockChildren 去比對(duì)并更新 Block 中的動(dòng)態(tài)子節(jié)點(diǎn)即可。 由此可以看出性能被大幅度提升,從 tree 級(jí)別的比對(duì),變成了線性結(jié)構(gòu)比對(duì)。

我們來看一下它的實(shí)現(xiàn):

const patchBlockChildren = (n1, n2) => {
  for (let i = 0; i < n2.dynamicChildren.length; i++) {
    patchElement(n1.dynamicChildren[i], n2.dynamicChildren[i])
  }
}

屬性 diff 優(yōu)化策略:

接下來我們看一下屬性比對(duì)的優(yōu)化策略:

const patchElement = (n1, n2) => { // 先復(fù)用節(jié)點(diǎn)、在比較屬性、在比較兒子
  let el = n2.el = n1.el;
  let oldProps = n1.props || {}; // 對(duì)象
  let newProps = n2.props || {}; // 對(duì)象
  let { patchFlag, dynamicChildren } = n2
  
  if (patchFlag > 0) {
    if (patchFlag & PatchFlags.FULL_PROPS) { // 對(duì)所 props 都進(jìn)行比較更新
      patchProps(el, n2, oldProps, newProps, ...)
    } else {
      // 存在動(dòng)態(tài) class 屬性時(shí)
      if (patchFlag & PatchFlags.CLASS) {
        if (oldProps.class !== newProps.class) {
          hostPatchProp(el, 'class', null, newProps.class, ...)
        }
      }
      // 存在動(dòng)態(tài) style 屬性時(shí)
      if (patchFlag & PatchFlags.STYLE) {
        hostPatchProp(el, 'style', oldProps.style, newProps.style, ...)
      }
      
      // 針對(duì)除了 style、class 的 props
      if (patchFlag & PatchFlags.PROPS) {
        const propsToUpdate = n2.dynamicProps!
        for (let i = 0; i < propsToUpdate.length; i++) {
          const key = propsToUpdate[i]
          const prev = oldProps[key]
          const next = newProps[key]
          if (next !== prev) {
            hostPatchProp(el, key, prev, next, ...)
          }
        }
      }
      if (patchFlag & PatchFlags.TEXT) { // 存在動(dòng)態(tài)文本
        if (n1.children !== n2.children) {
          hostSetElementText(el, n2.children as string)
        }
      } 
    } else if (dynamicChildren == null) {
      patchProps(el, n2, oldProps, newProps, ...)
    }
  }
}

function hostPatchProp(el, key, prevValue, nextValue) {
  if (key === 'class') { // 更新 class 
    patchClass(el, nextValue)
  } else if (key === 'style') { // 更新 style
    patchStyle(el, prevValue, nextValue)
  } else if (/^on[^a-z]/.test(key)) {  // events  addEventListener
    patchEvent(el, key, nextValue);
  } else { // 普通屬性 el.setAttribute
    patchAttr(el, key, nextValue);
  }
}

function patchClass(el, nextValue) {
  if (nextValue == null) {
    el.removeAttribute('class'); // 如果不需要class直接移除
  } else {
    el.className = nextValue
  }
}

function patchStyle(el, prevValue, nextValue = {}){
  ...
}

function patchAttr(el, key, nextValue){
  ...
}

總結(jié): vue3 會(huì)充分利用 patchFlagdynamicChildren 做優(yōu)化。如果確定只是某個(gè)局部的變動(dòng),比如 style 改變,那么只會(huì)調(diào)用 hostPatchProp 并傳入對(duì)應(yīng)的參數(shù) style 做特定的更新(靶向更新);如果有 dynamicChildren,會(huì)執(zhí)行 patchBlockChildren 做對(duì)比更新,不會(huì)每次都對(duì) props 和子節(jié)點(diǎn)進(jìn)行全量的對(duì)比更新。圖解如下:

vue3編譯優(yōu)化的內(nèi)容有哪些

靜態(tài)提升

靜態(tài)提升是將靜態(tài)的節(jié)點(diǎn)或者屬性提升出去,假設(shè)有以下模板:

<div>
  <span>hello</span> 
  <span a=1 b=2>{{name}}</span>
  <a><span>{{age}}</span></a>
</div>

編譯生成的 render 函數(shù)如下:

export function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createElementBlock("div", null, [
    _createElementVNode("span", null, "hello"),
    _createElementVNode("span", {
      a: "1",
      b: "2"
    }, _toDisplayString(_ctx.name), 1 /* TEXT */),
    _createElementVNode("a", null, [
      _createElementVNode("span", null, _toDisplayString(_ctx.age), 1 /* TEXT */)
    ])
  ]))
}

我們把模板編譯成 render 函數(shù)是這個(gè)醬紫的,那么問題就是每次調(diào)用 render 函數(shù)都要重新創(chuàng)建虛擬節(jié)點(diǎn)。

開啟靜態(tài)提升 hoistStatic 選項(xiàng)后

const _hoisted_1 = /*#__PURE__*/_createElementVNode("span", null, "hello", -1 /* HOISTED */)
const _hoisted_2 = {
  a: "1",
  b: "2"
}

export function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createElementBlock("div", null, [
    _hoisted_1,
    _createElementVNode("span", _hoisted_2, _toDisplayString(_ctx.name), 1 /* TEXT */),
    _createElementVNode("a", null, [
      _createElementVNode("span", null, _toDisplayString(_ctx.age), 1 /* TEXT */)
    ])
  ]))
}

預(yù)解析字符串化

靜態(tài)提升的節(jié)點(diǎn)都是靜態(tài)的,我們可以將提升出來的節(jié)點(diǎn)字符串化。 當(dāng)連續(xù)靜態(tài)節(jié)點(diǎn)超過 10 個(gè)時(shí),會(huì)將靜態(tài)節(jié)點(diǎn)序列化為字符串。

假如有如下模板:

<div>
  <span>static</span>
  <span>static</span>
  <span>static</span>
  <span>static</span>
  <span>static</span>
  <span>static</span>
  <span>static</span>
  <span>static</span>
  <span>static</span>
  <span>static</span>
</div>

開啟靜態(tài)提升 hoistStatic 選項(xiàng)后

const _hoisted_1 = /*#__PURE__*/_createStaticVNode("<span>static</span><span>static</span><span>static</span><span>static</span><span>static</span><span>static</span><span>static</span><span>static</span><span>static</span><span>static</span>", 10)
const _hoisted_11 = [  _hoisted_1]

export function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createElementBlock("div", null, _hoisted_11))
}

函數(shù)緩存

假如有如下模板:

<div @click="event => v = event.target.value"></div>

編譯后:

const _hoisted_1 = ["onClick"]

export function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createElementBlock("div", {
    onClick: event => _ctx.v = event.target.value
  }, null, 8 /* PROPS */, _hoisted_1))
}

每次調(diào)用 render 的時(shí)候要?jiǎng)?chuàng)建新函數(shù),開啟函數(shù)緩存 cacheHandlers 選項(xiàng)后,函數(shù)會(huì)被緩存起來,后續(xù)可以直接使用

export function render(_ctx, _cache, $props, $setup, $data, $options) {
  return (_openBlock(), _createElementBlock("div", {
    onClick: _cache[0] || (_cache[0] = event => _ctx.v = event.target.value)
  }))
}

以上就是關(guān)于“vue3編譯優(yōu)化的內(nèi)容有哪些”這篇文章的內(nèi)容,相信大家都有了一定的了解,希望小編分享的內(nèi)容對(duì)大家有幫助,若想了解更多相關(guān)的知識(shí)內(nèi)容,請(qǐng)關(guān)注億速云行業(yè)資訊頻道。

向AI問一下細(xì)節(jié)

免責(zé)聲明:本站發(fā)布的內(nèi)容(圖片、視頻和文字)以原創(chuàng)、轉(zhuǎn)載和分享為主,文章觀點(diǎn)不代表本網(wǎng)站立場(chǎng),如果涉及侵權(quán)請(qǐng)聯(lián)系站長(zhǎng)郵箱:is@yisu.com進(jìn)行舉報(bào),并提供相關(guān)證據(jù),一經(jīng)查實(shí),將立刻刪除涉嫌侵權(quán)內(nèi)容。

AI