关于vue.js:Yank-Note-系列-03-同内存泄露的艰难战斗

10次阅读

共计 3304 个字符,预计需要花费 9 分钟才能阅读完成。

Yank Note 是我编写的一款面向程序员的笔记利用。这里我将会写下一些对于 Yank Note 的文章

前言

在前一篇文章里,我讲述了给 Yank Note 做性能优化的一些事件。然而影响一个利用体验的,也不仅仅是性能,还包含性能是否满足用户需要、界面是否好看、交互操作是否正当晦涩、运行是否稳固。

要保障利用稳固运行,除了 Bug 要少,内存占用也要尽量少和稳固。Yank Note 是 Web 技术开发的,通常的 Web 利用无需太关怀内存占用,因为用户用完即走,切实卡顿了还能刷新页面解决。而 Yank Note 不一样,用户通常会始终关上利用,而当初的计算机通常也会很久都不关机,所以管制内存占用,缩小内存泄露就成为 Yank Note 稳固运行必须要做的工作。

在 v3.14.2 版本,Yank Note 针对内存方面做了不少优化,本文将形容在解决内存泄露过程中所做的事件。

试验形式和排查工具

试验场景

Yank Note 在应用过程中,用户会进行较多的文字输出,也会进行文档切换。两个场景是潜在内存泄露的重点关注中央。

得益于 Yank Note 凋谢的 Api,我只须要写几句脚本,即可在实在利用中模仿下面两个操作。

渲染脚本:模仿文本输出后的渲染

for (let i = 0; i < 100; i ++) {ctx.view.render();
  await ctx.utils.sleep(50);
}

文档切换脚本:模仿文档切换

for (let i = 0; i < 30; i ++) {await ctx.doc.showHelp('FEATURES.md');
  await ctx.utils.sleep(1000);
  await ctx.doc.showHelp('README.md');
  await ctx.utils.sleep(2000);
}

工具

应用 Chrome 隐衷标签,排除插件烦扰。用到了 Chrome 的性能监视器面板,内存面板,性能面板。

过程

运行模仿脚本,应用 Chrome 性能监视器查看内存增长曲线,配合内存快照性能,查找出内存泄露点。

Vue 虚构节点内存泄露

在运行完渲染脚本后,发现内存有不少增长。这意味着,用户在失常打字输出后,零碎应用的内存也会缓缓升高。

排查下来,发现了问题的要害:在代码外面应用 h('div') 不会有内存泄露,然而应用 h(CustomComponent) 则会产生泄露。

在上一篇文章里,为了优化渲染性能,Yank Note 引入了 Vue 的 VNode,成果很不错。每一个元素,都是新创建的 VNode。然而对于自定义函数式组件,都是应用的最后创立的组件,我的代码会追加 children 而不是替换,所以造成了内存泄露。

而自定义组件的 children 现阶段没任何用途,所以解决办法就是判断 VNode type 类型,type 是 string 和 fragment 节点才塞入 children。

百度脑图组件内存泄露

我应用百度的 kityminder-core 来做脑图的可视化。

这个库最后应该只是服务自家的脑图服务,基本上只思考了一个页面只有单实例,因而它在事件监听和全局资源的应用上很随性。尽管脑体实例提供了一个 destroy 办法,但调用后抛错,Hack 了一下后能跑完流程,但很多事件还是没勾销监听,全局变量应用也没开释。

解决事件监听

kityminder 所有的全局 DOM 事件监听都是产生在 new 这个类的时候。所以我罗唆在 new 这个类之前重写 window.addEventListener 记录监听了哪些办法,而后销毁时候手动勾销监听

const realAddEventListener = window.addEventListener.bind(window)
const events: {type: string, listener: any}[] = []
window.addEventListener = (type: string, listener: any) => {logger.debug('hack addEventListener', type)
  events.push({type, listener})
  realAddEventListener(type, listener)
}

const km = new window.kityminder.Minder()

// 复原原来的事件监听
window.addEventListener = realAddEventListener

// 销毁调用
events.forEach(({type, listener}) => {window.removeEventListener(type, listener)
})

解决全局变量应用

kityminder 外部应用的是 kity 这个库,也是百度出品。这个库将很多外部事件都绑定在了原型上,也没有回收资源的中央。而且 Yank Note 可能会在页面上嵌入多个脑图实例,因而粗犷的毁灭掉这些全局变量应用也不事实。

所以我借鉴了微前端的思维,那就在每次 new 类的时候,都应用全新的一个类吧。动了一下 kity 的源码,把模块导函数裸露进去。

Object.defineProperty(window, 'kity', {get: () => {logger.debug('new kity')
    return window.kityM()},
})

Object.defineProperty(window, 'kityminder', {get: () => {logger.debug('new kityminder')
    return window.kityminderM()},
})

每一次 new 类的时候,都从新调用模块的初始化办法创立一个新的类。尽管初始化效率比共用一个类低了一些,但也比 iframe 的形式效率高。

日志打印优化

在运行文档切换脚本后,内存占用迅速飙升,一度达到快 1G,十分吓人。打快照进去后,发现泄露点是在 Luckysheet 性能。

然而 Luckysheet 我是通过 iframe 引入的,按理说 iframe 销毁后,外部的所有资源当开释呀,怎么内存还没开释?而且更为要命的是,刷新页面之后,这些内存也不会销毁!这就让我很纳闷了,难不成遇到了 Chrome 的 Bug?

仔细观察堆快照,发现有很多 Detached Window,这些都是 Lucksheet 应用过的 iframe。在控制台你甚至还能够进入这个 Window,看到 this 和 globalThis 没有被清理。难不成真的是 Chrome 的 Bug?

前面又做了不少试验,发现也只有 Luckysheet 这个 iframe 会有这种状况,其余 iframe 都不会。那么必定是 Luckysheet 和其余有什么不同导致的。

终于,我发现了,Luckysheet 会在控制台打印对象日志,而其余 iframe 不会!而这就是罪魁祸首!

其实最开始在排查时候,我就看到了 Devtools console 的字样,然而因为它除了这一个中央还有其余挂载援用,所以我一时没往这方面想。

至于为什么刷新页面后,内存仍然不被开释,我猜测应该是 Chrome 控制台有个保留日志的性能,也就是刷新页面后,日志仍然能够展现,如果要反对这个性能,那么不开释内存也在情理之中了。

解决办法:嵌入 iframe 在生产环境敞开日志打印,通过重写 console 的形式实现。

console.warn = () => 0
console.log = () => 0
console.info = () => 0
console.dir = () => 0

通过了下面的解决后,运行切换文档脚本后,内存根本不再增长了。

论断:技术水平不够的时候, 不要轻易狐疑浏览器 ;尽量不要在日志中打印具备多层援用的对象,生产环境敞开日志打印。

其余

应用 Chrome 的内存工具还查找进去了其余几处内存泄露,根本都是和闭包的应用无关,这里就不多形容了。

总结

做了下面的工作之后,整个利用的内存应用状况好了很多。重复切换简单文档后,Javascript 堆内存应用也能维持在正当程度。

在开发过程中,须要留神正当应用闭包,应用第三方库也须要考查否满足性能和内存应用需要。

如果你对 Yank Note 感兴趣,想应用或者参加奉献,能够到 Github 理解更多。

本文由「Yank Note – 一款面向程序员的 Markdown 笔记利用」撰写

正文完
 0