Home  >  Article  >  Web Front-end  >  Memory leaks in vue usage [Recommended]_vue.js

Memory leaks in vue usage [Recommended]_vue.js

无忌哥哥
无忌哥哥Original
2018-07-12 14:04:311262browse

Memory leak refers to a new piece of memory that cannot be released or garbage collected. This article mainly introduces memory leaks in the use of vue. Friends in need can refer to

What is a memory leak? A memory leak refers to a new piece of memory that cannot be released or garbage collected. After newing an object, it allocates a piece of heap memory. When the object pointer is set to null or leaves the scope and is destroyed, then this memory will be automatically garbage collected if no one refers to it in JS. However, if the object pointer is not set to null and there is no way to obtain the object pointer in the code, the memory pointed to by it will not be released, which means a memory leak occurs. Why can’t the object pointer be obtained in the code? Here is an example:

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

After main.js initializes date, the date variable becomes It will exist until you close the page, because the date reference is in another module, it can be understood that the module is a closure and is invisible to the outside world. So if you want this date object to always exist and need to be used all the time, then there is no problem. But if you want to use it once and then not use it, there will be a problem. This object has been in the memory without being released, and a memory leak has occurred.

Another relatively hidden and very common memory leak is event binding, which forms a closure, causing some variables to always exist. As shown in the following example:

// 一个图片懒惰加载引擎示例
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'));
});

This is a model for lazy loading of images. Every time you click paging, the data on the previous page will be cleared and updated to The DOM of the current page and reinitializes a lazy loading engine. It listens to the scroll event and processes the DOM of the incoming picture list. Every time you click on a new page, a new page will be created. A memory leak occurs here, mainly caused by the following three lines of code:

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

Because the event binding here Defined into a closure, the two variables this/$photoList have not been released. This points to the instance of ImageLazyLoader, and $photoList points to the DOM node. When the data on the previous page is cleared, the relevant DOM node The nodes have been separated from the DOM tree, but there is still a $photoList pointing to them. As a result, these DOM nodes cannot be garbage collected and remain in the memory, and a memory leak occurs. Since this variable is also trapped by the closure and has not been released, there is also a memory leak in an instance of ImageLazyLoader.

The solution to this is relatively simple, which is to turn off the bound event when destroying the instance, as shown in the following code:

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'));
});

Before each instantiation of an ImageLazyLoader, clear the previous instance and unbind it in clear. Since JS has a constructor but no destructor, you need to write a clear yourself and manually adjust the clear outside. At the same time, the event is automatically unbound at the appropriate time during the execution of the event. The above is to judge that if all the pictures are displayed, then there is no need to monitor the scroll event and unbind it directly. This will solve the problem of memory leaks and trigger automatic garbage collection.

Why is there no closure reference after unbinding the event? Because the JS engine detects that the closure is no longer useful, it destroys the closure, and the external variables referenced by the closure will naturally be left blank.

Okay, the basic knowledge has been explained here. Now use the memory detection tool of Chrome devtools to actually operate it to facilitate the discovery of some memory leaks on the page. In order to avoid the impact of some plug-ins installed in the browser, use Chome's incognito mode page, which will disable all plug-ins.

Then open devtools, switch to the Memory tab, and select Heap snapshot, as shown below:


What is a heap snapshot? Translated, it is a heap snapshot, taking a picture of the current memory heap. Because the dynamically applied memory is in the heap, and the local variables are in the memory stack and are allocated and managed by the operating system, there will be no memory leaks. So just be concerned about the heap situation.

Then do some operations of adding, deleting and modifying the DOM, such as:

(1) Pop up a box, and then close the box

(2) Click on a single page Jump to another route, and then click back to return

(3) Click paging to trigger dynamic DOM change

That is to first add DOM, and then delete these DOM. Take a look at these DOM changes. Whether the deleted DOM still has objects referencing them.

Here I am using the second method to detect whether there is a memory leak in a routing page of a single-page application. First open the home page, click on another page, then click back, and then click the garbage collection button:

Trigger garbage collection to avoid unnecessary interference.

Then click the photo button:

 

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

 

然后在上面中间的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删了,绑在它上面的事件就自然解绑了。

The above is the detailed content of Memory leaks in vue usage [Recommended]_vue.js. For more information, please follow other related articles on the PHP Chinese website!

Statement:
The content of this article is voluntarily contributed by netizens, and the copyright belongs to the original author. This site does not assume corresponding legal responsibility. If you find any content suspected of plagiarism or infringement, please contact admin@php.cn