你好获取登录信息中




首页 主站 文章列表 分类列表
查看文章返回文章列表

前端资源未释放的分析与解决

发布时间:2019-10-29 14:20:48 by:
最后更新时间:2019-12-04 10:17:40 by:



为了避免涉密问题,文中提及的代码部分class名称被删除或打码,但不影响内容

前端时间发现办公电脑经常在下午因为内存不够卡死,然后发现是公司项目的页面吃的内存会随着使用有显著增加。由于这个项目是VUE的单页项目,不同页面的切换只是通过动态组件(component)切换的,所以很长一段时间都不会刷新页面,因此这个问题会更加明显。

经过测试,在谷歌浏览器的开发人员工具中可以看出,在页面第一次加载时,内存占用甚至不到30M,但是反复重新加载同一页面后(将动态组件的is属性设置为null然后重新赋值)内存占用就会成倍增长,十几次之后,占用内存能超过200M。通过网上找到的排查方法可以看到有大量的游离(未挂载到页面显示)的dom元素。

每次切换控件后游离的dom都会增加

难道是vue有问题不能正确处理?怎么想都觉得不可能,毕竟,如果是vue的问题,这个影响可太大了。那么会是哪里的问题呢?我在这里面看到了一个表单的dom元素,其实基本上也能确定问题所在了,不过为了确认这个问题,我还分块注释代码,然后逐步缩小范围,最后确定问题就是出在表单上。

这个表单呢,是这样做的基本上就是用的ElementUI的控件,但是呢,因为我们需要修改一些样式什么的,所以外面套了一层公司的控件,ElementUI作为槽插入其中。我这里能够确认到是外面这层控件的原因导致的了,接下来就是检查这个控件的活。

然后我们可以看到这里有一段用jquery做的部分。它的作用是把ElementUI原本显示在输入控件下方的信息变成鼠标悬停时显示(减少页面占用空间)。虽然好像是没啥问题,但是又总觉得不太对,于是我把这一段屏蔽了,然后问题顿时好了很多。

  1. /*
  2. * 创建验证悬浮窗
  3. */
  4. function createValidateToolTip($el) {
  5. //对form里面的所有el-input__inner添加鼠标进入和出去的事件,以在鼠标经过时(如果有的化)显示验证信息,出去时隐藏验证信息
  6. $($el)
  7. .find(".el-form-item__content")
  8. .each(function() {
  9. var $elinput = $(this).find(".el-input,.el-textarea__inner");
  10. var elFormItemContent = this;
  11. if ($elinput.size() > 0) {
  12. $($elinput).mouseenter(function() {
  13. var $findErrorItem = $(elFormItemContent).find(
  14. ".el-form-item__error"
  15. ); //看是否有错误验证提示信息
  16. if ($findErrorItem.size() > 0) {
  17. var validateMsg = $($findErrorItem.get(0)).html();
  18. //生成toolTip
  19. //生成外围div
  20. var $toolTip = $(
  21. '<div class=""></div>'
  22. ).appendTo($("body"));
  23. $(this).data("toolTip", $toolTip); //存放到元素的data里面,方便后面找到他然后移除
  24. $toolTip.width(strlen(validateMsg) * 6);
  25. //生成内容div
  26. var $toolTipContent = $(
  27. '<div class=""></div>'
  28. ).appendTo($toolTip);
  29. $toolTipContent.html(validateMsg);
  30. //生成箭头外围div
  31. var $toolTipArrowOuter = $(
  32. '<div class="" ></div>'
  33. ).appendTo($toolTip);
  34. //生成箭头div
  35. var $toolTipArrow = $(
  36. '<div class="" ></div>'
  37. ).appendTo($toolTip);
  38. var offset = $(this).offset();
  39. var toolTipOffset = {
  40. top:
  41. offset.top +
  42. ($(this).outerHeight() - $toolTip.outerHeight()) / 2,
  43. left: offset.left + $(this).outerWidth() + 8
  44. };
  45. $toolTip.offset(toolTipOffset);
  46. }
  47. });
  48. $($elinput).mouseleave(function() {
  49. if ($(this).data("toolTip"))
  50. $(this)
  51. .data("toolTip")
  52. .remove();
  53. });
  54. }
  55. });
  56. }

可是,为什么这里会出现这个问题呢?首先我想到以前说过jQuery是会做一些缓存提高性能的,可是怎么缓存的呢?抱着试一下的心态随便在控制台中输入$.然后看到一个cache字段的提示,赶紧打出来一看,果然有东西!重新加载页面之后,他还正好增加了一倍。

事件没解注册之前

这样一来,问题就算是找到了,vue销毁了页面,但是jQuery并不知道,并且这些元素会存放在全局的缓存中(即$.cache),然后这些dom因为仍然存在引用,所以不能被释放掉。

那么接下来就是解决这个问题了。其实吧我一开始以为是jquery选择器的缓存,可是查阅资料得知选择器不会缓存,差点没了头绪,好在点开一看,是事件的缓存。并且在这个事件的缓存中通过Scopes字段引用的dom,然后dom又有childrenparentNode这一类的字段,相互引用着,导致整个页面都还处于引用状态。那么接下来就是怎么解决这个问题了。其实也挺简单的,在控件销毁前(beforeDestroy钩子)把这些事件解注册就行了:

  1. $(this.$el).find(".el-input,.el-textarea__inner").off();

然后再看页面,反复加载$.cache也没有增长:

事件解注册之后

相应的,页面游离的dom也没有增加了(不过还是有闭包之类的在增加,不过那些暂时就不查了,至少现在内存占用已经控制住了):

游离dom不在增长

OK,准备收工。可是忽然有发现出现了没被释放的dom!难道?点开看看是啥:

jquery的data缓存

可以看到这是缓存了data属性,不知道大家还记得之前确实有这么一句东西

  1. $(this).data("toolTip", $toolTip);

对就是它同样需要手动释放掉,所以得到最终资源释放的代码如下:

  1. $(this.$el).find(".el-input,.el-textarea__inner").removeData("toolTip").off();

OK,虽然现在页面内存泄漏的问题仍然存在,但是速度已经控制住了,并且考虑运行我们产品的客户端一般不会打开其他网页和软件,只是可能很长时间(甚至几个月)都会一直开着,所以这样基本上还是可以接受了吧。

评论
    还没有评论
发表评论
正在等候用户中心返回数据
杂项
。。。