内存泄漏:JavaScript 中的内存泄漏



JavaScript 中内存泄漏

JavaScript 是种垃圾收集式语言这就是说内存是根据对象创建分配给该对象并会在没有对该对象引用时由浏览器收回JavaScript 垃圾收集机制本身并没有问题但浏览器在为 DOM 对象分配和恢复内存方式上却有些出入

Internet Explorer 和 Mozilla Firefox 均使用引用计数来为 DOM 对象处理内存在引用计数系统每个所引用对象都会保留个计数以获悉有多少对象正在引用它如果计数为零该对象就会被销毁其占用内存也会返回给堆虽然这种=kgb _disibledevent=>解决方案来说还算有效但在循环引用方面却存在些盲点

循环引用问题何在?

当两个对象互相引用时就构成了循环引用其中每个对象引用计数值都被赋 1在纯垃圾收集系统中循环引用问题不大:若涉及到两个对象中个对象被任何其他对象引用那么这两个对象都将被垃圾收集而在引用计数系统这两个对象都不能被销毁原因是引用计数永远不能为零在同时使用了垃圾收集和引用计数混合系统中将会发生泄漏系统不能正确识别循环引用在这种情况下DOM 对象和 JavaScript 对象均不能被销毁清单 1 显示了在 JavaScript 对象和 DOM 对象间存在个循环引用


清单 1. 循环引用导致了内存泄漏


<html>
<body>
<script type=\"text/javascript\">
document.write(\"circular references between JavaScript and DOM!\");
var obj;
window.onload = function{
obj=document.getElementById(\"DivElement\");
document.getElementById(\"DivElement\").expandoProperty=obj;
obj.bigString= Array(1000).join( Array(2000).join(\"XXXXX\"));
};
</script>
<div id=\"DivElement\">Div Element</div>
</body>
</html>




如上述清单中所示JavaScript 对象 obj 拥有到 DOM 对象引用表示为 DivElement而 DOM 对象则有到此 JavaScript 对象引用由 expandoProperty 表示可见JavaScript 对象和 DOM 对象间就产生了个循环引用由于 DOM 对象是通过引用计数管理所以两个对象将都不能销毁

种内存泄漏模式

在清单 2 中通过外部 myFunction 创建循环引用同样JavaScript 对象和 DOM 对象间循环引用也会导致内存泄漏


清单 2. 由外部引起内存泄漏


<html>
<head>
<script type=\"text/javascript\">[Page]
document.write(\" object s between JavaScript and DOM!\");
function myFunction(element)
{
this.elementReference = element;
// This code forms a circular reference here
//by DOM-->JS-->DOM
element.expandoProperty = this;
}
function Leak {
//This code will leak
myFunction(document.getElementById(\"myDiv\"));
}
</script>
</head>
<body _disibledevent=> <div id=\"myDiv\"></div>
</body>
</html>




正如这两个代码举例所示循环引用很容易创建在 JavaScript 最为方便编程结构的:闭包中循环引用尤其突出


JavaScript 中闭包

JavaScript 过人的处在于它允许嵌套个嵌套内部可以继承外部参数和变量并由该外部私有清单 3 显示了内部个举例


清单 3. 个内部


function parentFunction(paramA)
{
var a = paramA;
function childFunction
{
a + 2;
}


childFunction;
}




JavaScript 开发人员使用内部来在其他中集成小型实用如清单 3 所示此内部 childFunction 可以访问外部 parentFunction 变量当内部获得和使用其外部变量时就称其为个闭包

了解闭包

考虑如清单 4 所示代码片段


清单 4. 个简单闭包


<html>
<body>
<script type=\"text/javascript\">
document.write(\"Closure Demo!!\");
window.onload=
function closureDemoParentFunction(paramA)
{
var a = paramA;
function closureDemoInnerFunction (paramB)
{
alert( a +\" \"+ paramB);
};
};
var x = closureDemoParentFunction(\"outer x\");
x(\"inner x\");
</script>
</body>[Page]
</html>




在上述清单中closureDemoInnerFunction 是在父 closureDemoParentFunction 中定义内部当用外部 x 对 closureDemoParentFunction 进行外部变量 a 就会被赋值为外部 x会返回指向内部 closureDemoInnerFunction 指针该指针包括在变量 x 内

外部 closureDemoParentFunction 本地变量 a 即使在外部返回时仍会存在点区别于 C/C 这样编程语言在 C/C返回本地变量也将不复存在在 JavaScript 中 closureDemoParentFunction 时候带有属性 a 范围对象将会被创建该属性包括值 paramA又称为“外部 x”同样地当 closureDemoParentFunction 返回时它将会返回内部 closureDemoInnerFunction包括在变量 x 中

由于内部持有到外部变量引用所以这个带属性 a 范围对象将不会被垃圾收集当对具有参数值 inner x x 进行即 x(\"inner x\")将会弹出警告消息表明 “outer x innerx”

清单 4 简要解释了 JavaScript 闭包闭包功能非常强大原因是它们使内部在外部返回时也仍然可以保留对此外部变量访问不幸闭包非常易于隐藏 JavaScript 对象 和 DOM 对象间循环引用


闭包和循环引用

在清单 5 中可以看到个闭包在此闭包内JavaScript 对象(obj)包含到 DOM 对象引用(通过 id \"element\" 被引用)而 DOM 元素则拥有到 JavaScript obj 引用这样建立起来 JavaScript 对象和 DOM 对象间循环引用将会导致内存泄漏


清单 5. 由事件处理引起内存泄漏模式


<html>
<body>
<script type=\"text/javascript\">
document.write(\"Program to illustrate memory leak via closure\");
window.onload=function outerFunction{
var obj = document.getElementById(\"element\");
obj.onclick=function innerFunction{
alert(\"Hi! I will leak\");
};
obj.bigString= Array(1000).join( Array(2000).join(\"XXXXX\"));
// This is used to make the leak signicant
};
</script>
<button id=\"element\">Click Me</button>
</body>
</html>



避免内存泄漏

幸好JavaScript 中内存泄漏是可以避免当确定了可导致循环引用模式的后正如我们在上述章节中所做那样您就可以开始着手应对这些模式了这里我们将以上述 由事件处理引起内存泄漏模式 为例来展示 3种应对已知内存泄漏方式

种应对 清单 5 中内存泄漏解决方案是让此 JavaScript 对象 obj 为空这会显式地打破此循环引用如清单 6 所示 [Page]


清单 6. 打破循环引用


<html>
<body>
<script type=\"text/javascript\">
document.write(\"Avoiding memory leak via closure by ing the circular
reference\");
window.onload=function outerFunction{
var obj = document.getElementById(\"element\");
obj.onclick=function innerFunction
{
alert(\"Hi! I have avoided the leak\");
// Some logic here
};
obj.bigString= Array(1000).join( Array(2000).join(\"XXXXX\"));
obj = null; //This s the circular reference
};
</script>
<button id=\"element\">\"Click Here\"</button>
</body>


</html>




清单 7 是通过添加另个闭包来避免 JavaScript 对象和 DOM 对象间循环引用


清单 7. 添加另个闭包


<html>
<body>
<script type=\"text/javascript\">
document.write(\"Avoiding a memory leak by adding another closure\");
window.onload=function outerFunction{
var anotherObj = function innerFunction
{
// Some logic here
alert(\"Hi! I have avoided the leak\");
};
(function anotherInnerFunction{
var obj = document.getElementById(\"element\");
obj.onclick=anotherObj });
};
</script>
<button id=\"element\">\"Click Here\"</button>
</body>
</html>




清单 8 则通过添加另来避免闭包本身进而阻止了泄漏


清单 8. 避免闭包自身


<html>
<head>
<script type=\"text/javascript\">
document.write(\"Avoid leaks by avoiding closures!\");
window.onload=function[Page]
{
var obj = document.getElementById(\"element\");
obj.onclick = doesNotLeak;
}
function doesNotLeak
{
//Your Logic here
alert(\"Hi! I have avoided the leak\");
}

</script>
</head>
<body>
<button id=\"element\">\"Click Here\"</button>
</body>
</html>
//---------------------------------------------------------------------------------------------------------------------------------------- 首先我们知道数据传递有两种by value & by reference
javascript中对象传递传得是reference .
那下面例子介绍说明
<html>
<body>
<script type=\"text/javascript\">
//这句不用看了
document.write(\"circular references between JavaScript and DOM!\");
//创建个指针 放在栈内存中;
var obj;
//这个也忽略
window.onload = function{
// ---------------------------------------
// 1.创建个dom对象document.getElementById(\"DivElement\")
// 2.将内存中obj空指针指向 dom对象地址 (reference 引用)
// 3.dom对象重新指向本身 (reference)
// 4. obj = null then dom !=null end dom 被复制了份而且永远存在
obj=document.getElementById(\"DivElement\");
document.getElementById(\"DivElement\").expandoProperty=obj;
// -------------------------------
// obj.aa (dom).aa
// then obj = null; 这里obj = null 只是重新将obj指向 用JS对象介绍说明好比这样
// var a = String(1);
// var b = a;
// alert(b);
// b = null;
// alert(a);
// 我们会发现a 还是存在
// 因此 obj = null 只是重新将obj重新指向新reference ;
// 同时 内存中dom 对象property并未消除 导致上面说内存泄露
// 因此在编程中指针控制重要性就体现出来了
// 可参考 Prentice Hall PTR 出版 C设计语言(美)Brian W.Kernighan,Dennis M.Ritchie [作者]
// ( 非大学教材->tan hao qiang 版 ) 对指针操作有很好介绍说明[Page]
// 有关javascipt by value & by reference 看下段介绍说明
obj.aa = Array(100000).join(\"x\");

};

</script>
<div id=\"DivElement\">Div Element</div>
</body>
</html>

1.传值(by value)
变量值被复制出份,和原来值将不相干,也就是说即使新值被修改,原来值也不会改变,在JavaScript中基本类型都是传值.
function testPassValue
{

var m=1;
var n=2;

//将m,n值复制份,传递到passValue


passValue(m,n);
alert(m); //将是原有
}

function passValue(a,b)
{
a = a+b; //改变a值,这里a只是原有值份copy
alert(a);
}
输出结果:
3
1
2.传引用(by reference).

引用本身复制份传给function,引用指向对象并没有被复制传递(java中也是如此),在function中,如果改变了对象属性值,由于和原来引用指向是同个对象,因此在通过原来引用访问到将是修改过值;

但是如果只是在function中将引用指向个新对象,将不会改变原对象值,改变只是复制份引用.

function testPassValue
{
var date = Date(2007,06,22);
alert(date.getDate); //输出为 22

//将date引用本身复制份,传递到passReference,注意date所指向对象并没有被复制
passReference(date);
alert(date.getDate); //输出为12

//同上
changeReference(date);
alert(date.getDate); //输出还为12
}

function passReference(da)
{

//由于da和原引用指向是同个对象,在function外,通过原有引用访问到将是对象日期属性值,将是修改的后值.
da.Date(12);
}

function changeReference(da)
{

//此时da引用实际上是原引用份copy,将引用本身重新赋值,将不会影响原引用
da= Date(2007,06,23);
//将da引用指向个新对象,此时原引用指向还是原来对象
alert(da.getDate); // 输出为23

//--------------------------------------------------------------------------------------------------------

原始值(primitive value)是存储在栈中简单数据段也就是说它们值直接存储在变量访问位置
引用值(reference value)是存储在堆中对象也就是说存储在变量处值是个指针指向存储对象内存处
为变量赋值时ECMAScript解释必须判断该值是原始类型还是引用类型
要实现这解释则需尝试判断该值是否为ECMAScript原始类型的即Und、Null、Boolean和String型
由于这些原始类型占据空间是固定所以可将它们存储在较小内存区域——栈中这样存储便于迅速查寻变量
在许多语言中串都被看作引用类型而非原始类型长度是可变ECMAScript打破了这传统(http://www.w3c.com)[Page]
如果个值是引用类型那么它存储空间将从堆中分配由于引用值大小会改变所以不能把它放在栈中否则会降低变量查寻速度相反放在变量栈空间中值是该对象存储在堆中地址地址大小是固定所以把它存储在栈中对变量性能无任何负面影响
ECMAScript有5种原始类型(primitive type)即Und、Null、Boolean、Number和String

所以我如何还认为var obj 是在栈中......

就是说


数字 布尔值 null 地址(reference) undefiend


对象

Tags:  程序内存泄漏 什么是内存泄漏 vc内存泄漏 内存泄漏

延伸阅读

最新评论

发表评论