JavaScript 闭包中的内存泄漏风险 [英] Memory leak risk in JavaScript closures

查看:27
本文介绍了JavaScript 闭包中的内存泄漏风险的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

已解决

关于这个主题,网络上有很多相互矛盾的信息.感谢@John,我设法确定闭包(如下所用)不是内存泄漏的原因,而且 - 即使在 IE8 中 - 它们也没有人们声称的那么普遍.事实上,我的代码中只发生了 1 个泄漏,这证明修复并不难.

从现在开始,我对这个问题的回答是:
AFAIK,IE8 泄漏的唯一一次,是在全局对象上附加事件/处理程序时.(window.onload,window.onbeforeunload,...).要解决此问题,请参阅下面的答案.


巨大更新:

我现在完全迷失了......经过一段时间的新旧文章和tuts的挖掘后,我至少留下了一个巨大的矛盾.虽然其中一位 THE JavaScript Guru (Douglas Crockford) 说:

<块引用>

由于 IE 无法完成其工作并回收循环,因此我们必须完成它.如果我们明确打破循环,那么 IE 将能够回收内存.根据微软的说法,闭包是内存泄漏的原因.这当然是非常错误的,但它导致微软在如何处理微软的错误方面给程序员提供了非常糟糕的建议.事实证明,在 DOM 端很容易打破循环.在 JScript 方面几乎不可能打破它们.

正如@freakish 指出的,我下面的代码片段类似于 jQuery 的内部工作方式,我觉得我的解决方案非常安全,不会导致内存泄漏.同时我发现了你可以找到一个类似的比如虽然没有使用ajax,但是使用了setTimeout,结果差不多.(当然,您可以跳过下面的代码,直接回答问题)

我想到的代码是这样的:

function prepareAjax(callback,method,url){方法 = 方法 ||'邮政';回调 = 回调 ||成功;//默认的CB,只记录/提醒响应网址 = 网址 ||getUrl();//设置默认url/currentController/ajaxvar xhr = createXHRObject();//try{}catch etc...xhr.open(方法,网址,真);xhr.setRequestMethod('X-Requested-with','XMLHttpRequest');xhr.setRequestHeader('Content-type','application/x-www-form-urlencoded');xhr.setRequestHeader('接受','*/*');xhr.onreadystatechange = function(){回调.应用(xhr);}返回函数(数据){//在发送之前对数据做一些检查:data.hasOwnProperty('user') 等...xhr.send(数据);}}

所有非常简单的东西,除了 onreadystatechange 回调.在直接绑定处理程序时,我注意到 IE 的一些问题:xhr.onreadystatechange = callback;,因此是匿名函数.不知道为什么,但我发现这是让它发挥作用的最简单方法.

正如我所说,我使用了很多事件委托,因此您可以想象,访问触发 ajax 调用的实际元素/事件可能会很有用.所以我有一些看起来像这样的事件处理程序:

函数handleClick(e){无功目标,父母,数据,我;e = e ||窗口事件;目标 = e.target ||e.srcElement;if (target.tagName.toLowerCase() !== 'input' && target.className !== 'delegateMe'){返回真;}父 = 目标;while(parent.tagName.toLowerCase() !== 'tr'){parent = parent.parentNode;}数据 = {};for(i=0;i

如您所见,onreadystatechange 回调是函数的返回值,它在调用回调时提供对 target 元素的引用.多亏了事件委托,当我决定从 DOM 中删除它时(我有时会这样做),我不再需要担心可能绑定到该元素的事件.
然而,在我看来,回调函数的调用对象对于 IE 的 JScript 引擎及其垃圾收集器来说可能太过分了:

<块引用>

事件==>处理程序==>prepareAjax 是一个很正常的调用序列,但是回调参数:

[匿名.func(参数 t = target)返回匿名.F(可以访问 t,而 t 又将引用回目标)]
===>传递给匿名回调函数,使用 .apply 方法调用 xhr 对象,然后将 private 变量传递给 prepareAjax 函数

我已经测试了这个构造";在 FF 和铬.它在那里工作得很好,但是这种在关闭后关闭时的调用堆栈,每次传递对 DOM 元素的引用都会在 IE 中成为问题(尤其是 IE9 之前的版本)?


不,我不会使用 jQuery 或其他库.我喜欢纯 JS,并且想尽可能多地了解这种被严重低估的语言.代码片段不是实际的复制粘贴示例,但提供了 IMO,很好地表示了我如何在整个脚本中使用委托、闭包和回调.因此,如果某些语法不太正确,请随时纠正它,但这当然不是这个问题的内容.

解决方案

我曾经与 Microsoft 内的 JavaScript 前程序经理一起在非浏览器 EcmaScript (err.. JScr... JavaScript) 项目上工作.我们对关闭进行了一些冗长的讨论.最后,重点是它们更难 GC,并非不可能.我必须阅读 DC 关于 MS 是如何错误"导致内存泄漏的讨论——因为在 IE 的旧实现中,闭包肯定有问题,因为它们很难用 MS 实现进行垃圾收集强>.我觉得奇怪的是,一个雅虎人会试图告诉 MS 架构师,他们的代码的一个已知问题在其他地方.尽管我很欣赏他的工作,但我看不出他有什么依据.

请记住,您在上面引用的文章是指 IE6,因为在撰写本文时 IE7 仍在大力开发中.

顺便说一句——谢天谢地,IE6 已经死了(不要让我挖掘葬礼图片).尽管如此,不要忘记它的遗产......我还没有看到有人在发布的第一天就它不是世界上最好的浏览器提出可信的论点——问题是他们赢得了浏览器战争.因此,这相当于他们历史上最大的错误之一——他们事后解雇了这支球队,并且该球队停滞了近 5 年.多年来,IE 团队只有 4 到 5 个人在做错误修复,造成了巨大的人才流失并大大落后于曲线.当他们重新雇用团队并意识到他们所处的位置时,他们已经落后了好几年,因为处理一个没有人真正理解的单一代码库的额外工作.这是我作为公司内部人员的观点,但与该团队没有直接关系.

也请记住,IE 从未针对闭包进行过优化,因为没有 ProtoypeJS(见鬼,没有 Rails),而且 jQuery 在 Resig 的脑海中几乎没有一丝闪光.

在撰写本文时,他们的目标仍然是具有 256 兆 RAM 的机器,这些机器也没有报废.

在让我读完你整本书的问题之后,我认为给你上一节历史课是公平的.

最后,我的观点是您引用的材料已经过时了.是的,避免在 IE6 中使用闭包,因为它们会导致内存泄漏——但什么在 IE6 中没有?

最后,这是 MS 已经解决并将继续解决的问题.您将进行某种程度的关闭,即使在当时也是如此.

我知道他们围绕 IE8 在这方面做了大量工作(因为我的不起眼的项目使用了非当时的标准 JavaScript 引擎),并且这项工作一直持续到 IE9/10.StatCounter (http://gs.statcounter.com/) 表明 IE7 的市场份额从一年前的 6% 下降到 1.5%,并且在开发新"站点时,IE7 变得越来越不相关.您还可以针对引入 JavaScript 支持的 NetScape 2.0 进行开发,但这只是稍微不那么愚蠢.

真的......不要为了一个不再存在的引擎而试图过度优化.

Solved

There's a lot of contradictory information on the web, regarding this subject. Thanks to @John, I managed to work out that the closures (as used below) aren't the cause of memory leaks, and that -even in IE8- they're not that common as people claim. In fact there was only 1 leak that occurred in my code, which proved not that difficult to fix.

From now on, my answer to this question will be:
AFAIK, the only time IE8 leaks, is when events are attached/handlers are set on the global object. (window.onload,window.onbeforeunload,...). To get around this, see my answer below.


HUGE UPDATE:

I'm completly lost now... After some time digging through articles and tuts both old and new, I'm left with at least one humongous contradiction. While one of THE JavaScript Guru's (Douglas Crockford) says:

Since IE is unable to do its job and reclaim the cycles, it falls on us to do it. If we explicitly break the cycles, then IE will be able to reclaim the memory. According to Microsoft, closures are the cause of memory leaks. This is of course deeply wrong, but it leads to Microsoft giving very bad advice to programmers on how to cope with Microsoft's bugs. It turns out that it is easy to break the cycles on the DOM side. It is virtually impossible to break them on the JScript side.

And as @freakish pointed out that my snippets below are similar to jQuery's internal workings I felt pretty secure about my solution not causing memory leaks. At the same time I found this MSDN page, where the section Circular References with Closures was of particular interest to me. The figure below is pretty much a schematic representation of how my code works, isn't it:

The only difference being that I have the common sense of not attaching my event listeners to the elements themselves.
All the same Douggie is quite unequivocal: closures are not the source of mem-leaks in IE. This contradiction leaves me clueless as to who's right.

I've also found out that the leak issue isn't completely solved in IE9 either (can't find the link ATM).

One last thing: I've also come to learn that IE manages the DOM outside the JScript engine, which puts me in a spot of bother when I change the children of a <select> element, based on an ajax request:

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>';
    }
}

If IE really does manage the DOM as though the JScript engine weren't there, what are the odds that the option elements aren't deallocated using this code?
I've deliberately added this snippet as an example, because in this case I'm passing variables that are part of the closure scope as an argument to a global function. I couldn't find any documentation on this practice, but based on the documentation provided by Miscrosoft, it should break any circular references that might occur, doesn't it?



Warning: lengthy question... (sorry)

I've written a couple of fairly large JavaScripts to make Ajax calls in my web application. in order to avoid tons of callbacks and events, I'm taking full advantage of event delegation and closures. Now I've written a function that has me wondering as to possible memory leaks. Though I know IE > 8 deals with closures a lot better then its predecessors, it is company policy to support IE 8 all the same.

Below I've provided an example of what I'm on about, here you can find a similar example, though it doesn't use ajax, but a setTimeout, the result is pretty much the same. (You can, of course skip the code below, to the question itself)

The code I have in mind is this:

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);
    }
}

All pretty straight-forward stuff, except for the onreadystatechange callback. I noticed some issues with IE when binding the handler directly: xhr.onreadystatechange = callback;, hence the anonymous function. Don't know why, but I found this to be the easiest way to make it work.

As I said, I'm using a lot of event delegation, so you can imagine it may prove useful to have access to the actual element/event that fired the ajax call. So I have some event handlers that look like this:

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);
}

As you can see, the onreadystatechange callback is the return value of a function, that provides the reference to the target element when the callback is called. Thanks to event delegation, I no longer have to worry about events that might be bound to that element, when I decide to remove it from the DOM (which I do sometimes).
To my mind, however, the call object of the callback function might prove too much for IE's JScript engine and its garbage collector:

Event ==> handler ==> prepareAjax is a pretty normal call sequence, but the callback argument:

[anon. func (argument t = target) returns anon. F (has access to t which in turn refs back to target)]
   ===> passed to a anon callback function, called using .apply method to the xhr object, in turn a private variable to the prepareAjax function

I've tested this "construction" in FF and chrome. It works just fine there, but would this kind of callstack of closure upon closure upon closure, on each occasion passing a reference to a DOM element be an issue in IE (especially versions prior to IE9)?


No, I'm not going to use jQuery or other libs. I like pure JS, and want to know as much as I can about this seriously underrated language. The code snippets are not actual copy-paste examples, but provide, IMO, a good representation of how I'm using delegation, closures and callbacks throughout my script. So if some syntax isn't quite right, feel free to correct it, but that's not what this question is about, of course.

解决方案

I used to work with the ex-program manager for JavaScript within Microsoft, on a non-browser EcmaScript (err.. JScr... JavaScript) project. We had some lengthy discussions about closures. In the end, the point is that they are harder to GC, not impossible. I'd have to read DC's discussion of how MS is 'wrong' that closures cause memory leaks -- for in IE's older implementations, closures were certainly problematic, because they were very hard to garbage collect with the MS implementation. I find it strange that a Yahoo guy would try to tell the MS architects that a known issue with their code was somewhere else. As much as I appreciate his work, I don't see what basis he had there.

Remember, the article you reference above refers to IE6, as IE7 was still under heavy development at the time of its writing.

As an aside -- thank god, IE6 is dead (don't make my dig up the funeral pictures). Although, don't forget its legacy... I've yet to see anyone make a credible argument that it wasn't the finest browser in the world on day 1 of its release -- the problem was that they won the browser war. And so, in what amounts to one of the larger blunders of their history -- they fired the team afterwards and it sat stagnant for nearly 5 years. The IE team was down to 4 or 5 guys doing bug fixes for years, creating a huge brain drain and falling behind the curve dramatically. By the time they rehired the team and realized where they were, they were years behind because of the added cruft of dealing with a monolothic codebase that nobody really understood anymore. That's my perspective as an internal in the company, but not directly tied to that team.

Remember too, IE never optimized for closures because there was no ProtoypeJS (heck, there was no Rails), and jQuery was barely a glimmer in Resig's head.

At the time of the writing, they were also still targeting machines with 256 megs of RAM, which also hadn't been end-of-life'd.

I thought it only fair to hand you this history lesson, after making me read your entire book of a question.

In the end, my point is that you're referencing material which is hugely dated. Yes, avoid closures in IE6, as they cause memory leaks -- but what didn't in IE6?

In the end, it is a problem that MS has addressed and continues to address. You're going to make some level of closures, that was the case, even back then.

I know that they did heavy work in this area around IE8 (as my unmentionable project used the non-at-the-time standard JavaScript engine), and that work has continued into IE9/10. StatCounter (http://gs.statcounter.com/) suggests that IE7 is down to a 1.5% market share, down from 6% a year ago, and in developing 'new' sites, IE7 becomes less and less relevant. You can also develop for NetScape 2.0, which introduced JavaScript support, but that would be only slightly less silly.

Really... Don't try to over-optimize for the sake of an engine which doesn't exist anymore.

这篇关于JavaScript 闭包中的内存泄漏风险的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆