内存泄漏风险在JavaScriptclosures
解决了
关于这个问题,networking上有很多矛盾的信息。 感谢@John,我设法解决了closures(如下面所使用的)不是内存泄漏的原因,即使在IE8中,它们也不像人们声称的那样常见。 事实上,在我的代码中只发生了一次泄漏,事实certificate这并不难解决。
从现在起,我对这个问题的回答是:
AFAIK,唯一一次IE8泄漏,是事件被附加/处理程序设置在全局对象上。 ( window.onload
, window.onbeforeunload
,…)。 为了解决这个问题,请参阅下面的答案。
巨大的更新:
我现在完全失去了…经过一段时间的挖掘,新旧文章,我留下了至less一个巨大的矛盾。 虽然JavaScript大师之一(道格拉斯·克罗克福德)说:
由于IE无法完成工作并回收周期,因此我们需要这样做。 如果我们明确地打破周期,那么IE将能够回收内存。 据微软称,closures是导致内存泄漏的原因。 这当然是非常错误的,但是这会导致微软给程序员提出非常糟糕的build议,说明如何应对微软的错误。 事实certificate,很容易打破在DOM方面的周期。 在JScript方面几乎是不可能的。
正如@freakish指出,我的下面的代码片段类似于jQuery的内部工作,我觉得我的解决scheme非常安全,不会导致内存泄漏。 同时,我find了这个MSDN页面 ,其中“ Circular References with Closures
”一节对我特别感兴趣。 下图几乎是我的代码如何工作的示意图,是不是:
唯一的区别是,我没有把我的事件监听器附加到元素本身的常识。
所有相同的Douggie是非常明确的:闭包不是IE中的mem漏洞的来源。 这个矛盾让我无法确定谁是对的。
我也发现泄漏问题在IE9中也没有完全解决(无法find链接ATM)。
最后一件事 :我也学会了IE在JScript引擎之外pipe理DOM,当我根据ajax请求更改<select>
元素的子元素时,这让我有点麻烦:
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浏览器确实像使用JScript引擎那样pipe理DOM,那么使用这个代码不会解除分配选项元素的可能性有多大?
我故意添加这个片段作为例子,因为在这种情况下,我将作为闭包的一部分的variables作为parameter passing给全局函数。 我找不到任何这种做法的文档,但基于Miscrosoft提供的文档,它应该打破任何可能发生的循环引用,不是吗?
警告 :冗长的问题…( 对不起 )
我写了几个相当大的JavaScript来在我的Web应用程序中进行Ajax调用。 为了避免大量的callback和事件,我正在充分利用事件的委托和closures。 现在我写了一个函数,让我想知道可能的内存泄漏。 虽然我知道IE> 8的处理与其前辈相比要好很多,但支持IE 8的公司政策也一样。
下面我提供了一个我在做什么的例子, 在这里你可以find一个类似的例子,虽然它不使用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
callback。 当直接绑定处理程序时,我注意到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
callback是一个函数的返回值,它在onreadystatechange
callback时提供对target
元素的引用。 感谢事件代表团,当我决定从DOM(我有时候)中删除它的时候,我不再担心可能会绑定到这个元素的事件。
但是,在我看来,callback函数的调用对象对于IE的JScript引擎及其垃圾收集器来说可能太多了:
事件==>处理程序==> prepareAjax是一个非常正常的调用序列,但callback参数:
[匿名。 func(参数t = target)返回匿名。 F(有权访问t,然后再返回到目标)]
===>传递给一个匿名callback函数,使用.apply方法调用xhr对象,然后将一个私有variables赋给prepareAjax函数
我已经在FF和Chrome中testing了这个“构造”。 它在那里工作得很好,但是这种closures后封闭的调用堆栈,每次通过引用DOM元素都是IE中的问题(尤其是IE9之前的版本)?
不,我不打算使用jQuery或其他库。 我喜欢纯粹的JS,并想尽可能多地了解这个严重低估的语言。 这些代码片段并不是实际的复制粘贴示例,但是IMO提供了我在整个脚本中如何使用委托,closures和callback的一个很好的表示。 所以,如果某些语法不是很正确的话,可以随意纠正它,但这当然不是这个问题的关键。
我曾经在Microsoft的非浏览器EcmaScript(err.JScr … JavaScript)项目中使用JavaScript的ex-program manager。 关于closures我们有一些冗长的讨论。 最后,重点是他们更难GC,不是不可能的。 我不得不阅读DC关于MS如何“错误”的闭包导致内存泄漏的讨论 – 因为在IE的较早的实现中,闭包当然是有问题的,因为它们很难在MS实现中被垃圾收集。 我觉得奇怪的是,一个雅虎的人会试图告诉MS架构师,他们的代码的一个已知问题是在别的地方。 尽pipe我很欣赏他的工作,但我不明白他在那里有什么基础。
请记住,您上面引用的文章是指IE6,因为IE7在写作时仍处于重大的发展阶段。
另外 – 感谢上帝,IE6已经死了(不要让我挖出葬礼图片)。 虽然,不要忘记它的遗产…我还没有看到有人提出一个可信的说法,说它不是世界上最好的浏览器发布的第一天 – 问题是他们赢得了浏览器战争。 所以,他们历史上最大的失误之一 – 他们之后解雇了这个团队,并且停滞了将近5年。 IE团队多达4或5人,多年来一直在修复bug,创造了巨大的人才stream失,并大大落后于曲线。 当他们重新join团队并意识到他们在哪里的时候,他们已经落后了多年,因为处理一个没有人真正理解的单一代码库的问题已经变得越来越重要了。 这是我作为公司内部的angular度,但并不直接与这个团队挂钩。
还记得,IE从来没有优化闭包,因为没有ProtoypeJS(哎呀,没有Rails),jQuery在Resig的头上几乎没有一丝光芒。
在撰写本文的时候,他们还在瞄准256MB内存的机器,这些内存也没有达到使用寿命。
让我读完你的整本书之后,我认为把这个历史课程交给你是公平的。
最后,我的观点是你所指的材料是过时的。 是的,避免在IE6中closures,因为它们会导致内存泄漏 – 但IE6中没有?
最后,MS已经解决并继续解决这个问题。 你会做出某种程度的封闭,就是这样,甚至在那个时候。
我知道他们在IE8的这个领域做了很多工作(因为我的不可指望的项目使用了非标准的JavaScript引擎),这个工作一直延续到IE9 / 10。 StatCounter(http://gs.statcounter.com/)表示,IE7的市场份额低于一年前的6%,而在开发“新”网站时,IE7的相关性也越来越低。; 您也可以开发引入JavaScript支持的NetScape 2.0,但这只会稍微愚蠢一点。
真的…不要为了一个不存在的发动机而过度优化。
是的,花了一些时间与IEJSLeaksDetector
工具后,我发现我在原来的问题中谈到的东西不要导致漏水 。 但是,有1泄漏popup。 谢天谢地,我设法find了一个解决scheme:
我有一个主要的脚本,底部是一所老学校:
window.onload = function() { //do some stuff, get URI, and call: this['_init' + uri[0].ucFirst()](uri);//calls func like _initController //ucFirst is an augmentation of the String.prototype }
这导致在IE8泄漏,我不能用window.onbeforeunload
处理程序修复。 看来你必须避免绑定处理程序的全局对象。 解决scheme在于闭包和事件监听器,这有点儿难,但这是我最终做的:
(function(go) {//use closure to avoid leaking issue in IE function loader() { var uri = location.href.split(location.host)[1].split('/'); //do stuff if (this['_init' + uri[0].ucFirst()] instanceof Function) { this['_init' + uri[0].ucFirst()](uri); } if (!(this.removeEventListener)) { this.detachEvent('onload',loader);//(fix leak? return null; } this.removeEventListener('load',loader,false); } if (!(go.addEventListener)) { go.attachEvent('onload',loader);//(IE... } else { go.addEventListener('load',loader,false); } })(window);
这样,根据IEJSLeaksDetector工具,(on)加载事件在window.load
处理程序返回之前解除绑定,在我的应用程序中没有泄漏。 我很高兴。 我希望这个片段对你们中的一个有用处 – 如果有人有改进这种方法的build议,请不要犹豫!
欢呼声,感谢所有经历过阅读和尝试运球的烦恼!
PS:如果有人关心,这里是ucFirststring方法:
if (!(String.prototype.ucFirst)) { String.prototype.ucFirst = function() { "use strict"; return this.charAt(0).toUpperCase() + this.slice(1); }; }