91超碰碰碰碰久久久久久综合_超碰av人澡人澡人澡人澡人掠_国产黄大片在线观看画质优化_txt小说免费全本

溫馨提示×

溫馨提示×

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

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

vue中keepalive的內存問題怎么解決

發布時間:2023-03-13 16:36:59 來源:億速云 閱讀:393 作者:iii 欄目:編程語言

本篇內容介紹了“vue中keepalive的內存問題怎么解決”的有關知識,在實際案例的操作過程中,不少人都會遇到這樣的困境,接下來就讓小編帶領大家學習一下如何處理這些情況吧!希望大家仔細閱讀,能夠學有所成!

1.起因

最近發現公司項目偶發性發生 奔潰現象。

vue中keepalive的內存問題怎么解決

剛開始以為是代碼寫了一些死循環,檢查完并未發現。

后面通過 performance 檢查 發現內存飚到了1個多G, 可能是內存沒有正常的回收,而項目是從多頁面整合到單頁面后發生的,單頁面使用的是keepalive 內部頁簽實現。所以初步推斷可能是內存擠爆了。

定位原因

通過performance -> memory 看到當前內存使用情況,

  • 通過瘋狂的打開內部頁簽+關閉,發現內存已經達到驚人的2g,相關的操作已經開始無法響應,頁面卡頓甚至白屏

vue中keepalive的內存問題怎么解決

  • 通過命令可以看到 可以使用2g,已使用2g, 封頂4g

vue中keepalive的內存問題怎么解決

  • 神奇的是2g后,等一會依然可以繼續操作,繼續擼,內存已經懟到4g了

vue中keepalive的內存問題怎么解決

  • 這時候已經芭比Q了,控制臺console.log回車后,都沒空間執行和輸出了

vue中keepalive的內存問題怎么解決

2. 定位問題

1.還原場景

由于內部系統代碼復雜并有交叉邏輯和隱性的內存泄露的代碼。對比了公司其他內置多頁簽緩存項目,也存在類似問題。所以需要搭建一個純凈的環境一步步從底層分析。 首先還原項目使用的版本環境。

2.寫個demo

先寫個demo重現問題。使用vue-cli創建項目對應版本 vue2.6.12, vue-router3.6.4

main.js

import Vue from 'vue'
import App from './App.vue'
import router from './router'
Vue.config.productionTip = false

new Vue({
  render: h => h(App),
  router
}).$mount('#app')

App.vue

<template>
    <div>
        <div>keep-alive includeList:{{indexNameList}}</div>
        <button @click="routerAdd()">新增(enter)</button> <button @click="routerDel()">刪除(esc)</button> <button @click="gc()">強制垃圾回收(backspace)</button> <span  >內存已使用<b id="usedJSHeapSize"></b></span>
        <div class="keepaliveBox">
            <keep-alive :include="indexNameList">
                <router-view />
            </keep-alive>
        </div>
        <div class="barBox">
            <div class="box" v-for="(index) in indexList" :key="index">
                <span @click="routerClick(index)">a{{index}}</span>
                <button @click="routerDel(index)" title="刪除(esc)">x</button>
            </div>
        </div>
    </div>
</template>

<script>
export default {
    name: "App",
    data() {
        return {
            indexList: [],
            usedJSHeapSize: ''
        }
    },
    mounted() {
        const usedJSHeapSize = document.getElementById("usedJSHeapSize")
        window.setInterval(() => {
            usedJSHeapSize.innerHTML = (performance.memory.usedJSHeapSize / 1000 / 1000).toFixed(2) + "mb"
        }, 1000)
        // 新增快捷鍵模擬用戶實際 快速打開關閉場景
        document.onkeydown = (event) => {
            event = event || window.event;
            if (event.keyCode == 13) {//新增 
                this.routerAdd()
            } else if (event.keyCode == 27) {  //刪除  
                this.routerDel() 
            } else if (event.keyCode == 8) {  //垃圾回收  
                this.gc() 
            }
        };
    },
    computed: {
        indexNameList() {
            const res = ['index']//
            this.indexList.forEach(index => {
                res.push(`a${index}`)
            }) 
            return res
        }
    },
    methods: {
        routerAdd() {
            let index = 0
            this.indexList.length > 0 && (index = Math.max(...this.indexList)) 
            index++ 
            this.indexList.push(index)
            this.$router.$append(index)
            this.$router.$push(index)
        },
        routerDel(index) { 
            if (this.indexList.length == 0) return
            if(!index) {
                index = Math.max(...this.indexList)
            }  
               //每次刪除都先跳回到首頁, 確保刪除的view 不是正在顯示的view
            if (this.$route.path !== '/index') { 
                this.$router.push('/index') 
            }
            let delIndex = this.indexList.findIndex((item) => item == index)
            this.$delete(this.indexList, delIndex)
            //延遲執行,加到下一個宏任務
            // setTimeout(() => {
            //     this.gc() 
            // }, 100);
        },
        routerClick(index) {
            this.$router.$push(index)
        },
        gc(){
            //強制垃圾回收 需要在瀏覽器啟動設置 --js-flags="--expose-gc",并且不打開控制臺,沒有效果
            window.gc && window.gc()
        }, 
    }
};
</script>

<style scoped>
.keepaliveBox {
    border: 1px solid red;
    padding: 3px;
}

.barBox {
    display: flex;
    flex-wrap: wrap;
}

.box {
    margin: 2px;
    min-width: 70px;
}

.box>span {
    padding: 0 2px;
    background: black;
    color: #fff;
}
</style>

view/index.vue

<template>
    <div>首頁</div>
</template>
<script>
export default {
    name:'index',
}
</script>

view/a.vue

<template>
    <div>組件view<input v-model="myname"/> </div>
</template>
<script>
export default {
    name:'A',
    data(){
        return {
            a:new Array(20000000).fill(1),//大概80mb
            myname:""
        }
    },
    mounted(){  
        this.myname = this.$route.query.name
    }
}
</script>

router/index.js

import Vue from 'vue'
import Router from 'vue-router'
import a from '../view/a.vue'
Vue.use(Router)

const router = new Router({
    mode: 'hash', 
     routes: [
        {
            path: '/',
            redirect: '/index'
        },
        {
            path: '/index',
            component: () => import('../view/index.vue')
        }
    ]
})

//動態添加路由
router.$append = (index) => { 
    router.addRoute(`a${index}`,{
        path: `/a${index}`,
        component:  {
            ...a,
            name: `a${index}`
        },
    })  
}

router.$push = (index) => { 
        router.push({
            path:`/a${index}`,
            query:{
                name:`a${index}`
            }
        })
} 
export default  router

demo效果

vue中keepalive的內存問題怎么解決

  • 點擊新增會創建一個80mb的組件,可以看到新增4個組件,keepalive占用大概330mb左右,(實時監控和performance接口計算,內存診斷報告會有偏差)

  • 點擊刪除會默認移除最后一個元素,也可以通過元素上的x來刪除,每次刪除都先跳回到首頁, 確保刪除的view 不是正在顯示的view。

3.重現問題

1.當創建4個組件后,刪除最后一個a4時候,同時立即回收內存,內存并沒有釋放。依然是328mb。

2.但是當再刪除多一個a3的時候 居然又釋放的80,讓人更加疑惑。

3.這還不算,如果我新增4個,然后先刪除最前面的居然能實時的釋放

好家伙,vue官方api也這么不靠譜嗎?對于程序員來說,不確定問題比實實在在的錯誤都要難得多。

趕緊上官網看了下,發現vue2 從2.6.12 到 2.7.10 之間 在 2.6.13 修復了 關于keepalive的問題,由于2.7.10使用ts重寫了,并且引入的vue3的compositionAPI,為了穩定,只升級到 2.6的最新2.6.14。

vue中keepalive的內存問題怎么解決

vue中keepalive的內存問題怎么解決

結果問題依然存在,于是又試了下2.7.10,結果還是一樣的現象。

4.分析

4.1全局引用是否正常釋放

在vue里,只有一個子節點App,再里面就是 keepalive 和 a1,a2,a3,a4 ,這5個是平級的關系

vue中keepalive的內存問題怎么解決

vue中keepalive的內存問題怎么解決

可以看到當刪除a4的時候App里面的子節點只剩下keepalive 和 a1,a2,a3, 4個元素,所以這里沒有內存問題。

vue中keepalive的內存問題怎么解決

4.2keepalive 的cache是否正常釋放

可以看到cache集合里面已經移除a4的緩存信息

vue中keepalive的內存問題怎么解決

4.3挨個組件檢查引用關系

  • 通過診斷報告搜索vuecomponent,可以看到有7個vuecomponent的組件(keepalive 和 App.vue  + index.vue +  自定義創建的4個動態a組件)

vue中keepalive的內存問題怎么解決

  • 通過鼠標移動到對應的vueVomponent上會顯示對應的實例,如下圖的a4實例

vue中keepalive的內存問題怎么解決

  • 現在我嘗試刪除a4,再生成報告2,在報告2中我們還是能看到a4,這時候內存就沒有正常釋放了

vue中keepalive的內存問題怎么解決

  • 并且發引用關系已經變成11層,與其他的5層不一樣。點擊改a4后,下面Object頁簽會展開顯示正在引用他的對象

vue中keepalive的內存問題怎么解決

  • 鼠標移動到$vnode上看,發現居然是被a3組件引用了,這是為什么?

vue中keepalive的內存問題怎么解決

根據一層層關系最后發現

 a3組件.$vnode.parent.componentOptions.children[0] 引用著 a4

導致a4 無法正常釋放

基于這個點,查詢了前面a2,a3 也存在引用的關系,a1 正常無人引用它。

a2組件.$vnode.parent.componentOptions.children[0] 引用著 a3
a1組件.$vnode.parent.componentOptions.children[0] 引用著 a2
a1組件 正常,沒被引用
  • 這里看到看出 a3組件.$vnode.parent 其實就是keepalive對象。

  • 由于keepalive不參與渲染,但是每次組件渲染都會傳入componentOptions,componentOptions里面包含了當前的keepalive的信息,keepalive又包裹了上一次第一個渲染的子節點。

5.結論

  • 當加載組件a1,a1對應的keepalive的componentOptions的children[0]信息也是a1。

  • 當加載組件a2,a2對應的keepalive的componentOptions的children[0]信息也是a2,但是這時候上面的a1對應的keepalive由于是同一個引用,導致a1對應的keepalive的componentOptions信息也是a2。

  • 當加載組件a3,a3對應的keepalive的componentOptions的children[0]信息也是a3,導致a2對應的keepalive的componentOptions信息也是a3。

  • 當加載組件a4,a4對應的keepalive的componentOptions的children[0]信息也是a4,導致a3對應的keepalive的componentOptions信息也是a4。

vue中keepalive的內存問題怎么解決

上面描述的各個組件的引用關系,a1-> a2 -> a3 -> a4 。 這也解釋了為什么刪除a1內存能夠立即釋放,同理繼續刪除a2 也是能正常釋放。

但是如果先刪除a4,由于a3引用著他所以不能釋放a4。

3. 修復問題

1.思路

根據上面的關系我們指導,所有問題都是vue實例的時候關聯的keepalive引用了別的組件,我們只需要把keepalive上面componentOptions的children[0] 引用的關系切斷就ok了。這時候我們可以從vue的keepalive源碼入手調整。

2.構建可以定位具體源碼的環境

該項目使用的是vue 的cdn引入,所以只需要重新上傳一份支持sourcemap的并且沒有被混淆的vue庫即可。 通過--sourcemap 命令參數 生產支持源碼映射的代碼,以相對路徑的方式上傳的對應的cdn地址。參考地址

git clone --branch 2.6.14  https://github.com/vuejs/vue.git //拉取代碼

修改package.json,添加 --sourcemap

"dev": "rollup -w -c scripts/config.js --sourcemap --environment TARGET:webfull-dev",

本地運行

npm run dev

vue中keepalive的內存問題怎么解決

通過live server啟動服務

vue中keepalive的內存問題怎么解決

這樣每次修改源碼,都會實時發布到dist下的vue.js 我們就可以實時調試了訪問地址: 訪問地址:http://127.0.0.1:5500/dist/vue.js

3.改造現有項目成cdn

vue.config.js

module.exports = {
    chainWebpack: config => { 
      config.externals({
        vue: "Vue", 
      }); 
    },
    configureWebpack: {
      devtool: "eval-source-map"
    },
    lintOnSave: false
  };

public/index.html

<!DOCTYPE html>
<html lang="">
  <head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width,initial-scale=1.0">
    <link rel="icon" href="<%= BASE_URL %>favicon.ico">
    <title><%= htmlWebpackPlugin.options.title %></title> 
     <!-- 這里是本地的vue源碼 -->
    <script src="http://127.0.0.1:5500/dist/vue.js"></script>
  </head>
  <body>
    <noscript>
    </noscript>
    <div id="app"></div>
    <!-- built files will be auto injected -->
  </body>
</html>

這里cdn改成生成自己生成的vue sourcemap 實時地址。

4.調試代碼

在開發者工具里,crtl+p 打開源碼搜索框,輸入keepalive,找到對應的源碼。

vue中keepalive的內存問題怎么解決

在render方法里打上斷點,可以發現每當路由發送變化,keepalive的render方法都會重新渲染

vue中keepalive的內存問題怎么解決

打開源碼

/* @flow */

import { isRegExp, remove } from 'shared/util'
import { getFirstComponentChild } from 'core/vdom/helpers/index'

type CacheEntry = {
  name: ?string;
  tag: ?string;
  componentInstance: Component;
};

type CacheEntryMap = { [key: string]: ?CacheEntry };

function getComponentName (opts: ?VNodeComponentOptions): ?string {
  return opts && (opts.Ctor.options.name || opts.tag)
}

function matches (pattern: string | RegExp | Array<string>, name: string): boolean {
  if (Array.isArray(pattern)) {
    return pattern.indexOf(name) > -1
  } else if (typeof pattern === 'string') {
    return pattern.split(',').indexOf(name) > -1
  } else if (isRegExp(pattern)) {
    return pattern.test(name)
  }
  /* istanbul ignore next */
  return false
}

function pruneCache (keepAliveInstance: any, filter: Function) {
  const { cache, keys, _vnode } = keepAliveInstance
  for (const key in cache) {
    const entry: ?CacheEntry = cache[key]
    if (entry) {
      const name: ?string = entry.name
      if (name && !filter(name)) {
        pruneCacheEntry(cache, key, keys, _vnode)
      }
    }
  }
}

function pruneCacheEntry (
  cache: CacheEntryMap,
  key: string,
  keys: Array<string>,
  current?: VNode
) {
  const entry: ?CacheEntry = cache[key]
  if (entry && (!current || entry.tag !== current.tag)) {
    entry.componentInstance.$destroy()
  }
  cache[key] = null
  remove(keys, key)
}

const patternTypes: Array<Function> = [String, RegExp, Array]

export default {
  name: 'keep-alive',
  abstract: true,

  props: {
    include: patternTypes,
    exclude: patternTypes,
    max: [String, Number]
  },

  methods: {
    cacheVNode() {
      const { cache, keys, vnodeToCache, keyToCache } = this
      if (vnodeToCache) {
        const { tag, componentInstance, componentOptions } = vnodeToCache
        cache[keyToCache] = {
          name: getComponentName(componentOptions),
          tag,
          componentInstance,
        }
        keys.push(keyToCache)
        // prune oldest entry
        if (this.max && keys.length > parseInt(this.max)) {
          pruneCacheEntry(cache, keys[0], keys, this._vnode)
        }
        this.vnodeToCache = null
      }
    }
  },

  created () {
    this.cache = Object.create(null)
    this.keys = []
  },

  destroyed () {
    for (const key in this.cache) {
      pruneCacheEntry(this.cache, key, this.keys)
    }
  },

  mounted () {
    this.cacheVNode()
    this.$watch('include', val => {
      pruneCache(this, name => matches(val, name))
    })
    this.$watch('exclude', val => {
      pruneCache(this, name => !matches(val, name))
    })
  },

  updated () {
    this.cacheVNode()
  },

  render () {
    const slot = this.$slots.default
    const vnode: VNode = getFirstComponentChild(slot)
    const componentOptions: ?VNodeComponentOptions = vnode && vnode.componentOptions
    if (componentOptions) {
      // check pattern
      const name: ?string = getComponentName(componentOptions)
      const { include, exclude } = this
      if (
        // not included
        (include && (!name || !matches(include, name))) ||
        // excluded
        (exclude && name && matches(exclude, name))
      ) {
        return vnode
      }

      const { cache, keys } = this
      const key: ?string = vnode.key == null
        // same constructor may get registered as different local components
        // so cid alone is not enough (#3269)
        ? componentOptions.Ctor.cid + (componentOptions.tag ? `::${componentOptions.tag}` : '')
        : vnode.key
      if (cache[key]) {
        vnode.componentInstance = cache[key].componentInstance
        // make current key freshest
        remove(keys, key)
        keys.push(key)
      } else {
        // delay setting the cache until update
        this.vnodeToCache = vnode
        this.keyToCache = key
      }

      vnode.data.keepAlive = true
    }
    return vnode || (slot && slot[0])
  }
}

這里包含了整個keepalive的所有邏輯,

  • 剛開始也以為是LRU的設置問題,測試后發現keepalive的數組都是能正常釋放。

  • 懷疑是max最大長度限制,解決也是正常。 確保keepalive內部能正常釋放引用后,就要想如何修復這個bug,關鍵就是把children設置為空

組件.$vnode.parent.componentOptions.children = []

最合適的位置就在每次render的時候都重置一下所有錯誤的引用即可

代碼如下,把錯誤引用的children設置為空

  render () {
    const slot = this.$slots.default
    const vnode: VNode = getFirstComponentChild(slot) 
    
    //修復緩存列表問題
    for (const key in this.cache) {
      const entry: ?CacheEntry = this.cache[key]
      if (entry && vnode && entry.tag && entry.tag !== vnode.tag ) { //如果當前的緩存對象不為空 并且 緩存與當前加載不一樣
        entry.componentInstance.$vnode.parent.componentOptions.children = []
      }
    }
   .....
}

懷著喜悅的心情以為一切ok,運行后發現,a4依然被保留著。NND

vue中keepalive的內存問題怎么解決

點擊后發現,是a4的dom已經沒在顯示,dom處于游離detach狀態,看看是誰還引用著。好家伙,又是父節點keepalive的引用著,這次是elm。

vue中keepalive的內存問題怎么解決

于是在keepalive源碼的render方法加入

entry.componentInstance.$vnode.parent.elm = null

登錄后復制

整體代碼如下

  render () {
    const slot = this.$slots.default
    const vnode: VNode = getFirstComponentChild(slot) 
    
    //修復緩存列表問題
    for (const key in this.cache) {
      const entry: ?CacheEntry = this.cache[key]
      if (entry && vnode && entry.tag && entry.tag !== vnode.tag ) { //如果當前的緩存對象不為空 并且 緩存與當前加載不一樣
        entry.componentInstance.$vnode.parent.componentOptions.children = []
        entry.componentInstance.$vnode.parent.elm = null
      }
    }
   .....
}

再次懷著喜悅的心情運行,發現這次靠譜了。

“vue中keepalive的內存問題怎么解決”的內容就介紹到這里了,感謝大家的閱讀。如果想了解更多行業相關的知識可以關注億速云網站,小編將為大家輸出更多高質量的實用文章!

向AI問一下細節

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

AI

常宁市| 玛沁县| 大渡口区| 永康市| 南昌市| 电白县| 元谋县| 兴宁市| 三亚市| 新田县| 托克托县| 台东市| 高州市| 扶余县| 呼玛县| 罗江县| 油尖旺区| 扶风县| 瓦房店市| 咸丰县| 丰镇市| 澄江县| 华池县| 盘山县| 临澧县| 达州市| 茂名市| 郴州市| 东阿县| 沂南县| 鹰潭市| 上蔡县| 绥芬河市| 辽宁省| 磴口县| 涿鹿县| 威信县| 焦作市| 桃园县| 那坡县| 泽州县|