这是关于JavaScript内存泄露相关的序列文章中一篇。由于时间有限更新进度会有点慢,但会持续更新的。自己也在学习中,难免对某些知识点的理解不是很正确,所以才将文章放置github上,一是想与大家分享,二是方便持续更新,三是便于实时修正错误点。也希望看本文的各位同学能多提issues,我会根据提的意见不断完善文章。最后希望各位能从文章中有所收获
最新内容请以github上的为准❗️https://github.com/zhansingsong/js-leakage-patterns
什么是内存泄露
内存泄漏指由于疏忽或错误造成程序未能释放已经不再使用的内存。内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,导致在释放该段内存之前就失去了对该段内存的控制,从而造成了内存的浪费。内存泄漏通常情况下只能由获得程序源代码的程序员才能分析出来。然而,有不少人习惯于把任何不需要的内存使用的增加描述为内存泄漏,即使严格意义上来说这是不准确的。————wikipedia
注:下文中标注的CG是Chrome浏览器中Devtools的【Collect garbage】按钮缩写,表示回收垃圾操作。
意外的全局变量
JavaScript对未声明变量的处理方式:在全局对象上创建该变量的引用(即全局对象上的属性,不是变量,因为它能通过delete删除)。如果在浏览器中,全局对象就是window对象。
如果未声明的变量缓存大量的数据,会导致这些数据只有在窗口关闭或重新刷新页面时才能被释放。这样会造成意外的内存泄漏。
1 | function foo(arg) { |
等同于:
1 | function foo(arg) { |
另外,通过this创建意外的全局变量:1
2
3
4
5
6function foo() {
this.variable = "potential accidental global";
}
// 当在全局作用域中调用foo函数,此时this指向的是全局对象(window),而不是'undefined'
foo();
解决方法:
在JavaScript文件中添加’use strict’,开启严格模式,可以有效地避免上述问题。
1 | function foo(arg) { |
如果需要在一个函数中使用全局变量,可以像如下代码所示,在window上明确声明:
1 | function foo(arg) { |
这样不仅可读性高,而且后期维护也方便
谈到全局变量,需要注意那些用来临时存储大量数据的全局变量,确保在处理完这些数据后将其设置为null或重新赋值。全局变量也常用来做cache,一般cache都是为了性能优化才用到的,为了性能,最好对cache的大小做个上限限制。因为cache是不能被回收的,越高cache会导致越高的内存消耗。
console.log
console.log:向web开发控制台打印一条消息,常用来在开发时调试分析。有时在开发时,需要打印一些对象信息,但发布时却忘记去掉console.log语句,这可能造成内存泄露。
在传递给console.log的对象是不能被垃圾回收 ♻️,因为在代码运行之后需要在开发工具能查看对象信息。所以最好不要在生产环境中console.log任何对象。
实例——>demos/log.html
1 |
|
这里结合Chrome的Devtools–>Performance做一些分析,操作步骤如下:
⚠️注:最好在隐藏窗口中进行分析工作,避免浏览器插件影响分析结果
- 开启【Performance】项的记录
- 执行一次CG,创建基准参考线
- 连续单击【click】按钮三次,新建三个Leaker对象
- 执行一次CG
- 停止记录
可以看出【JS Heap】线最后没有降回到基准参考线的位置,显然存在没有被回收的内存。如果将代码修改为:
1 | !function () { |
去掉console.log(“Leaking an object %o: %o”, (new Date()), this);语句。重复上述的操作步骤,分析结果如下:
<img src=”https://raw.githubusercontent.com/zhansingsong/js-leakage-patterns/master/images/console_2.png" /》
从对比分析结果可知,console.log打印的对象是不会被垃圾回收器回收的。因此最好不要在页面中console.log任何大对象,这样可能会影响页面的整体性能,特别在生产环境中。除了console.log外,另外还有console.dir、console.error、console.warn等都存在类似的问题,这些细节需要特别的关注。