欧美bbbwbbbw肥妇,免费乱码人妻系列日韩,一级黄片

解決vue3中內(nèi)存泄漏的問(wèn)題

 更新時(shí)間:2023年07月30日 14:46:29   作者:PK  
在項(xiàng)目中會(huì)發(fā)現(xiàn)一個(gè)奇怪的現(xiàn)象,當(dāng)我們?cè)谑褂胑lement-plus中的圖標(biāo)組件時(shí)會(huì)出現(xiàn)內(nèi)存泄漏,所以本文講給大家講講如何解決vue3中的內(nèi)存泄漏的問(wèn)題,需要的朋友可以參考下

vue3的內(nèi)存泄漏

在項(xiàng)目中會(huì)發(fā)現(xiàn)一個(gè)奇怪的現(xiàn)象,當(dāng)使用element-plus中的圖標(biāo)組件時(shí)會(huì)出現(xiàn)內(nèi)存泄漏。詳情查看

解決方案1:關(guān)閉靜態(tài)提升。詳情查看

解決方案2:參考本文

至于為什么靜態(tài)提升會(huì)導(dǎo)致內(nèi)存泄漏,本文將通過(guò)幾個(gè)案例的源碼分析詳細(xì)講解。

案例1

 <div id="app"></div>
 <script type="module">
     ?import {
     ? ?createApp,
     ? ?ref,
      } from '../packages/vue/dist/vue.esm-browser.js'
     ?const app = createApp({
     ? ?setup() {
     ? ? ?const show = ref(false)
     ? ? ?return {
     ? ? ? ?show,
     ? ?  }
     ?  },
    ?
     ? ?template: `
     ? ? ? ?<div> 
     ? ? ? ? ? ?<button @click="show=!show">show</button>
     ? ? ? ? ? ?<template v-if="show">
     ? ? ? ? ? ? ? ?<template v-for="i in 3"> ?
     ? ? ? ? ? ? ? ? ? ?<div>
     ? ? ? ? ? ? ? ? ? ? ? ?<span>12</span>
     ? ? ? ? ? ? ? ? ? ? ? ?<span>34</span>
                        </div>
                   ?</template>
               ?</template>
            </div>
     ? ? ? ? ? ?`
      })
     ?app.mount('#app')
  </script>

點(diǎn)擊按鈕前:游離節(jié)點(diǎn)只有一個(gè),這是熱更新導(dǎo)致的,不需要管。

點(diǎn)擊兩次按鈕后:

對(duì)比可以發(fā)現(xiàn)多出了兩個(gè)span和和一個(gè)div和兩個(gè)text的游離節(jié)點(diǎn),最下面的注釋節(jié)點(diǎn)不需要管。

先來(lái)看一下這個(gè)模板編譯后的結(jié)果:

const _Vue = Vue
    const { createElementVNode: _createElementVNode, createCommentVNode: _createCommentVNode } = _Vue
    ?
    const _hoisted_1 = ["onClick"]
    const _hoisted_2 = /*#__PURE__*/_createElementVNode("span", null, "12", -1 /* HOISTED */)
    const _hoisted_3 = /*#__PURE__*/_createElementVNode("span", null, "34", -1 /* HOISTED */)
    const _hoisted_4 = [
     ?_hoisted_2,
     ?_hoisted_3
    ]
    ?
    return function render(_ctx, _cache, $props, $setup, $data, $options) {
     ?with (_ctx) {
     ? ?const { createElementVNode: _createElementVNode, renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock, createCommentVNode: _createCommentVNode } = _Vue
    ?
     ? ?return (_openBlock(), _createElementBlock("div", null, [
     ? ? ?_createElementVNode("button", {
     ? ? ? ?onClick: $event => (show=!show)
     ? ?  }, "show", 8 /* PROPS */, _hoisted_1),
     ? ? ?show
     ? ? ? ?? (_openBlock(true), _createElementBlock(_Fragment, { key: 0 }, _renderList(3, (i) => {
     ? ? ? ? ? ?return (_openBlock(), _createElementBlock("div", null, _hoisted_4))
     ? ? ? ?  }), 256 /* UNKEYED_FRAGMENT */))
     ? ? ?  : _createCommentVNode("v-if", true)
     ?  ]))
      }
    }

這里關(guān)注_hoisted_4靜態(tài)節(jié)點(diǎn)。

掛載階段

掛載第一個(gè)div的子節(jié)點(diǎn)時(shí):

此時(shí)children中兩個(gè)節(jié)點(diǎn)分別指向_hoisted_2 和 _hoisted_3

循環(huán)遍歷children時(shí),會(huì)走cloneIfMounted。

export function cloneIfMounted(child: VNode): VNode {
     ?return (child.el === null && child.patchFlag !== PatchFlags.HOISTED) ||
     ? ?child.memo
     ? ?? child
     ?  : cloneVNode(child)
    }

_hoisted_2 和 _hoisted_3一開(kāi)始屬性el為null但patchFlag使HOISTED,所以會(huì)走cloneVnode

export function cloneVNode<T, U>(
     ?vnode: VNode<T, U>,
     ?extraProps?: (Data & VNodeProps) | null,
     ?mergeRef = false
    ): VNode<T, U> {
     ?// This is intentionally NOT using spread or extend to avoid the runtime
     ?// key enumeration cost.
     ?const { props, ref, patchFlag, children } = vnode
     ?const mergedProps = extraProps ? mergeProps(props || {}, extraProps) : props
     ?const cloned: VNode<T, U> = {
     ? ?__v_isVNode: true,
     ? ?__v_skip: true,
     ? ?type: vnode.type,
     ? ?props: mergedProps,
     ? ?key: mergedProps && normalizeKey(mergedProps),
     ? ?ref:
     ? ? ?extraProps && extraProps.ref
     ? ? ? ?? // #2078 in the case of <component :is="vnode" ref="extra"/>
     ? ? ? ? ?// if the vnode itself already has a ref, cloneVNode will need to merge
     ? ? ? ? ?// the refs so the single vnode can be set on multiple refs
     ? ? ? ? ?mergeRef && ref
     ? ? ? ? ?? isArray(ref)
     ? ? ? ? ? ?? ref.concat(normalizeRef(extraProps)!)
     ? ? ? ? ?  : [ref, normalizeRef(extraProps)!]
     ? ? ? ?  : normalizeRef(extraProps)
     ? ? ?  : ref,
     ? ?scopeId: vnode.scopeId,
     ? ?slotScopeIds: vnode.slotScopeIds,
     ? ?children:
     ? ? ?__DEV__ && patchFlag === PatchFlags.HOISTED && isArray(children)
     ? ? ? ?? (children as VNode[]).map(deepCloneVNode)
     ? ? ?  : children,
     ? ?target: vnode.target,
     ? ?targetAnchor: vnode.targetAnchor,
     ? ?staticCount: vnode.staticCount,
     ? ?shapeFlag: vnode.shapeFlag,
     ? ?// if the vnode is cloned with extra props, we can no longer assume its
     ? ?// existing patch flag to be reliable and need to add the FULL_PROPS flag.
     ? ?// note: preserve flag for fragments since they use the flag for children
     ? ?// fast paths only.
     ? ?patchFlag:
     ? ? ?extraProps && vnode.type !== Fragment
     ? ? ? ?? patchFlag === -1 // hoisted node
     ? ? ? ? ?? PatchFlags.FULL_PROPS
     ? ? ? ?  : patchFlag | PatchFlags.FULL_PROPS
     ? ? ?  : patchFlag,
     ? ?dynamicProps: vnode.dynamicProps,
     ? ?dynamicChildren: vnode.dynamicChildren,
     ? ?appContext: vnode.appContext,
     ? ?dirs: vnode.dirs,
     ? ?transition: vnode.transition,
    ?
     ? ?// These should technically only be non-null on mounted VNodes. However,
     ? ?// they *should* be copied for kept-alive vnodes. So we just always copy
     ? ?// them since them being non-null during a mount doesn't affect the logic as
     ? ?// they will simply be overwritten.
     ? ?component: vnode.component,
     ? ?suspense: vnode.suspense,
     ? ?ssContent: vnode.ssContent && cloneVNode(vnode.ssContent),
     ? ?ssFallback: vnode.ssFallback && cloneVNode(vnode.ssFallback),
     ? ?el: vnode.el,
     ? ?anchor: vnode.anchor,
     ? ?ctx: vnode.ctx,
     ? ?ce: vnode.ce
      }
     ?if (__COMPAT__) {
     ? ?defineLegacyVNodeProperties(cloned as VNode)
      }
     ?return cloned
    }

克隆時(shí)會(huì)將被克隆節(jié)點(diǎn)的el賦值給新的節(jié)點(diǎn)。

回到循環(huán)體,可以看到將克隆后的節(jié)點(diǎn)重新賦值給了children即_hoisted_4[i],此時(shí)_hoisted_4 中的內(nèi)容不再指向_hoisted_2_hoisted_3,而是克隆后的節(jié)點(diǎn)。_hoisted_2_hoisted_3就此完全脫離了關(guān)系。這是一個(gè)疑點(diǎn),每次都需要克隆,不懂這樣靜態(tài)的提升的意義在哪里。

后續(xù)div子節(jié)點(diǎn)的掛載都會(huì)走這個(gè)循環(huán),每次循環(huán)都會(huì)克隆節(jié)點(diǎn)并重新賦值給children即_hoisted_4[i]。

到此,掛載完成。

可想而知,掛載完成后,children即_hoisted_4中的內(nèi)容是最后一個(gè)div的兩個(gè)虛擬子節(jié)點(diǎn)。

卸載階段

這里卸載的虛擬節(jié)點(diǎn)的type是Symbol(v-fgt)這是vue處理<template v-if>標(biāo)簽時(shí)創(chuàng)建的虛擬節(jié)點(diǎn),這里需要關(guān)注的是unmount方法的第四個(gè)參數(shù)doRemove,傳入了true。

 type UnmountFn = (
     ?vnode: VNode,
     ?parentComponent: ComponentInternalInstance | null,
     ?parentSuspense: SuspenseBoundary | null,
     ?doRemove?: boolean,
     ?optimized?: boolean
    ) => void
    const unmount: UnmountFn

進(jìn)入unmount函數(shù),會(huì)走到type===Fragment的分支。

else if (
      (type === Fragment &&
     ? patchFlag &
     ? (PatchFlags.KEYED_FRAGMENT | PatchFlags.UNKEYED_FRAGMENT)) ||
      (!optimized && shapeFlag & ShapeFlags.ARRAY_CHILDREN)
    ) {
     ?unmountChildren(children as VNode[], parentComponent, parentSuspense)
    }

調(diào)用unmountChildren方法。

type UnmountChildrenFn = (
     ?children: VNode[],
     ?parentComponent: ComponentInternalInstance | null,
     ?parentSuspense: SuspenseBoundary | null,
     ?doRemove?: boolean,
     ?optimized?: boolean,
     ?start?: number
    ) => void
const unmountChildren: UnmountChildrenFn = (
     ? ?children,
     ? ?parentComponent,
     ? ?parentSuspense,
     ? ?doRemove = false,
     ? ?optimized = false,
     ? ?start = 0
 ) => {
    for (let i = start; i < children.length; i++) {
     ?unmount(children[i], parentComponent, parentSuspense, doRemove, optimized)
    }
}

調(diào)用時(shí)沒(méi)有傳入第四個(gè)參數(shù),默認(rèn)是false。然后會(huì)遞歸調(diào)用unmount方法。

注意,此時(shí)傳入的doRemove是false。

循環(huán)調(diào)用unmount傳入div的虛擬節(jié)點(diǎn)

此時(shí)走到unmount方法中的這個(gè)分支

 else if (
     ?dynamicChildren &&
     ?// #1153: fast path should not be taken for non-stable (v-for) fragments
      (type !== Fragment ||
     ? (patchFlag > 0 && patchFlag & PatchFlags.STABLE_FRAGMENT))
    ) {
     ?// fast path for block nodes: only need to unmount dynamic children.
     ?unmountChildren(
     ? ?dynamicChildren,
     ? ?parentComponent,
     ? ?parentSuspense,
     ? ?false,
     ? ?true
      )
    }

dynamicChildren是空數(shù)組,所以unmountChildren不會(huì)發(fā)生什么。

繼續(xù)往下走,unmount方法中的最后有

if (doRemove) {
    ?remove(vnode)
}

此時(shí)doRemove為false,不會(huì)調(diào)用remove方法。

處理完三個(gè)div的節(jié)點(diǎn)后,函數(shù)回到上一層。接著處理type是Symbol(v-fgt)的虛擬節(jié)點(diǎn)。而此時(shí)doRemove為true,調(diào)用remove方法。

const remove: RemoveFn = vnode => {
     ?const { type, el, anchor, transition } = vnode
     ?if (type === Fragment) {
     ? ?if (
     ? ? ?__DEV__ &&
     ? ? ?vnode.patchFlag > 0 &&
     ? ? ?vnode.patchFlag & PatchFlags.DEV_ROOT_FRAGMENT &&
     ? ? ?transition &&
     ? ? ?!transition.persisted
     ?  ) {
     ? ?  ;(vnode.children as VNode[]).forEach(child => {
     ? ? ? ?if (child.type === Comment) {
     ? ? ? ? ?hostRemove(child.el!)
     ? ? ?  } else {
     ? ? ? ? ?remove(child)
     ? ? ?  }
     ? ?  })
     ?  } else {
     ? ? ?removeFragment(el!, anchor!)
     ?  }
     ? ?return
      }
    ?
     ?if (type === Static) {
     ? ?removeStaticNode(vnode)
     ? ?return
      }
    ?
     ?const performRemove = () => {
     ? ?hostRemove(el!)
     ? ?if (transition && !transition.persisted && transition.afterLeave) {
     ? ? ?transition.afterLeave()
     ?  }
      }
    ?
     ?if (
     ? ?vnode.shapeFlag & ShapeFlags.ELEMENT &&
     ? ?transition &&
     ? ?!transition.persisted
      ) {
     ? ?const { leave, delayLeave } = transition
     ? ?const performLeave = () => leave(el!, performRemove)
     ? ?if (delayLeave) {
     ? ? ?delayLeave(vnode.el!, performRemove, performLeave)
     ?  } else {
     ? ? ?performLeave()
     ?  }
      } else {
     ? ?performRemove()
      }
    }

會(huì)走到removeFragment方法。

const removeFragment = (cur: RendererNode, end: RendererNode) => {
 ?// For fragments, directly remove all contained DOM nodes.
 ?// (fragment child nodes cannot have transition)
 ?let next
 ?while (cur !== end) {
 ? ?next = hostNextSibling(cur)!
 ? ?hostRemove(cur)
 ? ?cur = next
  }
 ?hostRemove(end)
}

從這里可以看到,會(huì)依次刪除掉3個(gè)div的真實(shí)dom。

到此,整個(gè)<template v-if>卸載完成。

那到底內(nèi)存泄漏在哪里?

還記得_hoissted_4保存的是最后一個(gè)虛擬div節(jié)點(diǎn)的兩個(gè)虛擬span節(jié)點(diǎn),而節(jié)點(diǎn)中的el屬性依然維持著真實(shí)節(jié)點(diǎn)的引用,不會(huì)被GC,

所以這就造成了內(nèi)存泄漏。這里就解釋了那兩個(gè)游離的span節(jié)點(diǎn)。

好奇的你一定會(huì)問(wèn):還有一個(gè)游離的div和兩個(gè)游離的text節(jié)點(diǎn)哪里來(lái)的呢?

不要忘記了,el中也會(huì)保持對(duì)父親和兒子的引用。詳情見(jiàn)下圖

每一個(gè)span都有一個(gè)text兒子,共用一個(gè)div父節(jié)點(diǎn),完美解釋了前面提到的所有游離節(jié)點(diǎn)。

案例2

將案例1的代碼稍稍做下改動(dòng)。

<div id="app"></div>
    <script type="module">
     ?import {
     ? ?createApp,
     ? ?ref,
      } from '../packages/vue/dist/vue.esm-browser.js'
     ?const app = createApp({
     ? ?setup() {
     ? ? ?const show = ref(false)
     ? ? ?return {
     ? ? ? ?show,
     ? ?  }
     ?  },
    ?
     ? ?template: `
     ? ? ? ?<div> 
     ? ? ? ? ? ?<button @click="show=!show">show</button>
     ? ? ? ? ? ?<div v-if="show">
     ? ? ? ? ? ? ? ?<template v-for="i in 3"> ?
     ? ? ? ? ? ? ? ? ? ?<div>
     ? ? ? ? ? ? ? ? ? ? ? ?<span>12</span>
     ? ? ? ? ? ? ? ? ? ? ? ?<span>34</span>
                        </div>
                    </template>
                </div>
            </div>
     ? ? ? ? ? ?`
      })
     ?app.mount('#app')
    </script>

點(diǎn)擊按鈕前:游離節(jié)點(diǎn)只有一個(gè),這是熱更新導(dǎo)致的,不需要管。

點(diǎn)擊兩次按鈕后:

你會(huì)震驚地發(fā)現(xiàn),我就改變了一個(gè)標(biāo)簽,泄漏的節(jié)點(diǎn)竟然多了這么多。 對(duì)比可以發(fā)現(xiàn)多出了六個(gè)span和和四個(gè)div和八個(gè)text的游離節(jié)點(diǎn),最下面的注釋節(jié)點(diǎn)不需要管。

同樣查看編譯后的結(jié)果

const _Vue = Vue
    const { createElementVNode: _createElementVNode, createCommentVNode: _createCommentVNode } = _Vue
    ?
    const _hoisted_1 = ["onClick"]
    const _hoisted_2 = { key: 0 }
    const _hoisted_3 = /*#__PURE__*/_createElementVNode("span", null, "12", -1 /* HOISTED */)
    const _hoisted_4 = /*#__PURE__*/_createElementVNode("span", null, "34", -1 /* HOISTED */)
    const _hoisted_5 = [
     ?_hoisted_3,
     ?_hoisted_4
    ]
    ?
    return function render(_ctx, _cache, $props, $setup, $data, $options) {
     ?with (_ctx) {
     ? ?const { createElementVNode: _createElementVNode, renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock, createCommentVNode: _createCommentVNode } = _Vue
    ?
     ? ?return (_openBlock(), _createElementBlock("div", null, [
     ? ? ?_createElementVNode("button", {
     ? ? ? ?onClick: $event => (show=!show)
     ? ?  }, "show", 8 /* PROPS */, _hoisted_1),
     ? ? ?show
     ? ? ? ?? (_openBlock(), _createElementBlock("div", _hoisted_2, [
     ? ? ? ? ?  (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(3, (i) => {
     ? ? ? ? ? ? ?return (_openBlock(), _createElementBlock("div", null, _hoisted_5))
     ? ? ? ? ?  }), 256 /* UNKEYED_FRAGMENT */))
     ? ? ? ?  ]))
     ? ? ?  : _createCommentVNode("v-if", true)
     ?  ]))
      }
    }

這里關(guān)注的是_hoisted_5節(jié)點(diǎn)

掛載階段

掛載和案例1的過(guò)程大差不差,只需要知道掛載完成后,children即_hoisted_5中的內(nèi)容是最后一個(gè)div的兩個(gè)虛擬子節(jié)點(diǎn)。

卸載階段

這里卸載的虛擬節(jié)點(diǎn)的type是div,這是<div v-if>的虛擬節(jié)點(diǎn),這里需要關(guān)注的是unmount方法的第四個(gè)參數(shù)doRemove,傳入了true。

 type UnmountFn = (
     ?vnode: VNode,
     ?parentComponent: ComponentInternalInstance | null,
     ?parentSuspense: SuspenseBoundary | null,
     ?doRemove?: boolean,
     ?optimized?: boolean
    ) => void
    const unmount: UnmountFn

進(jìn)入unmount函數(shù),會(huì)走到這個(gè)分支。

else if (
 ?dynamicChildren &&
 ?// #1153: fast path should not be taken for non-stable (v-for) fragments
  (type !== Fragment ||
 ? (patchFlag > 0 && patchFlag & PatchFlags.STABLE_FRAGMENT))
) {
 ?// fast path for block nodes: only need to unmount dynamic children.
 ?unmountChildren(
 ? ?dynamicChildren,
 ? ?parentComponent,
 ? ?parentSuspense,
 ? ?false,
 ? ?true
  )
}

dynamicChildren是長(zhǎng)度為1的數(shù)組,保存著:

注意,這里傳入的doRemove參數(shù)是false,這是和案例一同樣是卸載Fragment的重大區(qū)別。

type UnmountChildrenFn = (
     ?children: VNode[],
     ?parentComponent: ComponentInternalInstance | null,
     ?parentSuspense: SuspenseBoundary | null,
     ?doRemove?: boolean,
     ?optimized?: boolean,
     ?start?: number
    ) => void
     ?const unmountChildren: UnmountChildrenFn = (
     ? ?children,
     ? ?parentComponent,
     ? ?parentSuspense,
     ? ?doRemove = false,
     ? ?optimized = false,
     ? ?start = 0
      ) => {
     ? ?for (let i = start; i < children.length; i++) {
     ? ? ?unmount(children[i], parentComponent, parentSuspense, doRemove, optimized)
     ?  }
      }

接著調(diào)用unmount方法,傳入的doRemove是false。

新的unmount會(huì)走這個(gè)分支

else if (
      (type === Fragment &&
     ? patchFlag &
     ? (PatchFlags.KEYED_FRAGMENT | PatchFlags.UNKEYED_FRAGMENT)) ||
      (!optimized && shapeFlag & ShapeFlags.ARRAY_CHILDREN)
    ) {
     ?unmountChildren(children as VNode[], parentComponent, parentSuspense)
    }

調(diào)用時(shí)沒(méi)有傳入第四個(gè)參數(shù),默認(rèn)是false。然后會(huì)遞歸調(diào)用unmount方法。

處理3個(gè)div時(shí)與案例一一樣,接著回到處理Framgent。

繼續(xù)往下走,unmount方法中的最后有

 if (doRemove) {
     ?remove(vnode)
    }

此時(shí)doRemove為false,不會(huì)調(diào)用remove方法。所以到這里,依舊沒(méi)有到案例一的循環(huán)刪除真實(shí)節(jié)點(diǎn)的環(huán)節(jié)。接著往下看。

處理完Framgent,再回到<div v-if>對(duì)應(yīng)的虛擬節(jié)點(diǎn)的那一層。

 if (doRemove) {
     ?remove(vnode)
    }

此時(shí)doRemove為true,進(jìn)入remove函數(shù)。

const remove: RemoveFn = vnode => {
    ?const { type, el, anchor, transition } = vnode
    ?...
    ?...
    ?...
    ?const performRemove = () => {
    ? ?hostRemove(el!)
    ? ? ? ? ? ? ? if (transition && !transition.persisted && transition.afterLeave) {
    ? ? ?transition.afterLeave()
    ?  }
     }
   ?
    ?if (
    ? ?vnode.shapeFlag & ShapeFlags.ELEMENT &&
    ? ?transition &&
    ? ?!transition.persisted
     ) {
    ? ?const { leave, delayLeave } = transition
    ? ?const performLeave = () => leave(el!, performRemove)
    ? ?if (delayLeave) {
    ? ? ?delayLeave(vnode.el!, performRemove, performLeave)
    ?  } else {
    ? ? ?performLeave()
    ?  }
     } else {
    ? ?//走到這
    ? ?performRemove()
     }
   }

調(diào)用performRemove,此時(shí)<div v-if>這個(gè)節(jié)點(diǎn)從文檔中移除,那么它所包含的所有子節(jié)點(diǎn)也移除了。

此時(shí)卸載階段完成。

現(xiàn)在來(lái)思考一下,游離的節(jié)點(diǎn)是從哪里來(lái)的?

同樣的,_hoisted_5中的兩個(gè)虛擬節(jié)點(diǎn)中還各自保存著相應(yīng)的el 即span。那么根據(jù)其引用父親和兒子將全部不能被GC,所以變成了游離的節(jié)點(diǎn)。

這時(shí)的你會(huì)有所疑惑,為什么同樣是維持著父子關(guān)系,案例一種游離的只有2個(gè)span,2個(gè)text和一個(gè)div,而這卻多了這么多。

進(jìn)入解答環(huán)節(jié):

對(duì)比可以發(fā)現(xiàn),關(guān)鍵點(diǎn)在于方案二中處理Fragment時(shí)沒(méi)有進(jìn)入到

 if (doRemove) {
     ?remove(vnode)
    }

也就沒(méi)有到

 const removeFragment = (cur: RendererNode, end: RendererNode) => {
     ?// For fragments, directly remove all contained DOM nodes.
     ?// (fragment child nodes cannot have transition)
     ?let next
     ?while (cur !== end) {
     ? ?next = hostNextSibling(cur)!
     ? ?hostRemove(cur)
     ? ?cur = next
      }
     ?hostRemove(end)
    }

取而代之的是直接將最頂層的div從文檔刪除。那么_hoisted_5中的兩個(gè)虛擬節(jié)點(diǎn)中保存的el,其parentNode是div,而div中又保持著兄弟節(jié)點(diǎn)(因?yàn)闆](méi)有顯示地刪除,所以會(huì)繼續(xù)存在),即剩余的兩個(gè)div以及它的父節(jié)點(diǎn)即<div v-if>,而各自又保存著各自的兒子。

離的個(gè)數(shù):

Span: 3 * 2 =6

Div: 3 + 1 = 4

Text: 3*2 = 6

等等,text節(jié)點(diǎn)不是有8個(gè)嗎,還有兩個(gè)在哪里?

這就要提到vue處理Fragment的時(shí)候做的處理了。

可以看到,處理Fragment時(shí)會(huì)創(chuàng)建兩個(gè)空節(jié)點(diǎn),作為子節(jié)點(diǎn)插入的錨點(diǎn)。所以上下會(huì)多了兩個(gè)文本節(jié)點(diǎn)。如下圖。

所以最終的游離個(gè)數(shù):

Span: 3 * 2 =6

Div: 3 + 1 = 4

Text: 3*2+2 = 8

到此,完美解釋了所有的游離節(jié)點(diǎn)。

通過(guò)案例引出解決方案

可以看到一個(gè)標(biāo)簽的改變,直接改變了游離節(jié)點(diǎn)的個(gè)數(shù),設(shè)想一下,這是一個(gè)表格,而且里面包含靜態(tài)提升的節(jié)點(diǎn),那么整個(gè)表格將會(huì)變成游離節(jié)點(diǎn),發(fā)生內(nèi)存泄漏,這是我在項(xiàng)目中的親身經(jīng)歷,才會(huì)引發(fā)我寫(xiě)出這篇文章。

好了,為什么使用element的圖標(biāo)庫(kù)會(huì)造成內(nèi)存泄漏?

看看源碼:

// src/components/add-location.vue
    var _hoisted_1 = {
     ?viewBox: "0 0 1024 1024",
     ?xmlns: "http://www.w3.org/2000/svg"
    }, _hoisted_2 = /* @__PURE__ */ _createElementVNode("path", {
     ?fill: "currentColor",
     ?d: "M288 896h448q32 0 32 32t-32 32H288q-32 0-32-32t32-32z"
    }, null, -1), _hoisted_3 = /* @__PURE__ */ _createElementVNode("path", {
     ?fill: "currentColor",
     ?d: "M800 416a288 288 0 1 0-576 0c0 118.144 94.528 272.128 288 456.576C705.472 688.128 800 534.144 800 416zM512 960C277.312 746.688 160 565.312 160 416a352 352 0 0 1 704 0c0 149.312-117.312 330.688-352 544z"
    }, null, -1), _hoisted_4 = /* @__PURE__ */ _createElementVNode("path", {
     ?fill: "currentColor",
     ?d: "M544 384h96a32 32 0 1 1 0 64h-96v96a32 32 0 0 1-64 0v-96h-96a32 32 0 0 1 0-64h96v-96a32 32 0 0 1 64 0v96z"
    }, null, -1), _hoisted_5 = [
     ?_hoisted_2,
     ?_hoisted_3,
     ?_hoisted_4
    ];
    function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
     ?return _openBlock(), _createElementBlock("svg", _hoisted_1, _hoisted_5);
    }

這是什么?天,靜態(tài)提升!所以只要使用了就會(huì)造成內(nèi)存泄漏!

解決方案一在開(kāi)頭就有,就是關(guān)閉靜態(tài)提升。沒(méi)錯(cuò)。關(guān)閉后就不會(huì)出現(xiàn)靜態(tài)提升的數(shù)組,就不會(huì)有數(shù)組中的虛擬節(jié)點(diǎn)一直引用著el。

一開(kāi)始以為是element的鍋,經(jīng)過(guò)深層次分析,其實(shí)不是。這里只要換成任意靜態(tài)節(jié)點(diǎn)并開(kāi)啟靜態(tài)提升都會(huì)有這個(gè)問(wèn)題。

解決方案2

先來(lái)看看關(guān)閉靜態(tài)提升后,案例一和案例二編譯后的結(jié)果: 案例一:

const _Vue = Vue
    ?
    return function render(_ctx, _cache, $props, $setup, $data, $options) {
     ?with (_ctx) {
     ? ?const { createElementVNode: _createElementVNode, renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock, createCommentVNode: _createCommentVNode } = _Vue
    ?
     ? ?return (_openBlock(), _createElementBlock("div", null, [
     ? ? ?_createElementVNode("button", {
     ? ? ? ?onClick: $event => (show=!show)
     ? ?  }, "show", 8 /* PROPS */, ["onClick"]),
     ? ? ?show
     ? ? ? ?? (_openBlock(true), _createElementBlock(_Fragment, { key: 0 }, _renderList(3, (i) => {
     ? ? ? ? ? ?return (_openBlock(), _createElementBlock("div", null, [
     ? ? ? ? ? ? ?_createElementVNode("span", null, "12"),
     ? ? ? ? ? ? ?_createElementVNode("span", null, "34")
     ? ? ? ? ?  ]))
     ? ? ? ?  }), 256 /* UNKEYED_FRAGMENT */))
     ? ? ?  : _createCommentVNode("v-if", true)
     ?  ]))
      }
    }

案例二:

    const _Vue = Vue
    ?
    return function render(_ctx, _cache, $props, $setup, $data, $options) {
     ?with (_ctx) {
     ? ?const { createElementVNode: _createElementVNode, renderList: _renderList, Fragment: _Fragment, openBlock: _openBlock, createElementBlock: _createElementBlock, createCommentVNode: _createCommentVNode } = _Vue
    ?
     ? ?return (_openBlock(), _createElementBlock("div", null, [
     ? ? ?_createElementVNode("button", {
     ? ? ? ?onClick: $event => (show=!show)
     ? ?  }, "show", 8 /* PROPS */, ["onClick"]),
     ? ? ?show
     ? ? ? ?? (_openBlock(), _createElementBlock("div", { key: 0 }, [
     ? ? ? ? ?  (_openBlock(true), _createElementBlock(_Fragment, null, _renderList(3, (i) => {
     ? ? ? ? ? ? ?return (_openBlock(), _createElementBlock("div", null, [
     ? ? ? ? ? ? ? ?_createElementVNode("span", null, "12"),
     ? ? ? ? ? ? ? ?_createElementVNode("span", null, "34")
     ? ? ? ? ? ?  ]))
     ? ? ? ? ?  }), 256 /* UNKEYED_FRAGMENT */))
     ? ? ? ?  ]))
     ? ? ?  : _createCommentVNode("v-if", true)
     ?  ]))
      }
    }

可以看到關(guān)鍵在于每次都會(huì)創(chuàng)建一個(gè)新的數(shù)組,這樣卸載之后,這個(gè)數(shù)組能被GC,自然就不會(huì)存在對(duì)el的引用,不會(huì)產(chǎn)生游離的節(jié)點(diǎn),自然就不會(huì)發(fā)生內(nèi)存泄漏。

所以,編寫(xiě)一個(gè)插件,對(duì)element的圖標(biāo)組件庫(kù)進(jìn)行改造。這里以vite為例。

import { defineConfig } from "vite";
import vue from "@vitejs/plugin-vue";
import fixHoistStatic from "./plugins/fix-hoist-static";
export default defineConfig(({ command, mode }) => {
 ?return {
 ? ?optimizeDeps: {
 ? ? ?exclude: ["@element-plus/icons-vue"],
 ?  },
 ? ?plugins: [
 ? ? ?vue(),
 ? ? ?fixHoistStatic(),
 ?  ],
  };
});
    // plugins/fix-hoist-static
    const reg = /(_createElementBlock\d*("svg", _hoisted_\d+, )(_hoisted_\d+)/g;
    export default () => ({
     ?name: "fix_hoistStatic",
     ?transform(code, id) {
     ? ?if (id.includes("@element-plus/icons-vue/dist/index.js")) {
     ? ? ?code = code.replace(reg, "$1[...$2]");
     ? ? ?return code;
     ?  }
      },
    });

這里利用正則將數(shù)組替換成解構(gòu)的數(shù)組。

因?yàn)関ite會(huì)進(jìn)行依賴預(yù)構(gòu)建,所以開(kāi)發(fā)階段需要添加配置排除。詳情查看

編譯后

 // src/components/add-location.vue
    var _hoisted_1 = {
     ?viewBox: "0 0 1024 1024",
     ?xmlns: "http://www.w3.org/2000/svg"
    }, _hoisted_2 = /* @__PURE__ */ _createElementVNode("path", {
     ?fill: "currentColor",
     ?d: "M288 896h448q32 0 32 32t-32 32H288q-32 0-32-32t32-32z"
    }, null, -1), _hoisted_3 = /* @__PURE__ */ _createElementVNode("path", {
     ?fill: "currentColor",
     ?d: "M800 416a288 288 0 1 0-576 0c0 118.144 94.528 272.128 288 456.576C705.472 688.128 800 534.144 800 416zM512 960C277.312 746.688 160 565.312 160 416a352 352 0 0 1 704 0c0 149.312-117.312 330.688-352 544z"
    }, null, -1), _hoisted_4 = /* @__PURE__ */ _createElementVNode("path", {
     ?fill: "currentColor",
     ?d: "M544 384h96a32 32 0 1 1 0 64h-96v96a32 32 0 0 1-64 0v-96h-96a32 32 0 0 1 0-64h96v-96a32 32 0 0 1 64 0v96z"
    }, null, -1), _hoisted_5 = [
     ?_hoisted_2,
     ?_hoisted_3,
     ?_hoisted_4
    ];
    function _sfc_render(_ctx, _cache, $props, $setup, $data, $options) {
     ?return _openBlock(), _createElementBlock("svg", _hoisted_1, [..._hoisted_5]);
    }

打開(kāi)控制臺(tái)進(jìn)行測(cè)試,完美解決內(nèi)存泄漏!

以上就是解決vue3中內(nèi)存泄漏的問(wèn)題的詳細(xì)內(nèi)容,更多關(guān)于vue3內(nèi)存泄漏的資料請(qǐng)關(guān)注腳本之家其它相關(guān)文章!

相關(guān)文章

  • JS 實(shí)現(xiàn)獲取對(duì)象屬性個(gè)數(shù)的方法小結(jié)

    JS 實(shí)現(xiàn)獲取對(duì)象屬性個(gè)數(shù)的方法小結(jié)

    這篇文章主要介紹了JS 實(shí)現(xiàn)獲取對(duì)象屬性個(gè)數(shù)的方法,結(jié)合實(shí)例形式總結(jié)分析了JS 獲取對(duì)象屬性個(gè)數(shù)的三種常用方法,需要的朋友可以參考下
    2023-05-05
  • vue3:setup語(yǔ)法糖使用教程

    vue3:setup語(yǔ)法糖使用教程

    <script setup>是在單文件組件中使用Composition API的編譯時(shí)語(yǔ)法糖,下面這篇文章主要給大家介紹了關(guān)于vue3:setup語(yǔ)法糖使用的相關(guān)資料,需要的朋友可以參考下
    2022-08-08
  • Vue Router之router.push和router.resolve頁(yè)面跳轉(zhuǎn)方式

    Vue Router之router.push和router.resolve頁(yè)面跳轉(zhuǎn)方式

    這篇文章主要介紹了Vue Router之router.push和router.resolve頁(yè)面跳轉(zhuǎn)方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2025-04-04
  • vue3中的useAttrs和props的區(qū)別解析

    vue3中的useAttrs和props的區(qū)別解析

    在vue3中,?提供了一個(gè)?useAttrs?的方法它接收到的參數(shù)一?prop中可以接收到的數(shù)據(jù)是基本一樣的如果我們想自已寫(xiě)一個(gè)組件,?把?elementPlus?中的期中一個(gè)組件封裝一下,這篇文章主要介紹了vue3中的useAttrs和props的區(qū)別,需要的朋友可以參考下
    2023-09-09
  • vue+jsplumb實(shí)現(xiàn)工作流程圖的項(xiàng)目實(shí)踐

    vue+jsplumb實(shí)現(xiàn)工作流程圖的項(xiàng)目實(shí)踐

    本文主要介紹了vue+jsplumb實(shí)現(xiàn)工作流程圖的項(xiàng)目實(shí)踐,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2022-04-04
  • vue中axios防止多次觸發(fā)終止多次請(qǐng)求的示例代碼(防抖)

    vue中axios防止多次觸發(fā)終止多次請(qǐng)求的示例代碼(防抖)

    這篇文章主要介紹了vue中axios防止多次觸發(fā)終止多次請(qǐng)求的實(shí)現(xiàn)方法(防抖),本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2020-02-02
  • Vue項(xiàng)目部署在Spring Boot出現(xiàn)頁(yè)面空白問(wèn)題的解決方案

    Vue項(xiàng)目部署在Spring Boot出現(xiàn)頁(yè)面空白問(wèn)題的解決方案

    這篇文章主要介紹了Vue項(xiàng)目部署在Spring Boot出現(xiàn)頁(yè)面空白問(wèn)題的解決方案,非常不錯(cuò),具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2018-11-11
  • Vue中v-form標(biāo)簽里的:rules作用及定義方法

    Vue中v-form標(biāo)簽里的:rules作用及定義方法

    文章介紹了在Vue項(xiàng)目中使用ElementUI或ElementPlus組件庫(kù)時(shí),如何通過(guò)`el-form`標(biāo)簽的`:rules`屬性進(jìn)行表單驗(yàn)證,`:rules`屬性用于定義表單項(xiàng)的驗(yàn)證規(guī)則,包括必填項(xiàng)、格式校驗(yàn)、長(zhǎng)度限制等,文章還展示了如何定義基本驗(yàn)證規(guī)則和自定義驗(yàn)證函數(shù),感興趣的朋友一起看看吧
    2025-03-03
  • VUE使用router.push實(shí)現(xiàn)頁(yè)面跳轉(zhuǎn)和傳參方式

    VUE使用router.push實(shí)現(xiàn)頁(yè)面跳轉(zhuǎn)和傳參方式

    這篇文章主要介紹了VUE使用router.push實(shí)現(xiàn)頁(yè)面跳轉(zhuǎn)和傳參方式,具有很好的參考價(jià)值,希望對(duì)大家有所幫助,如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2024-01-01
  • vue3?+?antv/x6實(shí)現(xiàn)流程圖的全過(guò)程

    vue3?+?antv/x6實(shí)現(xiàn)流程圖的全過(guò)程

    隨著互聯(lián)網(wǎng)的發(fā)展,越來(lái)越多的應(yīng)用需要實(shí)現(xiàn)流程圖的制作,如工作流程圖、電路圖等,文中通過(guò)代碼以及圖文將實(shí)現(xiàn)的過(guò)程介紹的非常詳細(xì),對(duì)大家學(xué)習(xí)或者工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2024-06-06

最新評(píng)論