Ошибка утечки памяти при закрытии JavaScript


Answers

Правильно, потратив некоторое время на этот инструмент IEJSLeaksDetector , я обнаружил, что то, о чем я говорил в своем первоначальном вопросе, НЕ ПРИНИМАЕТ СЛУЧАЯ MEM . Однако появилась одна утечка, которая всплыла. К счастью, мне удалось найти решение:

У меня есть главный сценарий, а внизу, есть старая школа:

window.onload = function()
{
    //do some stuff, get URI, and call:
    this['_init' + uri[0].ucFirst()](uri);//calls func like _initController
    //ucFirst is an augmentation of the String.prototype
}

Это вызывает утечку в IE8, что я не могу исправить с помощью обработчика window.onbeforeunload . Кажется, вам нужно избегать привязки обработчика к глобальному объекту. Решение заключается в закрытии и прослушивании событий, это немного фаворит, но вот что я в итоге сделал:

(function(go)
{//use closure to avoid leaking issue in IE
    function loader()
    {
        var uri = location.href.split(location.host)[1].split('/');
        //do stuff
        if (this['_init' + uri[0].ucFirst()] instanceof Function)
        {
            this['_init' + uri[0].ucFirst()](uri);
        }
        if (!(this.removeEventListener))
        {
            this.detachEvent('onload',loader);//(fix leak?
            return null;
        }
        this.removeEventListener('load',loader,false);
    }
    if (!(go.addEventListener))
    {
        go.attachEvent('onload',loader);//(IE...
    }
    else
    {
        go.addEventListener('load',loader,false);
    }
})(window);

Таким образом, событие нагрузки (on) отключается непосредственно перед возвратом обработчика window.load , в соответствии с инструментом IEJSLeaksDetector в моем приложении отсутствуют утечки. Я доволен этим. Я надеюсь, что этот фрагмент полезен для одного из вас - если у кого-то есть предложения по улучшению этого подхода, не стесняйтесь делать это!

Приветствия, и спасибо всем вам, кто пережил трудности чтения и испытания моего шара выше!

PS: в случае, если кто-то заботится, вот метод ucFirst String:

if (!(String.prototype.ucFirst))
{
    String.prototype.ucFirst = function()
    {
        "use strict";
        return this.charAt(0).toUpperCase() + this.slice(1);
    };
}
Question

решаемая

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

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

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

Теперь я полностью потерян ... Через некоторое время, пробираясь сквозь статьи и старая и новая, у меня осталось хотя бы одно огромное противоречие. Хотя один из авторов JavaScript Guru (Douglas Crockford) говорит:

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

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

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

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

Последнее : я также узнал, что IE управляет DOM за пределами механизма JScript, что заставляет меня беспокоиться, когда я меняю дочерние элементы элемента <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, она должна разорвать любые циклические ссылки, которые могут возникнуть, не так ли?

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

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

Ниже я привел пример того, о чем я говорю, here вы можете найти аналогичный пример, хотя он не использует 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 вызов onreadystatechange является возвращаемым значением функции, которая предоставляет ссылку на target элемент при вызове обратного вызова. Благодаря делегированию событий мне больше не нужно беспокоиться о событиях, которые могут быть связаны с этим элементом, когда я решил удалить его из DOM (что я иногда делаю).
Однако, на мой взгляд, объект вызова функции обратного вызова может оказаться слишком большим для IE JScript-движка и его сборщика мусора:

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

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

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

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




Links