javascript - Vue.js 内存泄漏与 Turbolinks 即使调用 destroy() - 浏览器始终保持对 vue 实例应用程序的引用
问题描述
我有一个使用 Turbolinks 的 Rails 应用程序。如果您不熟悉,turbolinks 使document
andwindow
对象始终保持不变(页面从不刷新),它通过 AJAX 拦截所有链接点击并替换 body 标记。
当导航到和从安装了 Vue 应用程序的页面时,这是我们观察到的内存/节点模式(它增长到无穷大):
如您所见,在每次页面更改时,内存只会增加,而不会被回收。
我们的 App 使用https://github.com/jeffreyguenther/vue-turbolinks,基本上就是这样的代码:
beforeMount: function() {
if (this === this.$root && this.$el) {
document.addEventListener('turbolinks:visit', function teardown() {
this.$destroy();
document.removeEventListener('turbolinks:visit', teardown);
})
// cache original element
this.$cachedHTML = this.$el.outerHTML;
// register root hook to restore original element on destroy
this.$once('hook:destroyed', function() {
if( this.$el.parentNode )
this.$el.outerHTML = this.$cachedHTML
});
}
}
我对 Vue 应用程序或其他任何地方都没有任何参考window
(实例化代码只是new Vue({ options})
;没有全局变量,使用 const/let 的现代代码。
应用内没有document.addEventListener
添加;所有事件监听器都由 v-on 添加,Vue 在销毁时自动删除。
为了进一步调试,我修补了上面的代码以添加存储,WeakRef
并在销毁 Vue 实例之前对其进行了设置,如下所示:
document.addEventListener('turbolinks:visit', function teardown() {
window.weakReferences = window.weakReferences || {};
window.weakReferences[new Date()] = new window.WeakRef(this);
this.$destroy();
})
切换屏幕一段时间后,我可以确认 Vue 对象仍然存在于内存中(WeakRef.deref()
不返回未定义),即使它们都_isDestroyed
被 Vue 标记为内部。
如何调试为什么 Vue 应用程序实例没有被垃圾收集以及为什么浏览器保留对它们的引用?
解决方案
男孩,我是来兜风的。我的应用程序中有 6 个内存泄漏。
要发现它们,请执行以下操作:
- 打开您的 Chrome 开发工具并关注内存选项卡(我建议在 icognito 选项卡中执行此操作,因此扩展程序不会干扰测量);刷新您的应用程序,然后单击小垃圾桶(这将强制垃圾收集 - GC);
开始注意“选择 Javascript VM 实例”中的 MB 指示符(您可能只有一行)。您可以忽略那里的向上/向下箭头指示符,只关注第一个数字,即您的选项卡现在使用的 MB 数。
- 开始使用您的应用程序;在您的情况下,导航到和从具有 Vue 应用程序的页面;请注意内存是否一直在上升,或者当您离开 Vue 应用程序时它会下降;
在您的情况下,每次从应用程序导航到和从应用程序都会增加 20 MB 的内存使用量,最终在 10 次左右后达到 200MB;单击垃圾收集图标仅减少了大约 10MB 的使用量,因此很明显我们遇到了泄漏。
将页面置于此泄漏状态并单击 GC 垃圾图标后,在单选选项中选择“堆快照”。它将收集快照。点击它。
在我们的例子中,我们的 Vue 应用程序由于泄漏而被保存在内存中。所以我们专注于列表中的“VueComponent”项。单击小三角形并观察屏幕的下部。它将开始向您展示哪些代码将这些引用保留在内存中。有时您会很幸运,甚至会在那里获得一些行号,您可以单击它,它将在源选项卡中打开。
我们在您的应用中有 6 次以上的泄漏;下面我将展示其中一个,您会看到内存跟踪指向$notify
,这是我们正在使用的 lib(vue-notification):
是的,您可能会从其他人的代码中泄漏,这很糟糕。查看那个库,我发现罪魁祸首是在created
钩子上定义的两个事件处理程序,它们从未被释放;我在这里发出了拉取请求。
- 冲洗并重复。在我们的 6 个内存泄漏中,通过这种方法诊断时,大多数都可以轻松解决。我们有一些东西:
库中的“错误”
mitt
(微小的事件发射器);eventEmitter.off()
应该清除所有事件发射器,但它没有;在这里打开一个问题;created
我们在(using )中订阅了 Vuex 事件,this.$store.subscribe()
但忘记在 beforeDestroy() 中取消订阅;该subscribe()
函数返回一个取消订阅的函数;将其保存在实例本身(如this.unsubscribeVuex = this.$store.subscribe(...)
)并this.unscubribeVuex
在您的beforeDestroy()
钩子中调用请注意您是否没有留下
window.myApp = new Vue()
参考资料;没有任何东西window.
被垃圾收集;window.myApp = null;
入beforeDestroy
钩的好主意;Google Chart 也导致了内存泄漏;我在下面粘贴快速修复,请注意差异中我们如何泄漏对我们的 vue 应用程序的永久引用:
推荐阅读
- checkbox - 输入事件似乎在 vuetify 复选框上不可用?
- ruby-on-rails - Rails - 三重嵌套路线上的 simple_form_for
- javascript - 如何用文本编辑内联按钮?电报机器人,电报,节点
- python - 使用 KIVY 作为 Box 布局的一部分显示图表
- php - Laravel 5.6:公共文件夹中下载文件的副本
- c# - ASP.NET Core 2.1 在 App Engine 中没有 HTTP/HTTPS 重定向
- javascript - 如何在 Three.js 中使用 ObjLoader 加载的对象的顶点
- python - Python总是四舍五入?
- kubernetes - Kubernetes 命名空间停留在终止状态
- node.js - 如何从机器人读取通过聊天控制(iframe)发送的参数值