Maison  >  Article  >  interface Web  >  Fuites de mémoire lors de l'utilisation de vue [Recommandé]_vue.js

Fuites de mémoire lors de l'utilisation de vue [Recommandé]_vue.js

无忌哥哥
无忌哥哥original
2018-07-12 14:04:311263parcourir

Une fuite de mémoire signifie qu'un nouveau morceau de mémoire ne peut pas être libéré ou récupéré. Cet article présente principalement les fuites de mémoire lors de l'utilisation de vue. Les amis dans le besoin peuvent se référer à

Qu'est-ce qu'une fuite de mémoire ? Une fuite de mémoire fait référence à un nouveau morceau de mémoire qui ne peut pas être libéré ou récupéré. Après avoir créé un objet, il alloue un morceau de mémoire de tas. Lorsque le pointeur d'objet est défini sur null ou quitte la portée et est détruit, cette mémoire sera automatiquement récupérée dans JS lorsque personne n'y fera référence. Cependant, si le pointeur d'objet n'est pas défini sur null et qu'il n'y a aucun moyen d'obtenir le pointeur d'objet dans le code, la mémoire pointée par celui-ci ne sera pas libérée, ce qui signifie qu'une fuite de mémoire se produit. Pourquoi ne pouvons-nous pas obtenir ce pointeur d'objet dans le code ? Voici un exemple :

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

Après l'initialisation de main.js, la variable date sera existe pendant un certain temps jusqu'à ce que vous fermiez la page. Parce que la référence de date se trouve dans un autre module, on peut comprendre que le module est une fermeture et est invisible pour le monde extérieur. Donc, si vous voulez que cet objet de date existe toujours et doit être utilisé tout le temps, alors il n'y a pas de problème. Cependant, si vous voulez l'utiliser une fois puis ne pas l'utiliser, il y aura un problème si cet objet l'a. été dans la mémoire et n’a pas été libéré, une fuite de mémoire se produira.

Une autre fuite de mémoire plus subtile et courante est la liaison d'événements, qui forme une fermeture et fait que certaines variables existent toujours. Comme le montre l'exemple suivant :

// 一个图片懒惰加载引擎示例
class ImageLazyLoader {
 constructor ($photoList) {
  $(window).on('scroll', () => {
   this.showImage($photoList);
  });
 }
 showImage ($photoList) {
  $photoList.each(img => {
   // 通过位置判断图片滑出来了就加载
   img.src = $(img).attr('src');
  });
 }
}
// 点击分页的时候就初始化一个图片懒惰加载的
$('.page').on('click', function () {
 new ImageLazyLoader($('img.photo'));
});

Il s'agit d'un modèle de chargement paresseux des images à chaque fois que vous cliquez sur la page. la page précédente sera effacée. Met à jour le DOM de la page actuelle et réinitialise un moteur de chargement paresseux. Il écoute l'événement de défilement et traite le DOM de la liste d'images entrantes. Chaque fois que vous cliquez sur une nouvelle page, une nouvelle page sera créée ici. Une fuite de mémoire se produit ici, principalement causée par les trois lignes de code suivantes :

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

Parce qu'ici la liaison d'événement forme une fermeture. Les deux variables this/$photoList n'ont pas été publiées. Cela pointe vers l'instance de ImageLazyLoader, et $photoList pointe vers le nœud DOM. Lorsque les données de la page précédente sont effacées, le pertinent Les nœuds DOM ont été séparés de l'arborescence DOM, mais il y a toujours une $photoList pointant vers eux. Par conséquent, ces nœuds DOM ne peuvent pas être récupérés et restent dans la mémoire, et une fuite de mémoire se produit. Puisque cette variable est également piégée par la fermeture et n'a pas été libérée, il y a également une fuite de mémoire dans une instance d'ImageLazyLoader.

La solution à ce problème est relativement simple, elle consiste à désactiver l'événement lié lors de la destruction de l'instance, comme indiqué dans le code suivant :

class ImageLazyLoader {
 constructor ($photoList) {
  this.scrollShow = () => {
   this.showImage($photoList);
  };
  $(window).on('scroll', this.scrollShow);
 }
 // 新增一个事件解绑       
 clear () {      
  $(window).off('scroll', this.scrollShow);
 }
 showImage ($photoList) {
  $photoList.each(img => {
   // 通过位置判断图片滑出来了就加载
   img.src = $(img).attr('src');
  });
  // 判断如果图片已全部显示,就把事件解绑了
  if (this.allShown) {
   this.clear();
  }
 }
}
// 点击分页的时候就初始化一个图片懒惰加载的
let lazyLoader = null;
$('.page').on('click', function () {
 lazyLoader && (lazyLoader.clear());
 lazyLoader = new ImageLazyLoader($('img.photo'));
});

Avant d'instancier un ImageLazyLoader à chaque fois, effacez l'instance précédente et dissociez-la à l'intérieur du clear Puisque JS a un constructeur mais pas de destructeur, vous devez écrire vous-même un clear et ajuster manuellement le clear à l'extérieur. Dans le même temps, l'événement est automatiquement dissocié au moment approprié pendant l'exécution de l'événement. Ce qui précède consiste à juger que si toutes les images sont affichées, il n'est pas nécessaire de surveiller l'événement de défilement et de le dissocier directement. Cela résoudra le problème des fuites de mémoire et déclenchera le garbage collection automatique.

Pourquoi n'y a-t-il pas de référence de clôture après la dissociation de l'événement ? Parce que le moteur JS détecte que la fermeture n'est plus utile, il détruit la fermeture et les variables externes référencées par la fermeture resteront naturellement vides.

D'accord, les connaissances de base ont été expliquées ici. Utilisez maintenant l'outil de détection de mémoire des outils de développement Chrome pour le faire fonctionner afin de faciliter la découverte de certaines fuites de mémoire sur la page. Afin d'éviter l'impact de certains plug-ins installés dans le navigateur, utilisez la page du mode incognito de Chome, qui désactivera tous les plug-ins.

Ensuite, ouvrez les outils de développement, passez à l'onglet Mémoire et sélectionnez Instantané du tas, comme indiqué ci-dessous :


Qu'est-ce qu'un instantané du tas ? Traduit, il s'agit d'un instantané de tas, prenant une photo du tas de mémoire actuel. Étant donné que la mémoire appliquée dynamiquement se trouve dans le tas et que les variables locales se trouvent dans la pile mémoire et sont allouées et gérées par le système d'exploitation, il n'y aura pas de fuite de mémoire. Alors soyez simplement préoccupé par la situation du tas.

Ensuite, effectuez quelques opérations d'ajout, de suppression et de modification du DOM, telles que :

(1) Faites apparaître une boîte, puis fermez la boîte

(2) Cliquez sur une seule page Passez à un autre itinéraire, puis cliquez en arrière pour revenir

(3) Cliquez sur la pagination pour déclencher un changement dynamique de DOM

C'est-à-dire d'abord ajouter des DOM, puis supprimer ces DOM Vérifiez si le DOM supprimé contient toujours des objets qui les référencent.

Ici, j'utilise la deuxième méthode pour détecter s'il y a une fuite de mémoire dans une page de routage d'une application monopage. Ouvrez d'abord la page d'accueil, cliquez sur une autre page, puis cliquez en arrière, puis cliquez sur le bouton de collecte des ordures :

pour déclencher la collecte des ordures afin d'éviter des interférences inutiles.

Cliquez ensuite sur le bouton photo :

 

它就会把当前页面的内存堆扫描一遍显示出来,如下图所示:

 

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

 

它就会显示所有已经分离了DOM树的DOM结点,重点关注distance值不为空的,这个distance表示距离DOM根结点的距离。上图展示的这些p具体是啥呢?我们把鼠标放上去不动等个2s,它就会显示这个p的DOM信息:

 

通过className等信息可以知道它就是那个要检查的页面的DOM节点,在下面的Object的窗口里面依次展开它的父结点,可以看到它最外面的父结点是一个VueComponent实例:

 

下面黄色字体native_bind表示有个事件指向了它,黄色表示引用仍然生效,把鼠标放到native_bind上面停留2秒:

它会提示你是在homework-web.vue这个文件有一个getScale函数绑定在了window上面,查看一下这个文件确实是有一个绑定:

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

所以虽然Vue组件把DOM删除了,但是还有个引用存在,导致组件实例没有被释放,组件里面又有一个$el指向DOM,所以DOM也没有被释放。

要在beforeDestroyed里面解绑的

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

所以综合上面的分析,造成内存泄露的可能会有以下几种情况:

(1)监听在window/body等事件没有解绑

(2)绑在EventBus的事件没有解绑

(3)Vuex的$store watch了之后没有unwatch

(4)模块形成的闭包内部变量使用完后没有置成null

(5)使用第三方库创建,没有调用正确的销毁函数

并且可以借助Chrome的内存分析工具进行快速排查,本文主要是用到了内存堆快照的基本功能,读者可以尝试分析自己的页面是否存在内存泄漏,方法是做一些操作如弹个框然后关了,拍一张堆快照,搜索detached,按distance排序,把非空的节点展开父级,找到标黄的字样说明,那些就是存在没有释放的引用。也就是说这个方法主要是分析仍然存在引用的游离DOM节点。因为页面的内存泄露通常是和DOM相关的,普通的JS变量由于有垃圾回收所以一般不会有问题,除非使用闭包把变量困住了用完了又没有置空。

DOM相关的内存泄露通常也是因为闭包和事件绑定引起的。绑了(全局)事件之后,在不需要的时候需要把它解绑。当然直接绑在p上面的可以直接把p删了,绑在它上面的事件就自然解绑了。

Ce qui précède est le contenu détaillé de. pour plus d'informations, suivez d'autres articles connexes sur le site Web de PHP en chinois!

Déclaration:
Le contenu de cet article est volontairement contribué par les internautes et les droits d'auteur appartiennent à l'auteur original. Ce site n'assume aucune responsabilité légale correspondante. Si vous trouvez un contenu suspecté de plagiat ou de contrefaçon, veuillez contacter admin@php.cn