最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

vue使用中的內(nèi)存泄漏【推薦】

 更新時(shí)間:2018年07月10日 15:56:42   作者:李銀城  
內(nèi)存泄露是指new了一塊內(nèi)存,但無(wú)法被釋放或者被垃圾回收。這篇文章主要介紹了vue使用中的內(nèi)存泄漏,需要的朋友可以參考下

今天看到一篇關(guān)于js使用中內(nèi)存泄露的文章,以及chrom瀏覽器查看內(nèi)存泄漏的方法,決定留著。本文只截取了我認(rèn)為比較重要的部分,喜歡原文的小伙伴,請(qǐng)點(diǎn)擊文章下方的原文鏈接。

什么是內(nèi)存泄露??jī)?nèi)存泄露是指new了一塊內(nèi)存,但無(wú)法被釋放或者被垃圾回收。new了一個(gè)對(duì)象之后,它申請(qǐng)占用了一塊堆內(nèi)存,當(dāng)把這個(gè)對(duì)象指針置為null時(shí)或者離開(kāi)作用域?qū)е卤讳N(xiāo)毀,那么這塊內(nèi)存沒(méi)有人引用它了在JS里面就會(huì)被自動(dòng)垃圾回收。但是如果這個(gè)對(duì)象指針沒(méi)有被置為null,且代碼里面沒(méi)辦法再獲取到這個(gè)對(duì)象指針了,就會(huì)導(dǎo)致無(wú)法釋放掉它指向的內(nèi)存,也就是說(shuō)發(fā)生了內(nèi)存泄露。為什么代碼里面會(huì)拿不到這個(gè)對(duì)象指針了呢,舉一個(gè)例子:

// module date.js
let date = null;
export default {
 init () {
  date = new Date();
 }
}
// main.js
import date from 'date.js';
date.init();

在main.js初始化了date之后,date這個(gè)變量就一會(huì)直存在了,直到你把頁(yè)面關(guān)了,因?yàn)閐ate的引用是在另一個(gè)module里面,可以理解為模塊就是一個(gè)閉包對(duì)外是不可見(jiàn)的。所以如果你是希望這個(gè)date對(duì)象一直存在、需要一直使用的話(huà),那么沒(méi)有問(wèn)題,但是如果想用一次就不用了那就會(huì)有問(wèn)題,這個(gè)對(duì)象一直在內(nèi)存里面沒(méi)有被釋放就發(fā)生了內(nèi)存泄露。

另一種比較隱蔽并且很常見(jiàn)的內(nèi)存泄露是事件綁定,形成了一個(gè)閉包,導(dǎo)致一些變量一直存在。如下例子所示:

// 一個(gè)圖片懶惰加載引擎示例
class ImageLazyLoader {
 constructor ($photoList) {
  $(window).on('scroll', () => {
   this.showImage($photoList);
  });
 }
 showImage ($photoList) {
  $photoList.each(img => {
   // 通過(guò)位置判斷圖片滑出來(lái)了就加載
   img.src = $(img).attr('data-src');
  });
 }
}
// 點(diǎn)擊分頁(yè)的時(shí)候就初始化一個(gè)圖片懶惰加載的
$('.page').on('click', function () {
 new ImageLazyLoader($('img.photo'));
});

這是一個(gè)圖片懶惰加載的模型,每次點(diǎn)分頁(yè)的時(shí)候就會(huì)清掉上一頁(yè)的數(shù)據(jù)更新為當(dāng)前頁(yè)的DOM,并重新初始化一個(gè)懶惰加載的引擎。它里面監(jiān)聽(tīng)了scroll事件,對(duì)傳進(jìn)來(lái)的圖片列表的DOM進(jìn)行處理。每點(diǎn)一次分頁(yè)就會(huì)重新new一個(gè),這里就發(fā)生了內(nèi)存泄露,主要是以下3行代碼導(dǎo)致的:

$(window).on('scroll', () => {
 this.showImage($photoList);
});

因?yàn)檫@里的事件綁定形成了一個(gè)閉包,this/$photoList這兩個(gè)變量一直沒(méi)有被釋放,this是指向ImageLazyLoader的實(shí)例,而$photoList是指向DOM結(jié)點(diǎn),當(dāng)清除掉上一頁(yè)的數(shù)據(jù)的時(shí)候,相關(guān)DOM結(jié)點(diǎn)已經(jīng)從DOM樹(shù)分離出來(lái)了,但是仍然還有一個(gè)$photoList指向它們,導(dǎo)致這些DOM結(jié)點(diǎn)無(wú)法被垃圾回收一直在內(nèi)存里面,就發(fā)生了內(nèi)存泄露。由于this變量也被閉包困住了沒(méi)有被釋放,所以還有一個(gè)ImageLazyLoader的實(shí)例發(fā)生內(nèi)存泄露。

這個(gè)的解決方法比較簡(jiǎn)單,就是銷(xiāo)毀實(shí)例的時(shí)候把綁定的事件off掉,如下代碼所示:

class ImageLazyLoader {
 constructor ($photoList) {
  this.scrollShow = () => {
   this.showImage($photoList);
  };
  $(window).on('scroll', this.scrollShow);
 }
 // 新增一個(gè)事件解綁       
 clear () {      
  $(window).off('scroll', this.scrollShow);
 }
 showImage ($photoList) {
  $photoList.each(img => {
   // 通過(guò)位置判斷圖片滑出來(lái)了就加載
   img.src = $(img).attr('data-src');
  });
  // 判斷如果圖片已全部顯示,就把事件解綁了
  if (this.allShown) {
   this.clear();
  }
 }
}
// 點(diǎn)擊分頁(yè)的時(shí)候就初始化一個(gè)圖片懶惰加載的
let lazyLoader = null;
$('.page').on('click', function () {
 lazyLoader && (lazyLoader.clear());
 lazyLoader = new ImageLazyLoader($('img.photo'));
});

在每次實(shí)例化一個(gè)ImageLazyLoader之前把先把上一個(gè)實(shí)例clear掉,clear里面進(jìn)行解綁,由于JS有構(gòu)造函數(shù)但是沒(méi)有解構(gòu)函數(shù),所以需要自己寫(xiě)一個(gè)clear,在外面手動(dòng)調(diào)一下clear。同時(shí)在事件的執(zhí)行過(guò)程的合適時(shí)機(jī)自動(dòng)把事件給解綁了,上面是判斷如果所有的圖片都展示出來(lái)了那么就沒(méi)必要監(jiān)聽(tīng)scroll事件了直接解綁了。這樣就能解決內(nèi)存泄露的問(wèn)題了,能夠觸發(fā)自動(dòng)垃圾回收。

為什么把事件解綁了,就不會(huì)有閉包引用了呢?因?yàn)镴S引擎檢測(cè)到那個(gè)閉包沒(méi)用了,就把那個(gè)閉包銷(xiāo)毀了,那么閉包引用的外部變量也自然會(huì)被置空。

好了,基礎(chǔ)知識(shí)就講解到這里,現(xiàn)在用Chrome devtools的內(nèi)存檢測(cè)工具來(lái)實(shí)際操作一遍,方便發(fā)現(xiàn)頁(yè)面的一些內(nèi)存泄露行為。為了避免裝給瀏覽器裝的一些插件造成影響,使用Chome的隱身模式頁(yè)面,它會(huì)把所有的插件都給禁掉。

然后打開(kāi)devtools,切到Memory的tab,選中Heap snapshot,如下所示:

 

什么叫heap snapshot呢?翻譯一下就是堆快照,給當(dāng)前內(nèi)存堆拍一張照片。因?yàn)閯?dòng)態(tài)申請(qǐng)的內(nèi)存都是在堆里面的,而局部變量是在內(nèi)存棧里面,是由操作系統(tǒng)分配管理的是不會(huì)內(nèi)存泄露了。所以關(guān)心堆的情況就好了。

然后做一些增刪改DOM的操作,如:

(1)彈一個(gè)框,然后把彈框給關(guān)了

(2)單頁(yè)面的點(diǎn)擊跳轉(zhuǎn)到另一個(gè)路由,然后再點(diǎn)后退返回

(3)點(diǎn)擊分頁(yè)觸發(fā)動(dòng)態(tài)改DOM

就是先增加DOM,然后把這些DOM給刪了,看一下這些被刪除的DOM是否還有對(duì)象引用它們。

這里我是第2種方式的場(chǎng)景,檢測(cè)單頁(yè)面應(yīng)用的某個(gè)路由頁(yè)面是否存在內(nèi)存泄露。先打開(kāi)首頁(yè),點(diǎn)到另一個(gè)頁(yè)面,再點(diǎn)后退,接著點(diǎn)一下垃圾回收的按鈕:

 

觸發(fā)垃圾回收,避免一些不必要的干擾。

然后再點(diǎn)一下拍照按鈕:

 

它就會(huì)把當(dāng)前頁(yè)面的內(nèi)存堆掃描一遍顯示出來(lái),如下圖所示:

 

然后在上面中間的Class Filter的搜索框里搜一下detached:

 

它就會(huì)顯示所有已經(jīng)分離了DOM樹(shù)的DOM結(jié)點(diǎn),重點(diǎn)關(guān)注distance值不為空的,這個(gè)distance表示距離DOM根結(jié)點(diǎn)的距離。上圖展示的這些div具體是啥呢?我們把鼠標(biāo)放上去不動(dòng)等個(gè)2s,它就會(huì)顯示這個(gè)div的DOM信息:

 

通過(guò)className等信息可以知道它就是那個(gè)要檢查的頁(yè)面的DOM節(jié)點(diǎn),在下面的Object的窗口里面依次展開(kāi)它的父結(jié)點(diǎn),可以看到它最外面的父結(jié)點(diǎn)是一個(gè)VueComponent實(shí)例:

 

下面黃色字體native_bind表示有個(gè)事件指向了它,黃色表示引用仍然生效,把鼠標(biāo)放到native_bind上面停留2秒:

它會(huì)提示你是在homework-web.vue這個(gè)文件有一個(gè)getScale函數(shù)綁定在了window上面,查看一下這個(gè)文件確實(shí)是有一個(gè)綁定:

mounted () {
 window.addEventListener('resize', this.getScale);
}

所以雖然Vue組件把DOM刪除了,但是還有個(gè)引用存在,導(dǎo)致組件實(shí)例沒(méi)有被釋放,組件里面又有一個(gè)$el指向DOM,所以DOM也沒(méi)有被釋放。

要在beforeDestroyed里面解綁的

beforeDestroyed () {
 window.removeEventListener('resize', this.getScale);
}

所以綜合上面的分析,造成內(nèi)存泄露的可能會(huì)有以下幾種情況:

(1)監(jiān)聽(tīng)在window/body等事件沒(méi)有解綁

(2)綁在EventBus的事件沒(méi)有解綁

(3)Vuex的$store watch了之后沒(méi)有unwatch

(4)模塊形成的閉包內(nèi)部變量使用完后沒(méi)有置成null

(5)使用第三方庫(kù)創(chuàng)建,沒(méi)有調(diào)用正確的銷(xiāo)毀函數(shù)

并且可以借助Chrome的內(nèi)存分析工具進(jìn)行快速排查,本文主要是用到了內(nèi)存堆快照的基本功能,讀者可以嘗試分析自己的頁(yè)面是否存在內(nèi)存泄漏,方法是做一些操作如彈個(gè)框然后關(guān)了,拍一張堆快照,搜索detached,按distance排序,把非空的節(jié)點(diǎn)展開(kāi)父級(jí),找到標(biāo)黃的字樣說(shuō)明,那些就是存在沒(méi)有釋放的引用。也就是說(shuō)這個(gè)方法主要是分析仍然存在引用的游離DOM節(jié)點(diǎn)。因?yàn)轫?yè)面的內(nèi)存泄露通常是和DOM相關(guān)的,普通的JS變量由于有垃圾回收所以一般不會(huì)有問(wèn)題,除非使用閉包把變量困住了用完了又沒(méi)有置空。

DOM相關(guān)的內(nèi)存泄露通常也是因?yàn)殚]包和事件綁定引起的。綁了(全局)事件之后,在不需要的時(shí)候需要把它解綁。當(dāng)然直接綁在div上面的可以直接把div刪了,綁在它上面的事件就自然解綁了。

相關(guān)文章

  • Vue中的生命周期介紹

    Vue中的生命周期介紹

    這篇文章介紹了Vue中的生命周期,文中通過(guò)示例代碼介紹的非常詳細(xì)。對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2022-03-03
  • vue通過(guò)點(diǎn)擊事件讀取音頻文件的方法

    vue通過(guò)點(diǎn)擊事件讀取音頻文件的方法

    最近做項(xiàng)目遇到這樣的一個(gè)需求,通過(guò)select元素來(lái)選擇音頻文件的名稱(chēng),點(diǎn)擊按鈕可以進(jìn)行試聽(tīng)。接下來(lái)通過(guò)本文給大家介紹vue項(xiàng)目中通過(guò)點(diǎn)擊事件讀取音頻文件的方法,需要的朋友可以參考下
    2018-05-05
  • Vue 3.0自定義指令的使用入門(mén)

    Vue 3.0自定義指令的使用入門(mén)

    在 Vue 的項(xiàng)目中,我們經(jīng)常會(huì)遇到 v-if、v-show、v-for 或 v-model 這些內(nèi)置指令,它們?yōu)槲覀兲峁┝瞬煌墓δ堋3耸褂眠@些內(nèi)置指令之外,Vue 也允許注冊(cè)自定義指令。接下來(lái),將使用Vue 3官方文檔自定義指令章節(jié)中使用的示例,來(lái)一步步揭開(kāi)自定義指令背后的秘密。
    2021-05-05
  • Vue入門(mén)學(xué)習(xí)筆記【基本概念、對(duì)象、過(guò)濾器、指令等】

    Vue入門(mén)學(xué)習(xí)筆記【基本概念、對(duì)象、過(guò)濾器、指令等】

    這篇文章主要介紹了Vue入門(mén)學(xué)習(xí)筆記,結(jié)合實(shí)例形式分析了vue.js的基本概念、對(duì)象、過(guò)濾器、指令等的相關(guān)原理與簡(jiǎn)單使用方法,需要的朋友可以參考下
    2019-04-04
  • vite+vue3使用@路徑報(bào)錯(cuò)處理

    vite+vue3使用@路徑報(bào)錯(cuò)處理

    本文主要介紹了vite+vue3使用@路徑報(bào)錯(cuò)處理,文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2023-11-11
  • 淺談VUE uni-app 基礎(chǔ)組件

    淺談VUE uni-app 基礎(chǔ)組件

    這篇文章主要介紹了uni-app 的基礎(chǔ)組件,本文通過(guò)實(shí)例代碼給大家介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值,需要的朋友可以參考下
    2021-10-10
  • vue調(diào)試工具vue-devtools安裝及使用方法

    vue調(diào)試工具vue-devtools安裝及使用方法

    本文主要介紹 vue的調(diào)試工具 vue-devtools 的安裝和使用,本文給大家介紹的非常詳細(xì),具有一定的參考借鑒價(jià)值,需要的朋友參考下吧
    2018-11-11
  • Pinia進(jìn)階setup函數(shù)式寫(xiě)法封裝到企業(yè)項(xiàng)目

    Pinia進(jìn)階setup函數(shù)式寫(xiě)法封裝到企業(yè)項(xiàng)目

    這篇文章主要為大家介紹了Pinia進(jìn)階setup函數(shù)式寫(xiě)法封裝到企業(yè)項(xiàng)目實(shí)例詳解,有需要的朋友可以借鑒參考下,希望能夠有所幫助,祝大家多多進(jìn)步,早日升職加薪
    2022-07-07
  • vue如何動(dòng)態(tài)修改$router參數(shù)

    vue如何動(dòng)態(tài)修改$router參數(shù)

    這篇文章主要介紹了vue如何動(dòng)態(tài)修改$router參數(shù),具有很好的參考價(jià)值,希望對(duì)大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2022-09-09
  • vue源碼中的檢測(cè)方法的實(shí)現(xiàn)

    vue源碼中的檢測(cè)方法的實(shí)現(xiàn)

    這篇文章主要介紹了vue源碼中的檢測(cè)方法的實(shí)現(xiàn),文中通過(guò)示例代碼介紹的非常詳細(xì),對(duì)大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來(lái)一起學(xué)習(xí)學(xué)習(xí)吧
    2019-09-09

最新評(píng)論

湘阴县| 宜丰县| 类乌齐县| 和平县| 陆良县| 东乌珠穆沁旗| 海城市| 招远市| 吴堡县| 乡宁县| 金湖县| 塔河县| 宿迁市| 浙江省| 巴彦淖尔市| 永仁县| 汶川县| 平舆县| 葫芦岛市| 绍兴县| 邯郸县| 托克托县| 克山县| 综艺| 无锡市| 勃利县| 古田县| 连城县| 赤城县| 贵州省| 湘潭市| 永宁县| 莱芜市| 巴塘县| 鸡泽县| 衡山县| 米泉市| 普兰店市| 韶关市| 景洪市| 高尔夫|