>  기사  >  웹 프론트엔드  >  vue 사용 시 메모리 누수 [권장]_vue.js

vue 사용 시 메모리 누수 [권장]_vue.js

无忌哥哥
无忌哥哥원래의
2018-07-12 14:04:311367검색

메모리 누수는 새로운 메모리 조각이 해제되거나 가비지 수집될 수 없음을 의미합니다. 이번 글은 주로 vue 사용시 발생하는 메모리 누수에 대해 소개합니다. 도움이 필요한 친구들은 참고하시면 됩니다

메모리 누수란 무엇인가요? 메모리 누수는 해제되거나 가비지 수집될 수 없는 새로운 메모리 조각을 의미합니다. 객체를 새로 만든 후 힙 메모리 조각을 할당합니다. 객체 포인터가 null로 설정되거나 범위를 벗어나 소멸되면 JS에서 아무도 참조하지 않으면 이 메모리가 자동으로 가비지 수집됩니다. 그러나 개체 포인터가 null로 설정되지 않고 코드에서 개체 포인터를 얻을 수 있는 방법이 없으면 해당 개체 포인터가 가리키는 메모리가 해제되지 않으므로 메모리 누수가 발생합니다. 다음은 코드에서 이 개체 포인터를 가져올 수 없는 이유입니다.

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

main.js가 날짜를 초기화한 후 날짜 참조가 다른 페이지에 있기 때문에 날짜 변수는 항상 존재합니다. 모듈의 경우, 모듈은 외부 세계에는 보이지 않는 클로저로 이해될 수 있습니다. 따라서 이 날짜 개체가 항상 존재하고 항상 사용되어야 한다면 문제가 없지만, 한 번 사용하고 사용하지 않으려면 문제가 발생합니다. 메모리가 해제되지 않고 메모리 누수가 발생했습니다.

또 다른 미묘하고 매우 일반적인 메모리 누수는 이벤트 바인딩으로, 이는 클로저를 형성하고 일부 변수가 항상 존재하게 만듭니다. 다음 예와 같이:

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

페이지를 클릭할 때마다 이전 페이지의 데이터가 지워지고 현재 페이지의 DOM으로 업데이트되는 이미지 지연 로딩 모델입니다. , 지연 로딩이 다시 초기화됩니다. 스크롤 이벤트를 수신하고 들어오는 그림 목록의 DOM을 처리합니다. 새 페이지를 클릭할 때마다 새 페이지가 생성됩니다. 주로 다음 세 줄의 코드로 인해 메모리 누수가 발생합니다.

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

여기서 이벤트 바인딩이 클로저를 형성하기 때문에 this/$ photoList 두 변수는 해제되지 않았습니다. 이는 ImageLazyLoader의 인스턴스를 가리키고, $photoList는 이전 페이지의 데이터가 지워지면 해당 DOM 노드가 DOM 트리에서 분리된 것입니다. 이를 가리키는 $photoList가 있으므로 이러한 DOM 노드는 가비지 수집이 불가능하고 메모리에 남아 있게 되며 메모리 누수가 발생합니다. 이 변수도 클로저에 의해 트랩되어 해제되지 않았으므로 ImageLazyLoader 인스턴스에도 메모리 누수가 있습니다.

이 문제에 대한 해결책은 비교적 간단합니다. 즉, 다음 코드에 표시된 대로 인스턴스를 삭제할 때 바인딩된 이벤트를 끄는 것입니다.

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

매번 ImageLazyLoader를 인스턴스화하기 전에 이전 인스턴스를 지우고 바인딩을 해제합니다. JS에는 생성자는 있지만 소멸자는 없으므로 직접 Clear를 작성하고 외부에서 Clear를 수동으로 조정해야 합니다. 동시에 이벤트 실행 중 적절한 시점에 자동으로 이벤트 바인딩이 해제됩니다. 위의 내용은 모든 그림이 표시되면 스크롤 이벤트를 모니터링하고 직접 바인딩을 해제할 필요가 없다고 판단하기 위한 것입니다. 이렇게 하면 메모리 누수 문제가 해결되고 자동 가비지 수집이 실행됩니다.

이벤트 바인딩을 해제한 후 종료 참조가 없는 이유는 무엇인가요? JS 엔진은 클로저가 더 이상 유용하지 않다는 것을 감지하기 때문에 클로저를 파괴하고 클로저가 참조하는 외부 변수는 자연스럽게 공백으로 남게 됩니다.

자, 여기서 기본 지식을 설명했습니다. 이제 Chrome 개발자 도구의 메모리 감지 도구를 사용하여 실제로 페이지에서 일부 메모리 누수를 쉽게 발견할 수 있습니다. 브라우저에 설치된 일부 플러그인의 영향을 방지하려면 Chome의 시크릿 모드 페이지를 사용하세요. 그러면 모든 플러그인이 비활성화됩니다.

그런 다음 아래와 같이 devtools를 열고 메모리 탭으로 전환하고 힙 스냅샷을 선택합니다.


힙 스냅샷이란 무엇입니까? 번역하면 현재 메모리 힙의 사진을 찍는 힙 스냅샷입니다. 동적으로 적용되는 메모리는 힙에 있고 지역 변수는 메모리 스택에 있고 운영 체제에 의해 할당 및 관리되므로 메모리 누수가 발생하지 않습니다. 따라서 힙 상황에 대해 걱정하십시오.

그런 다음 다음과 같이 DOM을 추가, 삭제 및 수정하는 작업을 수행합니다.

(1) 상자를 띄운 다음 팝업 상자를 닫습니다.

(2) 한 페이지를 클릭하여 다른 페이지로 이동합니다. 라우트한 다음 다시 클릭하여 돌아옵니다.

(3) 페이징을 클릭하여 동적 DOM 변경을 트리거합니다

. 먼저 DOM을 추가한 다음 이러한 DOM을 삭제하고 삭제된 DOM에 이를 참조하는 객체가 여전히 있는지 확인하세요.

여기서는 단일 페이지 애플리케이션의 라우팅 페이지에 메모리 누수가 있는지 여부를 감지하기 위해 두 번째 방법을 사용하고 있습니다. 먼저 홈페이지를 열고 다른 페이지를 클릭한 다음 뒤로를 클릭하고 가비지 수집 버튼을 클릭하세요.

불필요한 간섭을 피하기 위해 가비지 수집을 실행합니다.

그런 다음 사진 버튼을 클릭하세요:

 

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

 

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

위 내용은 vue 사용 시 메모리 누수 [권장]_vue.js의 상세 내용입니다. 자세한 내용은 PHP 중국어 웹사이트의 기타 관련 기사를 참조하세요!

성명:
본 글의 내용은 네티즌들의 자발적인 기여로 작성되었으며, 저작권은 원저작자에게 있습니다. 본 사이트는 이에 상응하는 법적 책임을 지지 않습니다. 표절이나 침해가 의심되는 콘텐츠를 발견한 경우 admin@php.cn으로 문의하세요.