亚洲乱码中文字幕综合,中国熟女仑乱hd,亚洲精品乱拍国产一区二区三区,一本大道卡一卡二卡三乱码全集资源,又粗又黄又硬又爽的免费视频

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

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

vue3的內(nèi)存泄漏

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

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

解決方案2:參考本文

至于為什么靜態(tài)提升會導(dǎo)致內(nèi)存泄漏,本文將通過幾個案例的源碼分析詳細(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>

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

點擊兩次按鈕后:

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

先來看一下這個模板編譯后的結(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é)點。

掛載階段

掛載第一個div的子節(jié)點時:

此時children中兩個節(jié)點分別指向_hoisted_2 和 _hoisted_3

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

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

_hoisted_2 和 _hoisted_3一開始屬性el為null但patchFlag使HOISTED,所以會走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
    }

克隆時會將被克隆節(jié)點的el賦值給新的節(jié)點。

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

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

到此,掛載完成。

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

卸載階段

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

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

進入unmount函數(shù),會走到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ù),默認(rèn)是false。然后會遞歸調(diào)用unmount方法。

注意,此時傳入的doRemove是false。

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

此時走到unmount方法中的這個分支

 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不會發(fā)生什么。

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

if (doRemove) {
    ?remove(vnode)
}

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

處理完三個div的節(jié)點后,函數(shù)回到上一層。接著處理type是Symbol(v-fgt)的虛擬節(jié)點。而此時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()
      }
    }

會走到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)
}

從這里可以看到,會依次刪除掉3個div的真實dom。

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

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

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

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

好奇的你一定會問:還有一個游離的div和兩個游離的text節(jié)點哪里來的呢?

不要忘記了,el中也會保持對父親和兒子的引用。詳情見下圖

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

案例2

將案例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>
     ? ? ? ? ? ?<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>

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

點擊兩次按鈕后:

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

同樣查看編譯后的結(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é)點

掛載階段

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

卸載階段

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

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

進入unmount函數(shù),會走到這個分支。

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是長度為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會走這個分支

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ù),默認(rèn)是false。然后會遞歸調(diào)用unmount方法。

處理3個div時與案例一一樣,接著回到處理Framgent。

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

 if (doRemove) {
     ?remove(vnode)
    }

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

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

 if (doRemove) {
     ?remove(vnode)
    }

此時doRemove為true,進入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,此時<div v-if>這個節(jié)點從文檔中移除,那么它所包含的所有子節(jié)點也移除了。

此時卸載階段完成。

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

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

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

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

對比可以發(fā)現(xiàn),關(guān)鍵點在于方案二中處理Fragment時沒有進入到

 if (doRemove) {
     ?remove(vnode)
    }

也就沒有到

 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中的兩個虛擬節(jié)點中保存的el,其parentNode是div,而div中又保持著兄弟節(jié)點(因為沒有顯示地刪除,所以會繼續(xù)存在),即剩余的兩個div以及它的父節(jié)點即<div v-if>,而各自又保存著各自的兒子。

離的個數(shù):

Span: 3 * 2 =6

Div: 3 + 1 = 4

Text: 3*2 = 6

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

這就要提到vue處理Fragment的時候做的處理了。

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

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

Span: 3 * 2 =6

Div: 3 + 1 = 4

Text: 3*2+2 = 8

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

通過案例引出解決方案

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

好了,為什么使用element的圖標(biāo)庫會造成內(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)提升!所以只要使用了就會造成內(nèi)存泄漏!

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

一開始以為是element的鍋,經(jīng)過深層次分析,其實不是。這里只要換成任意靜態(tài)節(jié)點并開啟靜態(tài)提升都會有這個問題。

解決方案2

先來看看關(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)鍵在于每次都會創(chuàng)建一個新的數(shù)組,這樣卸載之后,這個數(shù)組能被GC,自然就不會存在對el的引用,不會產(chǎn)生游離的節(jié)點,自然就不會發(fā)生內(nèi)存泄漏。

所以,編寫一個插件,對element的圖標(biāo)組件庫進行改造。這里以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ù)組。

因為vite會進行依賴預(yù)構(gòu)建,所以開發(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]);
    }

打開控制臺進行測試,完美解決內(nèi)存泄漏!

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

相關(guān)文章

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

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

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

    vue3:setup語法糖使用教程

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

最新評論