JavaScript 闭包中的内存泄漏风险

2024-04-09

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,并且想尽可能多地了解这种被严重低估的语言。这些代码片段不是实际的复制粘贴示例,但在我看来,很好地说明了我如何在整个脚本中使用委托、闭包和回调。因此,如果某些语法不太正确,请随时纠正它,但这当然不是这个问题的目的。


我曾经与 Microsoft 内的 JavaScript 前程序经理一起工作,参与一个非浏览器 EcmaScript(呃.. 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 的相关性越来越低。您还可以针对 NetScape 2.0 进行开发,该版本引入了 JavaScript 支持,但这只是稍微不那么愚蠢。

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

本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

JavaScript 闭包中的内存泄漏风险 的相关文章

  • 关闭选项卡时要求确认[关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 当我在某些浏览器上关闭页面时 我希望出现一个消息框 并询问我是否真的要关闭页面 有两个按钮 如果我单击No那么这个标签就不会被关闭 我怎样
  • 从数据 URI 解码 QR 码

    我尝试从数据 uri 中解码二维码 var dataUri data image gif base64 R0lGODdh9gD2AIAAAAAAAP ywAAAAA9gD2AAAC decodeQrCode dataUri cb 我已经尝试
  • TypeError: props.render 不是一个函数(React hook 形式)

    我将方法作为我用react hook form制作的形式的道具传递 当从react hook form添加控制器时 它给了我 TypeError props render不是一个函数 我在网上找不到任何解决方案 因此感谢任何帮助 impor
  • 如何抑制窗口鼠标滚轮滚动...?

    我正在开发嵌入页面中的画布应用程序 我有它 因此您可以使用鼠标滚轮放大绘图 但不幸的是 这会滚动页面 因为它是文章的一部分 当我在 dom 元素上滚动鼠标滚轮时 是否可以阻止鼠标滚轮在窗口上滚动 附加鼠标滚轮 不是 Gecko DOMMou
  • Node.js:如何在检索数据(块)时关闭响应/请求

    我正在用 node js 构建一个应用程序 它加载多个页面并分析内容 因为 node js 发送块 所以我可以分析这些块 如果一个块包含例如索引 nofollow 我想关闭该连接并继续其余部分 var host example com to
  • 从未用 @flow 标记的导入文件中获取类型定义

    TL DR我怎么告诉flow从未声明的导入模块导入类型定义 flow 加长版 流接缝能够从不使用流语法的文件中派生类型 请参阅示例 示例文件 flow js if Math random lt 0 5 var y hello else va
  • 在requestAnimationFrame中使用clearRect不显示动画

    我正在尝试在 HTML5 画布上做一个简单的 javascript 动画 现在我的画布是分层的 这样当我收到鼠标事件时 背景层不会改变 但带有头像的顶层会移动 如果我使用 requestAnimationFrame 并且不清除屏幕 我会看到
  • 在javascript中解析json - 长数字被四舍五入

    我需要解析一个包含长数字的 json 在 java servlet 中生成 问题是长数字被四舍五入 当执行这段代码时 var s x 6855337641038665531 var obj JSON parse s alert obj x
  • 在 webpack 2.x 中使用 autoprefixer 和 postcss

    如何使用autoprefixer使用 webpack 2 x 以前 它曾经是这样的 module loaders test scss loader style css sass postcss postcss gt return autop
  • 通过 CDN 使用 Dojo 时如何加载自定义 AMD 模块?

    我正在使用 google 的 CDN 并尝试使用他们的加载程序加载我自己的 AMD 模块 我知道我做错了什么 但我被困住了 有任何想法吗
  • Firefox 书签探索未超过 Javascript 的第一级

    我已经编写了一些代码来探索我的 Firefox 书签 但我只获得了第一级书签 即我没有获得文件夹中的链接 e g 搜索引擎 雅虎网站 谷歌网站 在此示例中 我只能访问 Search engines 和 google com 不能访问 yah
  • Javascript 数组到 VBScript

    我有一个使用 Javascript 构建的对象数组 我需要使用 VBScript 读取它 如下例所示 我找不到在 VbScript 代码中循环遍历数组的方法myArray object 这个例子是我的问题的简化 我无法更改页面的默认语言 这
  • 如何获取给定 DOM 元素的所有定义的 CSS 选择器?

    如何使用 jQuery 获取给定 DOM 元素的所有定义的 CSS 选择器 定义后 我的意思是在应用于任何样式表的所有 CSS 选择器document 在某种程度上 这类似于 FireBug 实现的功能 其中显示所选 DOM 元素的所有应用
  • 为什么我不能在 AngularJS 中使用 data-* 作为指令的属性名称?

    On the t他的笨蛋 http plnkr co edit l3KoY3 p preview您可以注意到属性名称模式的奇怪行为data 在指令中 电话 Test of data named attribute br
  • Javascript转换时区问题

    我在转换当前时区的日期时间时遇到问题 我从服务器收到此日期字符串 格式为 2015 10 09T08 00 00 这是中部时间 但是当我使用 GMT 5 中的 new Date strDate 转换此日期时间时 它返回给我的信息如下 这是不
  • Javascript 纪元时间(以天为单位)

    我需要以天为单位的纪元时间 迄今为止 我已经看到过有关如何翻译它的帖子 但几天后就没有了 我对纪元时间很不好 我怎么能得到这个 我需要以天为单位的纪元时间 我将解释为您想要自纪元以来的天数 纪元本身是第 0 天 或第 1 天的开始 无论您如
  • Safari 支持 JavaScript window.onerror 吗?

    我有一个附加到 window onerror 的函数 window onerror function errorMsg url line window alert asdf 这在 firefox chrome 和 IE 中工作正常 但在 s
  • JQuery 图像上传不适用于未来的活动

    我希望我的用户可以通过帖子上传图像 因此 每个回复表单都有一个上传表单 用户可以通过单击上传按钮上传图像 然后单击提交来提交帖子 现在我的上传表单可以上传第一个回复的图像 但第二个回复的上传不起作用 我的提交过程 Ajax 在 php 提交
  • 如何获取浏览器视口中当前显示的内容

    如何获取当前正在显示长文档的哪一部分的指示 例如 如果我的 html 包含 1 000 行 1 2 3 9991000 并且用户位于显示第 500 行的中间附近 那么我想得到 500 n501 n502 或类似的内容 显然 大多数场景都会比
  • 如何从图像输入中获取 xy 坐标?

    我有一个输入设置为图像类型

随机推荐