【问题标题】:Avoid Memory Leak / Usage Javascript避免内存泄漏/使用 Javascript
【发布时间】:2012-10-22 19:45:27
【问题描述】:

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

【问题讨论】:

  • 运行几千次,检查前后内存使用情况...
  • 您特别关注哪一部分?
  • 使用事件委托而不是单独分配每个事件处理程序。
  • 您添加的所有 cmets 似乎都与内存或内存泄漏问题有关。您是否担心内存泄漏或内存使用?为什么你认为你需要在hideWrapper() 周围尝试/捕捉?
  • 调用匿名函数很好,将函数内联声明为侦听器也是如此,但是我会尽量避免在内部循环中执行任何一个,特别是如果每​​个循环的函数都没有改变,我会将匿名代码编辑为普通代码,并在循环之前将侦听器声明为变量。

标签: javascript jquery memory-leaks


【解决方案1】:

虽然在编写大型纯 JavaScript 项目时需要避免特定于浏览器的问题,但在使用诸如 jQuery 之类的库时,应该假定该库的设计可以帮助您避免这些问题。但是,考虑到内存泄漏很难追踪,并且特定浏览器的每个不同版本的行为可能会有所不同 - 知道如何通常避免内存泄漏要比具体说明要好得多:

  1. 如果您的代码被多次迭代,请确保您使用的变量可以被垃圾收集丢弃,并且不会被闭包引用绑定。
  2. 如果您的代码正在处理大型数据结构,请确保您有办法删除或取消数据。
  3. 如果您的代码构造了许多对象、函数和事件侦听器 - 最好也包含一些解构代码。
  4. 尽量避免将 javascript 对象或函数作为属性直接附加到元素 - 即 element.onclick = function(){}
  5. 如有疑问,请务必在代码完成后进行整理。

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

使用上面的代码,我唯一的建议是:

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

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

  3. 绑定事件

更具体:

自动调用匿名函数

(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.
  */
})();

使用 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.
  */
});

拆解与解构

关于我的解释,我已将注意力集中在何时不再需要该页面并且用户正在导航离开。然而,在当今 ajaxed 内容和高度动态界面的世界中,上述内容变得更加相关;不断创建和丢弃元素的 GUI。

如果您正在创建一个动态 javascript 应用程序,我无法强调让构造函数具有 .tearDown.deconstruct 方法在不再需要代码时执行的重要性。这些应该逐步完成大型自定义对象构造并使其内容无效,以及删除已动态创建且不再使用的事件侦听器和元素。在替换元素的内容之前,您还应该使用 jQuery 的 empty 方法 - 这可以用他们的话更好地解释:

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

为避免内存泄漏,jQuery 在删除元素本身之前从子元素中删除其他构造,例如数据和事件处理程序。

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

使用 tearDown 方法进行编码不仅会迫使您更整洁地进行编码(即确保将相关的代码、事件和元素放在一起命名空间),这通常意味着您以更简洁的方式构建代码模块化时尚;显然,这对于您的应用程序的未来验证、可读性以及以后可能接管您项目的任何其他人来说要好得多。

【讨论】:

  • 关于“自动调用匿名函数”的部分并不完全正确。虽然一般来说是正确的,但您仍然可以从 IEFE 导出/泄漏函数(作为事件侦听器、全局变量等),以便保留变量范围,使其成为闭包
  • @Bergi - 您所说的泄漏应该只由其他内部闭包引起,而不是 auto called anonymous function 或 IEFE 本身,因为不会存储对匿名函数的引用——唯一的如果您持有对 arguments.callee 的引用,您可以亲自使 js 运行时保留此函数(因此它是作用域链)。任何其他导致泄漏的子函数都将归结为它自己的作用域链,而不是这个包装函数。但是如果浏览器决定让这个函数保持活动状态,那就是它自己的问题;)
【解决方案2】:

这是一篇使用 Chrome 或 Safari 检测内存泄漏的优秀文章:http://javascript.crockford.com/memory/leak.html

它使用了开发者工具面板中不为人知的功能。

有趣且非常有用!

编辑

这不是很好的链接(但仍然有用)。这是一个:http://gent.ilcore.com/2011/08/finding-memory-leaks.html

【讨论】:

    猜你喜欢
    • 1970-01-01
    • 2018-04-08
    • 2013-06-24
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 2010-12-22
    相关资源
    最近更新 更多