решаемые
В Интернете много противоречивой информации по этому вопросу. Благодаря @John мне удалось понять, что закрытие (как используется ниже) не является причиной утечки памяти, и что даже в IE8 они не так распространены, как утверждают люди. Фактически в моем коде произошла только одна утечка, что оказалось не так сложно исправить.
Отныне мой ответ на этот вопрос будет:
AFAIK, единственный раз утечка IE8, - это когда привязаны события/обработчики на глобальном объекте. (window.onload
window.onbeforeunload
,...). Чтобы обойти это, см. Мой ответ ниже.
ОГРОМНОЕ ОБНОВЛЕНИЕ:
Теперь я полностью потерялся... Через некоторое время, пробираясь сквозь статьи и старая и новая, у меня осталось хотя бы одно огромное противоречие. Хотя один из авторов JavaScript (Douglas Crockford) говорит:
Поскольку IE не может выполнять свою работу и возвращать циклы, нам приходится это делать. Если мы явно разрываем циклы, тогда IE сможет восстановить память. По словам Microsoft, закрытие является причиной утечки памяти. Это, конечно, глубоко неправильно, но это приводит к тому, что Microsoft дает очень плохие советы программистам о том, как справиться с ошибками Microsoft. Оказывается, легко разбить циклы на стороне DOM. Их практически невозможно разбить на стороне JScript.
И как @freakish отметил, что мои фрагменты ниже похожи на внутренние работы jQuery, я чувствовал себя довольно уверенно в своем решении, не вызывая утечки памяти. В то же время я нашел эту страницу MSDN, где раздел Circular References with Closures
представлял для меня особый интерес. На рисунке ниже представлено схематическое представление о том, как работает мой код, не так ли:
Единственное различие заключается в том, что у меня есть здравый смысл не прикреплять слушателей событий к самим элементам.
Все тот же Douggie совершенно недвусмыслен: закрытие не является источником mem-утечек в IE. Это противоречие оставляет меня невежественным относительно того, кто прав.
Я также выяснил, что проблема утечки не полностью решена в IE9 (не удается найти ATM).
Последнее: я также узнал, что 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 все-таки.
Ниже я привел пример того, о чем я говорю, здесь вы можете найти аналогичный пример, хотя он не используйте 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
- это возвращаемое значение функции, которое предоставляет ссылку на элемент target
при вызове обратного вызова. Благодаря делегированию событий мне больше не нужно беспокоиться о событиях, которые могут быть связаны с этим элементом, когда я решил удалить его из DOM (что я иногда делаю).
Однако, на мой взгляд, объект вызова функции обратного вызова может оказаться слишком большим для IE JScript engine и его сборщика мусора:
Событие == > обработчик == > prepareAjax - довольно нормальная последовательность вызовов, но аргумент обратного вызова:
[Anon. func (аргумент t = target) возвращает anon. F (имеет доступ к t, который в свою очередь возвращает обратно к цели)]
=== > передается функции обратного вызова anon, вызываемой с использованием метода .apply для объекта xhr, в свою очередь, частной переменная к функции prepareAjax
Я тестировал эту "конструкцию" в FF и хром. Он прекрасно работает там, но будет ли этот вид стоп-кода закрытия после закрытия после закрытия, при каждом переходе ссылки на элемент DOM будет проблемой в IE (особенно версии до IE9)?
Нет, я не буду использовать jQuery или другие библиотеки. Мне нравится чистый JS, и я хочу знать как можно больше об этом серьезно недооцененном языке. Фрагменты кода не являются фактическими примерами копирования-вставки, но предоставляют IMO, хорошее представление о том, как я использую делегирование, закрытие и обратные вызовы во всем моем script. Поэтому, если какой-то синтаксис не совсем прав, не стесняйтесь его исправить, но это не тот вопрос, о котором идет речь, конечно.