утечка памяти addEventListener из-за фреймов


У меня есть скрипт GreaseMonkey, который работает на сайте, использующем фреймы как неотъемлемую часть своего интерфейса. Этот сценарий пропускает память, как решето, и я считаю, что это связано с моим использованием addEventListener в одном из фреймов. Проще говоря, я подключаю различные прослушиватели событий, затем фрейм перезагружается, и я подключаю прослушиватели событий, а затем фрейм перезагружается, снова и снова в течение сотен или, возможно, тысяч итераций, когда вы взаимодействуете с различными элементами в этом фрейме или другие. К концу его Firefox ушел от ~300M памяти до целых 2G (или падает, прежде чем он туда доберется).

Я где-то читал, что полная перезагрузка страницы позволит подпрограммам сборки мусора FireFox включиться и восстановить всю память из осиротевших обработчиков событий, и, конечно же, когда я нажимаю F5 после того, как мой скрипт работает некоторое время, в течение примерно 10 секунд память возвращается до 300 м. К сожалению, это нарушает другой фрейм на сайте (очень популярный чат окно), так что, хотя это, кажется, подтверждает мои подозрения, что addEventListener виноват, на самом деле это не вариант как решение.

Есть ли что-то еще, что я могу сделать, чтобы освободить память должным образом, не заставляя полную страницу обновить?

(в настоящее время используется GM 1.5 и FF 17, но проблема существует начиная с GM 0.8/FF 4 или около того.)

1 3

1 ответ:

Не видя вашего полного сценария, или Краткий, самодостаточный, компилируемый пример, мы не можем быть уверены в том, что происходит. Возможно, что addEventListener не является проблемой.

Вот некоторые стратегии для лучшего кода с меньшим количеством утечек памяти:

  1. Встроенные/анонимные функциичасто являются виновниками, особенно с обработчиками событий.

    Бедный / Дырявый:

    elem.onclick = function () {/*do something*/};
    elem.addEventListener ("click", function() {/*do something*/}, false);
    $("elem").click ( function () {/*do something*/} );
    

    Не протекает и к тому же легче поддерживать:

    elem.onclick = clickHandler;
    elem.addEventListener ("click", clickHandler, false);
    $("elem").click (clickHandler);
    
    function clickHandler (evt) {
        /*do something*/
    }
    
    Обратите внимание, что для userscripts вы должны избегать onclick и т. д. в любом случае.
  2. Точно так же не используйте JS для атрибутов HTML. Например, не использовать <span onclick="callSomeFunction()"> и т. д.

  3. Сверните код, который выполняется в iframes, только до того кода, который вы явно хотите.

    1. используйте @include, @exclude, и @match директивы, чтобы блокировать как можно больше нежелательных iframes.
    2. Оберните весь код, который не нужно запускать в iframes в блоке примерно так:

      if (window.top === window.self) {
        // Not in a frame
      }
      
  4. Не используйте innerHTML.

  5. Для множества элементов или элементов, которые приходят и уходят с AJAX, не используйте addEventListener() или jQuery .bind(), .click(), и т.д.
    Это реплицирует слушателя через, потенциально, тысячи узлов.

    Используйте jQuery .on(). Таким образом, слушатель присоединяется только один раз и срабатывает соответствующим образом через пузырение. (Обратите внимание, что в некоторых редких случаях .on() может быть заблокирован страница написана на javascript.)

    В вашем случае, вероятно, вы хотите что-то вроде:

    $(document).on ("click", "YOUR ELEM SELECTOR", clickHandler);
    
    function clickHandler (evt) {
        /*do something*/
    }
    
  6. Чтобы избежать неожиданных циклических ссылок или потерянных элементов, используйте jQuery для добавления или удаления элементов, а не прямые методы DOM, такие как createElement(), appendChild(), и т.д.
    jQuery разработан/протестирован, чтобы минимизировать такие вещи.

  7. Остерегайтесь чрезмерного использования GM_setValue(). Он легко может использовать множество глобальных ресурсов или вызвать сбой экземпляра скрипта.

    1. для значения в той же области, используйте localStorage.
    2. не используйте GM_setValue() для хранения чего-либо, кроме строк. Для всего остального используйте сериализатор, например GM_SuperValue. Даже невинные на вид целые числа могут привести к сбою default GM_setValue().
    3. вместо того, чтобы хранить множество маленьких переменных, может быть лучше обернуть их в объект и хранить , что с одним из сериализаторов.


  8. Всегда проверяйте возвращаемые значения и предполагайте, что элементы могут быть отсутствует:
    Это плохо (и, увы, типично):

    $("selector").text($("selector").text().match(/foo=([bar]+)/)[1]);
    

    Лучше:

    var salesItemDiv    = $("selector");
    var fooMatch        = salesItemDiv.text ().match (/\bfoo\s*=\s*([bar]+)\b/i);
    if (fooMatch  &&  fooMatch.length > 1) {
        salesItemDiv.text ( fooMatch[1] );
    }
    

    Возможно, за ним последуют:

    salesItemDiv = fooMatch = null;
    

    См.ниже.

  9. Остерегайтесь рекурсивных / встроенных setTimeout() вызовов. Используйте setInterval() для повторного выбора времени. Как и в случае с обработчиками событий, не используйте встроенные / анонимные функции.

  10. Запустите код через JSLint.

  11. Избегайте eval() и auto / hidden eval() призывы .

  12. Установите переменные в null, когда вы закончите с ними. смотрите это, например.

  13. Ссылка: " знаете ли вы, что может вызвать утечку памяти в JavaScript?"

  14. Дополнительное чтение на утечках памяти JS

  15. Mozilla Производительность: Инструменты Утечки