Риск утечки памяти при закрытии JavaScript

Решено

В сети много противоречивой информации по этому поводу тема. Благодаря @John мне удалось выяснить, что замыкания (используемые ниже) не являются причиной утечек памяти и что даже в IE8 они не так распространены, как утверждают люди. На самом деле в моем коде произошла только одна утечка, которую оказалось не так уж сложно исправить.

Отныне мой ответ на этот вопрос будет таким:
Насколько я знаю, единственная утечка в IE8 — это когда события присоединяются/обработчики устанавливаются для глобального объекта. ( window.onload, window.onbeforeunload,...). Чтобы обойти это, см. мой ответ ниже.


ОГРОМНОЕОБНОВЛЕНИЕ:

Теперь я совсем запутался... После некоторого времени копания в статьях и туториалах, как старых, так и новых, я обнаружил, по крайней мере, одно огромное противоречие. В то время как один из THEгуру JavaScript (Дуглас Крокфорд) говорит:

Поскольку IE не может выполнять свою работу и восстанавливать циклы, это ложится на нас. Если мы явно разорвем циклы, то IE сможет вернуть память. По словам Microsoft, замыкания являются причиной утечек памяти. Это, конечно, глубоко неправильно, но это приводит к тому, что Microsoft дает очень плохие советы программистам о том, как справляться с ошибками Microsoft. Оказывается, циклы на стороне DOM легко сломать.Практически невозможно сломать их на стороне JScript.

И поскольку @freakish указал, что мои фрагменты ниже похожи на внутреннюю работу jQuery, я был уверен, что мое решение не вызывает утечек памяти. В то же время я нашел эту страницу MSDN, где раздел Циркулярные ссылки с замыканиямименя особенно заинтересовал. На приведенном ниже рисунке схематично показано, как работает мой код, не так ли:

Circular References with Closures

Единственная разница в том, что у меня есть здравый смысл не привязывать прослушиватели событий к самим элементам.
Всё тот ​​же Дуггисовершенно однозначен: замыкания не являются источником утечек памяти в IE. Это противоречие оставляет меня в неведении относительно того, кто прав.

Я также обнаружил, что проблема утечки не полностью решена и в IE9 (не могу найти ссылку ATM).

И последнее: я также узнал, что IE управляет DOM вне движка JScript, что ставит меня в затруднительное положение, когда я изменяю дочерние элементы на основе 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 = '';
    }
}

Если IE действительно управляет DOM так, как если бы движка JScript там не было, каковы шансы, что элементы option не будут освобождены с помощью этого кода?
Я намеренно добавил этот фрагмент в качестве примера, поскольку в данном случае я передаю переменные, являющиеся частью области замыкания, в качестве аргумента глобальной функции.Я не смог найти никакой документации по этой практике, но, судя по документации, предоставленной Miscrosoft, она должна нарушать любые циклические ссылки, которые могут возникнуть, не так ли?



Предупреждение: длинный вопрос... ( извините)

Я написал несколько довольно больших JavaScript-кодов для выполнения вызовов Ajax в своем веб-приложении. чтобы избежать множества обратных вызовов и событий, я в полной мере использую делегирование и закрытие событий. Теперь я написал функцию, которая заставляет меня задуматься о возможных утечках памяти. Хотя я знаю, что IE > 8 гораздо лучше справляется с замыканиями, чем его предшественники, политика компании все равно поддерживает IE 8.

Ниже я привел пример того, о чем я говорю, здесьвы можете найти аналогичный пример, хотя он использует не 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

Как видите, обратный вызов onreadystatechange— это возвращаемое значение функции, которая предоставляет ссылку на элемент target. когда вызывается обратный вызов. Благодаря делегированию событий мне больше не нужно беспокоиться о событиях, которые могут быть связаны с этим элементом, когда я решаю удалить его из DOM (что я иногда и делаю).
Однако, на мой взгляд,объект вызова функции обратного вызова может оказаться слишком сложным для механизма IE JScript и его сборщика мусора:

Event ==> handler ==> prepareAjax — довольно обычная последовательность вызовов, но аргумент обратного вызова:

[анон. func (аргумент t = цель) возвращает anon. F (имеет доступ к t, который, в свою очередь, ссылается обратно на цель)]
   ===> передается функции обратного вызова anon, вызываемой с использованием метода .apply для объекта xhr, в свою очередь, переменной privateк функции prepareAjax

Я тестировал эту "конструкцию" в FF и хроме. Там это работает просто отлично, но будет ли такой стек вызовов закрытия при закрытии при закрытии, каждый раз передавая ссылку на элемент DOM, проблемой в IE (особенно в версиях до IE9)?


Нет, я не собираюсь использовать jQuery или другие библиотеки. Мне нравится чистый JS, и я хочу узнать как можно больше об этом серьезно недооцененном языке. Фрагменты кода не являются фактическими примерами копирования и вставки, но дают, IMO, хорошее представление о том, как я использую делегирование, замыкания и обратные вызовы в моем сценарии. Поэтому, если какой-то синтаксис не совсем правильный, не стесняйтесь его исправлять, но, конечно, этот вопрос не об этом.

26
задан Elias Van Ootegem 3 July 2012 в 10:25
поделиться