Solved
关于这个主题,网络上有很多相互矛盾的信息。感谢@John,我设法发现闭包(如下所用)并不是内存泄漏的原因,而且 - 即使在 IE8 中 - 它们并不像人们声称的那么常见。事实上,我的代码中只发生了 1 处泄漏,事实证明修复起来并不困难。
从现在开始,我对这个问题的回答是:
AFAIK,IE8 泄漏的唯一时间是在全局对象上附加事件/设置处理程序时。 (window.onload
,window.onbeforeunload
,...)。要解决这个问题,请参阅下面我的回答。
HUGE UPDATE:
我现在完全迷失了......经过一段时间的挖掘新旧文章和评论后,我至少留下了一个巨大的矛盾。而其中之一THEJavaScript 大师 (Douglas Crockford) 说:
由于 IE 无法完成其工作并回收循环,因此我们需要这样做。如果我们明确地打破循环,那么 IE 将能够回收内存。根据微软的说法,闭包是导致内存泄漏的原因。这当然是严重错误的,但它导致微软就如何应对微软的错误向程序员提供了非常糟糕的建议。事实证明,打破 DOM 端的循环是很容易的。在 JScript 端破坏它们几乎是不可能的。
正如 @freakish 指出的,我下面的代码片段与 jQuery 的内部工作原理类似,我对我的解决方案不会导致内存泄漏感到非常放心。同时我发现这个 MSDN 页面 http://msdn.microsoft.com/en-us/library/bb250448(v=vs.85).aspx,其中部分Circular References with Closures
我特别感兴趣。下图几乎是我的代码如何工作的示意图,不是吗:
唯一的区别是,我有一个常识,即不将事件侦听器附加到元素本身。
全部都一样Douggie非常明确:闭包不是 IE 中内存泄漏的根源。这种矛盾让我不知道谁是对的。
我还发现 IE9 中的泄漏问题也没有完全解决(找不到链接 ATM)。
最后一件事:我还了解到 IE 在 JScript 引擎之外管理 DOM,这让我在更改某个对象的子级时遇到麻烦。<select>
元素,基于 ajax 请求:
function changeSeason(e)
{
var xhr,sendVal,targetID;
e = e || window.event;//(IE...
targetID = this.id.replace(/commonSourceFragment/,'commonTargetFragment');//fooHomeSelect -> barHomeSelect
sendVal = this.options[this.selectedIndex].innerHTML.trim().substring(0,1);
xhr = prepareAjax(false,(function(t)
{
return function()
{
reusableCallback.apply(this,[t]);
}
})(document.getElementById(targetID)),'/index/ajax');
xhr({data:{newSelect:sendVal}});
}
function reusableCallback(elem)
{
if (this.readyState === 4 && this.status === 200)
{
var data = JSON.parse(this.responseText);
elem.innerHTML = '<option>' + data.theArray.join('</option><option>') + '</option>';
}
}
如果 IE 确实管理 DOM,就好像 JScript 引擎不存在一样,那么使用此代码未释放选项元素的可能性有多大?
我特意添加了这个片段作为示例,因为在这种情况下,我将作为闭包范围一部分的变量作为参数传递给全局函数。我找不到有关此实践的任何文档,但根据 Miscrosoft 提供的文档,它应该会破坏可能发生的任何循环引用,不是吗?
Warning: 长问题...(sorry)
我已经编写了几个相当大的 JavaScript 来在我的 Web 应用程序中进行 Ajax 调用。为了避免大量的回调和事件,我充分利用了事件委托和关闭。现在我编写了一个函数,让我想知道可能的内存泄漏。尽管我知道 IE > 8 比其前辈更好地处理闭包,但支持 IE 8 仍然是公司政策。
下面我提供了一个我正在讨论的例子,here http://jsfiddle.net/6xsRv/你可以找到一个类似的例子,虽然它没有使用ajax,但是使用了setTimeout,结果几乎是一样的。 (当然,您可以跳过下面的代码,直接讨论问题本身)
我想到的代码是这样的:
function prepareAjax(callback,method,url)
{
method = method || 'POST';
callback = callback || success;//a default CB, just logs/alerts the response
url = url || getUrl();//makes default url /currentController/ajax
var xhr = createXHRObject();//try{}catch etc...
xhr.open(method,url,true);
xhr.setRequestMethod('X-Requested-with','XMLHttpRequest');
xhr.setRequestHeader('Content-type','application/x-www-form-urlencoded');
xhr.setRequestHeader('Accept','*/*');
xhr.onreadystatechange = function()
{
callback.apply(xhr);
}
return function(data)
{
//do some checks on data before sending: data.hasOwnProperty('user') etc...
xhr.send(data);
}
}
所有非常简单的东西,除了onreadystatechange
打回来。我注意到直接绑定处理程序时 IE 存在一些问题:xhr.onreadystatechange = callback;
,因此是匿名函数。不知道为什么,但我发现这是最简单的方法。
正如我所说,我使用了大量事件委托,因此您可以想象,访问触发 ajax 调用的实际元素/事件可能会很有用。所以我有一些如下所示的事件处理程序:
function handleClick(e)
{
var target,parent,data,i;
e = e || window.event;
target = e.target || e.srcElement;
if (target.tagName.toLowerCase() !== 'input' && target.className !== 'delegateMe')
{
return true;
}
parent = target;
while(parent.tagName.toLowerCase() !== 'tr')
{
parent = parent.parentNode;
}
data = {};
for(i=0;i<parent.cells;i++)
{
data[parent.cells[i].className] = parent.cells[i].innerHTML;
}
//data looks something like {name:'Bar',firstName:'Foo',title:'Mr.'}
i = prepareAjax((function(t)
{
return function()
{
if (this.readyState === 4 && this.status === 200)
{
//check responseText and, if ok:
t.setAttribute('disabled','disabled');
}
}
})(target));
i(data);
}
如您所见,onreadystatechange
回调是函数的返回值,它提供对target
调用回调时的元素。感谢事件委托,当我决定从 DOM 中删除该元素时(我有时会这样做),我不再需要担心可能绑定到该元素的事件。
然而,在我看来,回调函数的调用对象对于 IE 的 JScript 引擎及其垃圾收集器来说可能太多了:
事件==>处理程序==>prepareAjax是一个非常正常的调用序列,但是回调参数:
[匿名。 func(参数 t = 目标)返回 anon。 F(可以访问 t,而 t 又引用回目标)]
===> 传递给一个匿名回调函数,使用 .apply 方法调用 xhr 对象,依次调用private变量到prepareAjax函数
我已经在 FF 和 chrome 中测试了这个“结构”。它在那里工作得很好,但是这种每次关闭时都传递对 DOM 元素的引用的调用堆栈在 IE 中是否会出现问题(尤其是 IE9 之前的版本)?
不,我不会使用 jQuery 或其他库。我喜欢纯 JS,并且想尽可能多地了解这种被严重低估的语言。这些代码片段不是实际的复制粘贴示例,但在我看来,很好地说明了我如何在整个脚本中使用委托、闭包和回调。因此,如果某些语法不太正确,请随时纠正它,但这当然不是这个问题的目的。