深入浅出JavaScript内存泄漏(1)(2)
Figure 1: 基本的循环引用模型
本模型中引起的泄漏问题基于COM的引用计数。脚本引擎对象会维持对DOM对象的引用,并在清理和释放DOM对象指针前等待所有引用的移除。在我们的示例 中,我们的脚本引擎对象上有两个引用:脚本引擎作用域和DOM对象的expando属性。
当终止脚本引擎时第一个引用会释放,DOM对象引用由于在等待脚 本擎的释放而并不会被释放。你可能会认为检测并修复假设的这类问题会非常的容易,但事实上这样基本的的示例只是冰山一角。你可能会在30个对象链的末尾发 生循环引用,这样的问题排查起来将会是一场噩梦。如果你仍不清楚这种泄漏方式在HTML代码里到底怎样,你可以通过一个全局脚本变量和一个DOM对象来引发并展现它。
- <html>
- <head>
- <script language="JavaScript">
- var myGlobalObject;
- function SetupLeak()
- {
- // First set up the script scope to element reference
- myGlobalObject = document.getElementById("LeakedDiv");
- // Next set up the element to script scope reference
- document.getElementById("LeakedDiv").expandoProperty = myGlobalObject;
- }
- function BreakLeak()
- {
- document.getElementById("LeakedDiv").expandoProperty = null;
- }
- </script>
- </head>
- <body onload="SetupLeak()" onunload="BreakLeak()">
- <div id="LeakedDiv"></div>
- </body>
- </html>
你可以使用直接赋null值得方式来破坏该泄漏情形。在页面文档卸载前赋null值,将会让脚本引擎知道对象间的引用链没有了。现在它将能正常的清理引用 并释放DOM对象。在这个示例中,作为Web开发员的你因该更多的了解了对象间的关系。
作为一个基本的情形,循环引用可能还有更多不同的复杂表现。对基于对象的JavaScript,一个通常用法是通过封装JavaScript对象来扩充DOM对象。在 构建过程中,你常常会把DOM对象的引用放入JavaScript对象中,同时在DOM对象中也存放上对新近创建的JavaScript对象的引用。你的这种应用模式 将非常便于两个对象之间的相互访问。这是一个非常直接的循环引用问题,但是由于使用不用的语法形式可能并不会让你在意。要破环这种使用情景可能变得更加复 杂,当然你同样可以使用简单的示例以便于清楚的讨论。
- <html>
- <head>
- <script language="JavaScript">
- function Encapsulator(element)
- {
- // Set up our element
- this.elementReference = element;
- // Make our circular reference
- element.expandoProperty = this;
- }
- function SetupLeak()
- {
- // The leak happens all at once
- new Encapsulator(document.getElementById("LeakedDiv"));
- }
- function BreakLeak()
- {
- document.getElementById("LeakedDiv").expandoProperty = null;
- }
- </script>
- </head>
- <body onload="SetupLeak()" onunload="BreakLeak()">
- <div id="LeakedDiv"></div>
- </body>
- </html>
更复杂的办法还有记录所有需要解除引用的对象和属性,然后在Web文档卸载的时候统一清理,但大多数时候你可能会再造成额外的泄漏情形,而并没有解决你的 问题。
闭包函数(Closures)
由于闭包函数会使程序员在不知不觉中创建出循环引用,所以它对资源泄漏常常有着不可推卸的责任。而在闭包函数自己被释放前,我们很难判断父函数的参数以及 它的局部变量是否能被释放。实际上闭包函数的使用已经很普通,以致人们频繁的遇到这类问题时我们却束手无策。在详细了解了闭包背后的问题和一些特殊的闭包 泄漏示例后,我们将结合循环引用的图示找到闭包的所在,并找出这些不受欢迎的引用来至何处。






