javascript如何避免内存泄漏

文章来自ajax in action,里面的例子可以让我们更容易理解“避免内存泄漏”

任何程序都有可能发生内存“泄漏”(即申请了系统内存并且在工作完成后没有释放),并且对于使用非托管语言(unmanaged languages)(如C语言)的开发者来说,内存的分配和释放是一个主要的关注点。JavaScript是一种内存托管(memory-managed)的语言,垃圾回收过程能够帮助程序员自动地处理内存的分配和释放。该机制解决了大部分困扰的非托管代码的问题,但是,认为内存托管语言不会产生内存泄漏却是错误的。

垃圾回收进程尝试推断何时可以安全地回收不再使用的变量,通常是通过判定程序是否能够通过变量之间形成的引用网络到达该变量。当确信变量是不可达的,就在它上面标上可以回收的记号,并且在回收器的下一次清理中(可能在未来的任意时刻)释放相关的内存。在托管语言中产生内存泄漏非常简单:只需使用完变量而忘记解除引用。

我们来考虑一个简单的例子,其中定义了一个描述家庭宠物及其主人的对象模型。首先看看主人,以Person对象描述:

function Person(name){
this.name=name;
this.pets=new Array();
}

一个主人可以养一只或者多只宠物。当主人得到了一只宠物,他告诉宠物现在自己是它的主人:

Person.prototype.addPet=function(pet){
this.pets[pet.name]=pet;
if (pet.assignOwner){
pet.assignOwner(this);
}
}

类似的,当主人从他的宠物列表中删除了一只宠物,他告诉宠物自己不再是它的主人:

this.removePet(petName)=function{
var orphan=this.pets[petName];
this.pets[petName]=null;
if (orphan.unassignOwner){
orphan.unassignOwner(this);
}
}

主人在任何时刻都知道谁是他的宠物并且能够通过提供的addPet()removePet()方法来管理宠物列表。主人在领养或不再领养宠物时都会通知该宠物,这基于一个假设,即每个宠物都会遵守这个契约(在JavaScript中,这个契约是隐含的,可以在运行时检查是否遵守了契约)。

宠物多种多样,在这里定义了两种:猫和狗。它们的区别在于对待被领养的态度上,猫并不在意被谁所领养,而狗一生都会伴随领养它的主人(我为这个普遍的观点向动物世界道歉!)。

因此宠物猫的定义看起来像是这样:

function Cat(name){
this.name=name;
}
Cat.prototype.assignOwner=function(person){}
Cat.prototype.unassignOwner=function(person){}

猫对于是否被领养并不感兴趣,因此仅仅提供了契约方法的空实现。

另一方面,我们可以将狗定义为忠实地记得它的主人是谁,即使被遗弃了仍然保持对主人的“引用”(一些狗确实如此!):

function Dog(name){
this.name=name;
}
Dog.prototype.assignOwner=function(person){
this.owner=person;
}
Dog.prototype.unassignOwner=function(person){
this.owner=person;
}

Cat和Dog对象都是Pet的行为恶劣的实现。作为宠物,它们严格依照契约的文字来实现,但是却没有遵循契约的灵魂。在Java或C#的实现中,我们可以明确地定义Pet接口,但是那样仍然不能阻止实现违背契约的灵魂。在现实编程世界中,对象建模者花费了大量的时间防止出现接口的行为恶劣的实现,尽力封堵所有可能被利用的漏洞。

我们来将这个对象模型具体化。在下面的脚本中,我们创建了三个对象:

  1. jim,人(Person)
  2. whiskers,猫(Cat)
  3. fido,狗(Dog)

首先,我们实例化一个人(Person)(第1步):

var jim=new Person("jim");

接下来,我们给了那个人一只宠物猫(第2步)。在对addPet()的调用中以内嵌方式实例化whiskers,因此对猫的特定引用的寿命仅仅与方法调用的时间一样长。然而,jim同样带有一个到whiskers的引用,因此只要jim是可达的,该对象就是可达的,也就是说,直到我们在脚本的最后删除jim:

jim.addPet(new Cat("whiskers"));

我们也给了jim一只宠物狗(第3步)。作为一个全局变量来声明,fido比whiskers稍微多一点优势:

var fido=new Dog("fido");
jim.addPet(fido);

有一天,jim送掉了他的猫(第4步):

jim.removePet("whiskers");

后来,他又送掉了他的狗(第5步)。也许他移民了?

jim.removePet("fido");

我们对jim失去了兴趣并且释放了对他的引用(第6步):

jim=null;

最后,我们又释放了对fido的引用(第7步):

fido=null;

在第6步和第7步之间,我们可能相信已经通过设置jim为null摆脱了他。事实上,他仍然被fido引用并且仍然可以通过代码fido.owner到达。垃圾回收器无法将他释放,只能留下他潜伏在JavaScript引擎的堆空间里,占用着宝贵的内存。直到第7步,当fido声明为null时,jim才变成不可达的,随后内存才能被释放。

在简单的脚本中,这是一个很小的、临时性的问题,但是这个例子展示了表面上很随意的决定对于垃圾回收过程所产生的影响。fido可能在删除jim后没有被直接删除,并且,如果它拥有记住多于一个前任主人的能力,在销毁之前可能会将大批Person对象密封在堆空间中,使其过着暗无天日的生活。如果我们选择以内嵌方式声明fido并且将那只猫声明为全局变量,将不存在任何这类的问题。为了评估fido行为的严重性,我们需要问自己以下的问题:

  1. 当它引用其他已删除的对象时,将会消耗多少内存?我们知道头脑简单的fido一次只能记住一个Person,但是尽管如此,Person可能还包含500个其他仅能通过他自身才能到达的对宠物猫的引用,因此额外的内存消耗可能无法估量。
  2. 额外的内存消耗将会保持多长时间?在这个简单的脚本中,答案是“不是很久”,但是我们可能稍后会在删除jim和删除fido之间添加额外的步骤。而且,JavaScript开发者总是以事件驱动的方式编程,因此,如果删除jim和删除fido发生在分离的事件处理函数中,我们将很难预言一个确定的答案。如果不去做某种类型的用例分析,甚至都无法给出一个概率性的答案。

任何一个问题都不像它们看起来那么容易回答。我们能够做的,就是在编写和修改代码时,对这类问题保持关注,并且执行测试以验证我们的假设是否正确。当编写代码的时候,我们就应该考虑应用的使用模式,而不只是在事后追悔莫及。

以上内容覆盖了内存管理的通用原则。

你的内存侧漏了吗

标题党了,其实是内存泄露。(是泄露!!)以下纯是个人见解,读者自辩

首先什么是内存侧漏呢?

我的理解是内存不能被有效的利用,或者内存在被使用后不能有效的回收再利用,最后造成可利用的内存减少,影响执行效率。

之前写JavaScript的时候并不会特意去关注内存泄露的问题,因为那都是些小case,代码也只有几百行。就算造成内存泄露,也被忽略了。(习惯就是这样慢慢养成的,推荐大家不要如此这般)

javascipt内存泄露是如何产生的?

其实很容易产生,当你这么做以后:

function HelloWorld() {
var robot = document.getElementById('robot');
robot.onclick = function () {
window.alert('Hello World!');
};
}

没有去做robot.onclick = null;robot = null;,其实内存就少量的侧漏了...如何保护呢,我们可以换一种形式:

function sayHelloToWorld() {
window.alert('Hello World!');
}
function HelloWorld() {
var robot = document.getElementById('robot');
robot.onclick = sayHelloToWorld;
}

以上就是循环结构(cyclic structure),解释一下:给ID为robot的DOM元素(以下称DOM:robot)添加一个onclick事件函数,DOM:robot就有了相应的事件处理函数,而JavaScript函数对象中也有了DOM:robot的onclick属性,这就形成了循环结构。循环结构所占用的内存是可以被垃圾回收器(garbage collector)回收的,但是由于某个主流浏览器的回收机制有问题,会加重内存泄露。

使用第二种方法,也就是抛弃了闭包(closure)的方式,内存泄露也就跟着去了...

href执行javascript好吗

昨天在实际应用中发现href执行javascript会影响iframe的载入,具体表现为在IE下,当点击了带有href="javascript:xxx;"这样的链接时,如果有iframe正在载入,那iframe就会停止载入动作。

其实我并没有让href去执行javascript的意思,我的应用习惯的是这样的href="javascript://;" onclick="xxx();",以前一直没发现这个问题,暂时修改成这样onclick="xxx();return false;"来解决问题,养成这个不良的习惯比较糟糕(更糟糕的其实是写inline javascript的习惯)

getElementsByClassName

在firefox 3的DOM improvements中,添加了对于The Web Applications 1.0 (HTML5)中包含的getElementsByClassName的支持。以前我们一般都是通过自己定一个函数来达到目的。

比如:

function GetElementsByClassName(elementName,className) {
var allElements = document.getElementsByTagName(elementName);
var elemColl = new Array();
for (var i = 0; i< allElements.length; i++) {
if (allElements[i].className.search('(^|\\s)' + className + '(\\s|$)') != -1) {
elemColl[elemColl.length] = allElements[i];
}
}
return elemColl;
}

看来大家都在进步,“通过”Acid2测试的IE8也要加油了!

 1 2 3 … 6 Next →