全国免费咨询:

13245491521

VR图标白色 VR图标黑色
X

中高端软件定制开发服务商

与我们取得联系

13245491521     13245491521

2025-02-19_为了解决内存泄露,我把 vue 源码改了

您的位置:首页 >> 新闻 >> 行业资讯

为了解决内存泄露,我把 vue 源码改了 点击关注公众号,“技术干货”及时达! 前言彦祖们,好久不见,最近一直忙于排查单位业务的终端内存泄露问题,已经吃了不下 10 个 bug 了 但是排查内存泄露在前端领域属于比较冷门的领域了 这篇文章笔者将带你一步步分享业务实践中遇到的内存泄露问题以及如何修复的经历 本文涉及技术栈 vue2场景复现如果之前有看过我文章的彦祖们,应该都清楚 笔者所在的单位有一个终端叫做工控机(类似于医院挂号的终端),没错!所有的 bug 都源自于它?? 因为内存只有 1G 所以一旦发生内存泄露就比较可怕 不过没有这个机器 好像也不会创作这篇文章?? 复现 demo彦归正传,demo 其实非常简单,只需要一个最简单的 vue2 demo 就可以了 App.vuetemplate div id="app" button @click="render = true"render/button button @click="render = false"destroy/button Test v-if="render"/ /div/templatescriptimport Test from './test.vue'export default { name: 'App', components: { Test }, data () { return { render: false } }}/scriptstyle#app { font-family: Avenir, Helvetica, Arial, sans-serif; -webkit-font-smoothing: antialiased; -moz-osx-font-smoothing: grayscale; text-align: center; color: #2c3e50;}/style test.vuetemplate div class="test" div{{ total }}/div div v-for="(item,index) in 1000" :key="`${item}-${index}`" class="item" {{ item }}ipc-prod2.8 /div /div/template scriptexport default { name: 'Test', data () { return { total: 1000 } }, mounted () { this.timer = setTimeout(() = { this.total = 10000 }, 500) }, beforeDestroy () { clearTimeout(this.timer) }}/script复现流程以下流程建议彦祖们在 chrome 无痕模式下执行 我们点击 render 按钮渲染 test 组件,此时我们发现 dom 节点的个数来到了 2045 考虑到有彦祖可能之前没接触过这块面板,下图展示了如何打开此面板 500ms 后(定时器执行完成后,如果没复现可以把 500ms 调整为 1000ms, 1500ms),我们点击 destroy 按钮我们点击面板这里的强制回收按钮(发现节点并没有回收,已发生内存泄露) 如果你的浏览器是最新的 chrome,还能够点击这里的 已分离的元素(detached dom),再点击录制 我们会发现此时整个 test 节点已被分离 问题分析那么问题到底出在哪里呢? vue 常见泄露场景笔者搜遍了全网,网上所说的不外乎以下几种场景 1.「未清除的定时器」 2.「未及时解绑的全局事件」 3.「未及时清除的 dom 引用」 4.「未及时清除的 全局变量」 5.「console 对引用类型变量的劫持」 好像第一种和笔者的场景还比较类似,但是仔细看看代码好像也加了 beforeDestroy () { clearTimeout(this.timer)}这段代码啊,就算不加,timer 执行完后,事件循环也会把它回收掉吧 同事提供灵感就这样笔者这段代码来回测试了半天也没发现猫腻所在 这时候同事提供了一个想法说"total 更新的时候是不是可以提供一个 key" 改了代码后就变成了这样了 test.vuetemplate div class="test" div :key="renderKey"{{ total }}/div div v-for="(item,index) in 1000" :key="`${item}-${index}`" class="item" {{ item }}ipc-prod2.8 /div /div/template scriptexport default { name: 'Test', data () { return { renderKey: 0, total: 1000 } }, mounted () { this.timer = setTimeout(() = { this.total = 10000 this.renderKey = Date.now() }, 500) }, beforeDestroy () { clearTimeout(this.timer) }}/script 神奇的事情就这样发生了,笔者还是按以上流程测试了一遍,直接看结果吧 我们看到这个 DOM 节点曲线,在 destroy 的时候能够正常回收了 问题复盘最简单的 demo 问题算是解决了 但是应用到实际项目中还是有点困难 难道我们要把每个更新的节点都手动加一个 key 吗? 其实仔细想想,有点 vue 基础的彦祖应该了解这个 key 是做什么的? 不就是为了强制更新组件吗? 等等,强制更新组件?更新组件不就是 updated 吗? updated 涉及的不就是八股文中我们老生常谈的 patch 函数吗?(看来八股文也能真有用的时候??) 那么再深入一下, patch 函数内部不就是 patchVnode 其核心不就是 diff 算法吗? 首对首比较,首对尾比较,尾对首比较,尾对尾比较 这段八股文要是个 vuer 应该都不陌生吧??? 动手解决其实有了问题思路和想法 那么接下来我们就深入看看 vue 源码内部涉及的 updated 函数到底在哪里吧? 探索 vue 源码我们找到 node_modules/vue/vue.runtime.esm.js 我们看到了 _update 函数真面目,其中有个 __patch__ 函数,我们再重点查看一下 createPatchFunction 最后 return 了这个函数 我们最终来看这个 updateChildren 函数 其中多次出现了上文中所提到的八股文,每个都用 sameVnode进行了对比 function sameVnodefunction sameVnode (a, b) { return (a.key === b.key && a.asyncFactory === b.asyncFactory && ((a.tag === b.tag && a.isComment === b.isComment && isDef(a.data) === isDef(b.data) && sameInputType(a, b)) || (isTrue(a.isAsyncPlaceholder) && isUndef(b.asyncFactory.error))));}果然这里我们看到了上文中 key 的作用 key 不一样就会认作不同的 vnode 那么就会强制更新节点 对应方案既然找到了问题的根本 在判定条件中我们是不是直接加个 || a.text !== b.text 强制对比下文本节点不就可以了吗? 修改 sameVnode看下我们修改后的 sameVnode function sameVnode (a, b) { if(a.text !== b.text) return false // 文本不相同 直接 return return (a.key === b.key && a.asyncFactory === b.asyncFactory && ((a.tag === b.tag && a.isComment === b.isComment && isDef(a.data) === isDef(b.data) && sameInputType(a, b)) || (isTrue(a.isAsyncPlaceholder) && isUndef(b.asyncFactory.error))));}方案效果让我们用同样的代码来测试下 测试了几次发现非常的顺利,至此我们本地的修改算是完成了 如何上线?以上的方案都是基于本地开发的,那么我们如何把代码应用到线上呢? 其他开发者下载的 vue 包依旧是 老的 sameVnode 啊 不慌,接着看 patch-package对比了好几种方式,最终我们选择了这个神器 其实使用也非常简单 1.npm i patch-package 2.修改 node_modules/vue 源码 3.在根目录执行 npx patch-package vue(此时如果报错,请匹配对应 node 版本的包) 我们会发现新增了一个这样的文件 4.我们需要在package.json scripts 新增以下代码 package.json"scripts": { +"postinstall":"patch-package"}至此上线后,其他开发者执行 npm i 后便能使变动的补丁生效了 优化点其实我们的改造还有一定的进步空间,比如说在指定节点上新增一个 attribute 在函数内部判断这个 attribute 再 return false 这样就不用强制更新每个节点了 当然方式很多种,文章的意义在于解决问题的手段和耐心 点击关注公众号,“技术干货”及时达! 阅读原文

上一篇:2020-12-16_比起赛博朋克2077,这款游戏的预告更让我心跳 下一篇:2021-07-06_大夏天的 , 飞猪跑去沙漠里造了一片海

TAG标签:

17
网站开发网络凭借多年的网站建设经验,坚持以“帮助中小企业实现网络营销化”为宗旨,累计为4000多家客户提供品质建站服务,得到了客户的一致好评。如果您有网站建设网站改版域名注册主机空间手机网站建设网站备案等方面的需求...
请立即点击咨询我们或拨打咨询热线:13245491521 13245491521 ,我们会详细为你一一解答你心中的疑难。
项目经理在线

相关阅读 更多>>

猜您喜欢更多>>

我们已经准备好了,你呢?
2022我们与您携手共赢,为您的企业营销保驾护航!

不达标就退款

高性价比建站

免费网站代备案

1对1原创设计服务

7×24小时售后支持

 

全国免费咨询:

13245491521

业务咨询:13245491521 / 13245491521

节假值班:13245491521()

联系地址:

Copyright © 2019-2025      ICP备案:沪ICP备19027192号-6 法律顾问:律师XXX支持

在线
客服

技术在线服务时间:9:00-20:00

在网站开发,您对接的直接是技术员,而非客服传话!

电话
咨询

13245491521
7*24小时客服热线

13245491521
项目经理手机

微信
咨询

加微信获取报价