这是关于JavaScript内存泄露相关的序列文章中一篇。由于时间有限更新进度会有点慢,但会持续更新的。自己也在学习中,难免对某些知识点的理解不是很正确,所以才将文章放置github上,一是想与大家分享,二是方便持续更新,三是便于实时修正错误点。也希望看本文的各位同学能多提issues,我会根据提的意见不断完善文章。最后希望各位能从文章中有所收获
最新内容请以github上的为准:exclamation:️ https://github.com/zhansingsong/js-leakage-patterns
什么是内存泄露
内存泄漏指由于疏忽或错误造成程序未能释放已经不再使用的内存。内存泄漏并非指内存在物理上的消失,而是应用程序分配某段内存后,由于设计错误,导致在释放该段内存之前就失去了对该段内存的控制,从而造成了内存的浪费。内存泄漏通常情况下只能由获得程序源代码的程序员才能分析出来。然而,有不少人习惯于把任何不需要的内存使用的增加描述为内存泄漏,即使严格意义上来说这是不准确的。————wikipedia
注:下文中标注的CG是Chrome浏览器中Devtools的【Collect garbage】按钮缩写,表示回收垃圾操作。
意外的全局变量
JavaScript对未声明变量的处理方式:在全局对象上创建该变量的引用(即全局对象上的属性,不是变量,因为它能通过delete删除)。如果在浏览器中,全局对象就是window对象。
如果未声明的变量缓存大量的数据,会导致这些数据只有在窗口关闭或重新刷新页面时才能被释放。这样会造成意外的内存泄漏。
function foo(arg) { bar = "this is a hidden global variable with a large of data"; }
等同于:
function foo(arg) { window.bar = "this is an explicit global variable with a large of data"; }
另外,通过this创建意外的全局变量:
function foo(){ this.variable = "potential accidental global"; } // 当在全局作用域中调用foo函数,此时this指向的是全局对象(window),而不是'undefined' foo();
解决方法:
在JavaScript文件中添加’use strict’,开启严格模式,可以有效地避免上述问题。
function foo(arg){ "use strict" // 在foo函数作用域内开启严格模式 bar = "this is an explicit global variable with a large of data";// 报错:因为bar还没有被声明 }
如果需要在一个函数中使用全局变量,可以像如下代码所示,在window上明确声明:
function foo(arg) { window.bar = "this is a explicit global variable with a large of data"; }
这样不仅可读性高,而且后期维护也方便
谈到全局变量,需要注意那些用来临时存储大量数据的全局变量,确保在处理完这些数据后将其设置为null或重新赋值。全局变量也常用来做cache,一般cache都是为了性能优化才用到的,为了性能,最好对cache的大小做个上限限制。因为cache是不能被回收的,越高cache会导致越高的内存消耗。
console.log
console.log:向web开发控制台打印一条消息,常用来在开发时调试分析。有时在开发时,需要打印一些对象信息,但发布时却忘记去掉console.log语句,这可能造成内存泄露。
在传递给console.log的对象是不能被垃圾回收 :recycle:,因为在代码运行之后需要在开发工具能查看对象信息。所以最好不要在生产环境中console.log任何对象。
实例——>demos/log.html
<!DOCTYPE html> <htmllang="en"> <head> <metacharset="UTF-8"> <metaname="viewport"content="width=device-width, initial-scale=1.0"> <metahttp-equiv="X-UA-Compatible"content="ie=edge"> <title>Leaker</title> </head> <body> <inputtype="button"value="click"> <script> !function (){ function Leaker(){ this.init(); }; Leaker.prototype = { init:function (){ this.name = (Array(100000)).join('*'); console.log("Leaking an object %o: %o", (new Date()),this);// this对象不能被回收 }, destroy:function (){ // do something.... } }; document.querySelector('input').addEventListener('click',function (){ new Leaker(); },false); }() </script> </body> </html>
这里结合Chrome的Devtools–>Performance做一些分析,操作步骤如下:
:warning:注:最好在隐藏窗口中进行分析工作,避免浏览器插件影响分析结果
- 开启【Performance】项的记录
- 执行一次CG,创建基准参考线
- 连续单击【click】按钮三次,新建三个Leaker对象
- 执行一次CG
- 停止记录
可以看出【JS Heap】线最后没有降回到基准参考线的位置,显然存在没有被回收的内存。如果将代码修改为:
!function (){ function Leaker(){ this.init(); }; Leaker.prototype = { init: function (){ this.name = (Array(100000)).join('*'); }, destroy: function (){ // do something.... } }; document.querySelector('input').addEventListener('click', function (){ new Leaker(); }, false); }()
去掉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等都存在类似的问题,这些细节需要特别的关注。
注:本文内容来自互联网,旨在为开发者提供分享、交流的平台。如有涉及文章版权等事宜,请你联系站长进行处理。