vue3編譯做了哪些優(yōu)化

          來(lái)源:php中文網(wǎng) | 2022-12-19 18:04:15 |

          本教程操作環(huán)境:windows7系統(tǒng)、vue3版,DELL G3電腦。

          本文主要來(lái)分析 Vue3.0編譯階段做的優(yōu)化,在 patch階段是如何利用這些優(yōu)化策略來(lái)減少比對(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 過(guò)程如圖所示:

          可以看到,因?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ù)量無(wú)關(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通過(guò)編譯階段對(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來(lái)追蹤自身包含的動(dòng)態(tài)節(jié)點(diǎn)。借助 Block tree,Vue.js 將 vnode 更新性能由與模版整體大小相關(guān)提升為與動(dòng)態(tài)內(nèi)容的數(shù)量相關(guān),這是一個(gè)非常大的性能突破。

          PatchFlag

          由于 diff算法無(wú)法避免新舊虛擬 DOM中無(wú)用的比較操作,Vue.js 3.0引入了 patchFlag,用來(lái)標(biāo)記動(dòng)態(tài)內(nèi)容。在編譯過(guò)程中會(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, // 掛載過(guò)事件的  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

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

          let currentBlock = nullfunction _openBlock() {  currentBlock = [] // 用一個(gè)數(shù)組來(lái)收集多個(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 如下:

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

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

          我們之前分析過(guò),在 patch階段更新節(jié)點(diǎn)元素的時(shí)候,會(huì)執(zhí)行 patchElement函數(shù),我們?cè)賮?lái)回顧一下它的實(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é)分析過(guò)這個(gè)流程,在分析子節(jié)點(diǎn)更新的部分,當(dāng)時(shí)并沒有考慮到優(yōu)化的場(chǎng)景,所以只分析了全量比對(duì)更新的場(chǎng)景。

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

          我們來(lái)看一下它的實(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)化策略:

          接下來(lái)我們看一下屬性比對(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ì)比更新。圖解如下:

          靜態(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è)醬紫的,那么問(wèn)題就是每次調(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)的,我們可以將提升出來(lái)的節(jié)點(diǎn)字符串化。 當(dāng)連續(xù)靜態(tài)節(jié)點(diǎn)超過(guò) 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ì)被緩存起來(lái),后續(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)  }))}

          總結(jié)

          以上幾點(diǎn)即為 Vuejs在編譯階段做的優(yōu)化,基于上面幾點(diǎn),Vuejspatch過(guò)程中極大地提高了性能。

          以上就是vue3編譯做了哪些優(yōu)化的詳細(xì)內(nèi)容,更多請(qǐng)關(guān)注php中文網(wǎng)其它相關(guān)文章!

          關(guān)鍵詞: Vue.js

          国产亚洲自拍一区| 亚洲AV人无码综合在线观看 | 在线综合亚洲欧洲综合网站| 亚洲熟妇无码乱子AV电影| jzzijzzij在线观看亚洲熟妇| 亚洲人成电影院在线观看| 91亚洲国产成人久久精品网站| 亚洲成色在线影院| 亚洲AV日韩精品久久久久久久| 亚洲国产精品无码av| 亚洲成a人片在线观看无码专区| 亚洲日产无码中文字幕| 日本亚洲欧洲免费天堂午夜看片女人员| 亚洲性日韩精品国产一区二区| 亚洲国产精品成人一区| 亚洲日韩在线观看| 亚洲一区二区三区自拍公司| 国产精品亚洲精品日韩已满| 亚洲va久久久噜噜噜久久天堂| 亚洲av中文无码乱人伦在线咪咕| 国产亚洲一区二区三区在线| 亚洲AV综合色一区二区三区| 亚洲an天堂an在线观看| 精品亚洲成AV人在线观看| 亚洲国产精品午夜电影| 国产精品亚洲自在线播放页码| 一本天堂ⅴ无码亚洲道久久| 亚洲youwu永久无码精品 | 亚洲国产精品VA在线观看麻豆| 亚洲动漫精品无码av天堂| 亚洲人成在线播放网站岛国| 亚洲精品亚洲人成在线观看麻豆| 亚洲一级高清在线中文字幕| 亚洲一卡2卡3卡4卡5卡6卡| 国产精品自拍亚洲| 中文字幕亚洲一区二区va在线| 亚洲国产精品VA在线观看麻豆| 亚洲精品美女久久久久9999| 亚洲看片无码在线视频| 久久精品国产亚洲av天美18| 亚洲色图综合在线|