避免内存泄漏/使用Javascript

13

我是jQuery的新手。我有点困惑是否安全,或者可能会导致内存泄漏?

这是代码:此方法在某些日期过滤器上调用以获取每个新值。

function preapreTooltip(chart) {
    var tickLength = chart.xAxis[0].tickPositions.length,
        ticks = chart.xAxis[0].ticks,
        tickPositions = chart.xAxis[0].tickPositions;
    for ( var iCntr = 0; iCntr < tickLength; iCntr++) {
         var tickVal = tickPositions[iCntr];

    //.label or .mark or both
    (function(tickVal) { // Is this good practice to call function like this?
        ticks[tickVal].label
        .on('mouseover', function(event) { // Is this good practice to call function like this?
            var label = '', labelCnt=0;
            $(chart.series).each(function(nCntr, series) {
                //business logic for each series
            });

           // calling method to show values in a popup
        });

        ticks[tickVal].label.on('mouseout', function(event) { // Is this good practice to call function like this?
            try {
                hideWrapper(); // hides popup
            } catch (e) {
            // do nothing
            }
        });

    })(tickVal);
  }
}

运行它数千次,在之前和之后检查内存使用情况... - Programming Guy
1
你特别关心哪个部分? - alex
1
您添加的所有注释似乎都与内存或内存泄漏问题无关。您是否担心内存泄漏或内存使用情况?为什么认为需要在hideWrapper()周围加上try/catch? - nnnnnn
@nnnnnn:我对两者都感到担忧。 - Hardik Mishra
1
调用匿名函数是可以的,声明内联函数作为侦听器也可以,但是我会尽量避免在循环内部执行这两种操作,特别是如果这些函数对于每个循环都没有变化,我会将匿名函数编辑为普通代码,并在循环之前将侦听器声明为变量。 - Paul S.
显示剩余2条评论
2个回答

20
虽然编写大型纯JavaScript项目时需要避免特定于浏览器的问题,但使用jQuery等库时应该假定库的设计可以帮助您避免这些问题。然而,考虑到内存泄漏很难跟踪,并且每个特定浏览器的不同版本可能会有所不同,因此最好知道如何通常避免内存泄漏而不是具体地指出:

  1. 如果您的代码被迭代多次,请确保您正在使用的变量可以通过垃圾回收进行丢弃,并且没有被封闭引用占用。
  2. 如果您的代码处理大型数据结构,请确保您有一种方法来删除或使数据无效。
  3. 如果您的代码构造了许多对象、函数和事件监听器,则最好也包括一些解构代码。
  4. 尽量避免直接将JavaScript对象或函数附加到元素作为属性 - 即element.onclick = function(){}
  5. 如有疑问,请在代码完成后进行整理。

您似乎认为调用函数的方式会影响泄漏,但实际上更有可能是这些函数的内容会导致问题。

对于您上面的代码,我的唯一建议是:

  1. 每当使用事件监听器时,请尝试找到一种重用函数的方法,而不是为每个元素创建一个函数。这可以通过使用事件委托(在祖先/父级上捕获事件并将反应委托给event.target或编写一个通用的单一函数来处理您的元素,通常相对于this$(this)

  2. 当需要创建许多事件处理程序时,通常最好将这些事件监听器存储为命名函数,以便在完成后可以再次删除它们。这意味着避免使用匿名函数,就像您正在做的那样。但是,如果您知道只有您的代码处理DOM,则可以回退到使用$(elements).unbind('click')来删除应用于所选元素的所有单击处理程序(无论是否为匿名)使用jQuery。然而,如果使用后一种方法,则绝对最好使用jQuery的事件命名空间功能 - 这样您就知道只删除自己的事件。即$(elements).unbind('click.my_app');。这显然意味着您必须使用$(elements).bind('click.my_app', function(){...});绑定事件。

更具体地说:

自动调用匿名函数

(function(){
  /*
   running an anonymous function this way will never cause a memory
   leak because memory leaks (at least the ones we have control over) 
   require a variable reference getting caught in memory with the 
   JavaScript runtime still believing that the variable is in use, 
   when it isn't - meaning that it never gets garbage collected. 
   This construction has nothing to reference it, and so will be 
   forgotten the second it has been evaluated.
  */
})();

adding an anonymous event listener with jQuery:

var really_large_variable = {/*Imagine lots of data here*/};

$(element).click(function(){
  /*
   Whilst I will admit not having investigated to see how jQuery
   handles its event listeners onunload, I doubt if it is auto-
   matically unbinding them. This is because for most code they
   wont cause a problem, especially if only a few are in use. For
   larger projects though it is a good idea to create some beforeunload
   or unload handlers that delete data and unbind any event handling.
   The reason for this is not to protect against the reference of the
   function itself, but to make sure the references the function keeps
   alive are removed. This is all down to how JS scope works, if you
   have never read up on JavaScript scope... I suggest you do so.

   As an example however, this anonymous function has access to the
   `really_large_variable` above - and will prevent any garbage collection
   system from deleting the data contained in `really_large_variable`
   even if this function or any other code never makes use of it. 
   When the page unloads you would hope that the browser would be able
   to know to clear the memory involved, but you can't be 100% certain
   it will *(especially the likes of IE6/7)* - so it is always best
   to either make sure you set the contents of `really_large_variable` to null
   or make sure you remove your references to your closures/event listeners.
  */
});

tearDowns 和清理

在我的解释中,我主要关注的是当页面不再需要并且用户正在导航离开时。然而,在今天的Ajax内容和高度动态交互界面的世界中,上述问题变得更加重要;GUI不断地创建并销毁元素。

如果您正在创建一个动态的JavaScript应用程序,我强烈建议您使用具有 .tearDown.deconstruct 方法的构造函数,这些方法在代码不再需要时执行。这些方法应该遍历大型自定义对象结构并将其内容变为空,同时删除已经动态创建并且不再使用的事件监听器和元素。在替换元素的内容之前,您还应该使用 jQuery 的 empty 方法 - 这可以从他们的说明中更好地解释:

http://api.jquery.com/empty/

为了避免内存泄漏,jQuery会从子元素中删除数据和事件处理程序等其他构造物,然后再删除元素本身。

如果您想删除元素,而不破坏它们的数据或事件处理程序(以便稍后可以重新添加),请改用 .detach()。

编写具有 tearDown 方法的代码不仅迫使您更加整洁地编写代码(即确保将相关的代码、事件和元素命名空间在一起),而且通常意味着您以更模块化的方式构建代码;这显然对于提高应用程序的未来可靠性、可读性以及任何可能在以后接手项目的人都有很大的好处。


"自动调用匿名函数" 这部分内容不完全正确。虽然一般来说是正确的,但你仍然可以从IEFE(立即执行函数表达式)导出/泄漏函数(作为事件监听器,全局变量等),以保留变量作用域,使其成为闭包。 - Bergi
@Bergi - 你所说的泄漏应该只会由其他内部闭包引起,而不是自动调用的匿名函数或IEFE本身,因为没有对匿名函数存储的引用 -- 你个人能够导致js运行时保留此函数*(因此它的作用域链)*的唯一方法是如果你持有对其arguments.callee的引用。任何其他导致泄漏的子函数都将归因于它自己的作用域链,而不是这个包装函数..但是,如果浏览器决定保持此函数活动,则这是它自己的问题 ;) - Pebbl

1

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接