Во многих статьях (например, MSDN) было сказано, что циклическая ссылка не может быть очищена в некоторых браузерах, когда это включает Объект DOM и объект JS.
(IE 6 не может сделать, он вообще и IE7 может только сделать это между запросами страницы):
Собственный компонент JavaScript (утечки):
function leak(){
var elem = document.createElement("DIV");
document.body.appendChild(elem);
elem.onclick = function () {
elem.innerHTML = elem.innerHTML + ".";
// ...
};
}
Поскольку onload свойство элемента вернулось к себе посредством закрытия, оно создает циклическую ссылку:
elem [DOM] -> elem.onclick [JS] -> elem [DOM]
Версия jQuery (НЕ протекает):
function leak(){
var elem = $('');
$(document.body).append(elem);
elem.click(function () {
elem.html(elem.html() + ".");
// ...
};
}
В этом случае jQuery мешает утечке произойти во ВСЕХ браузерах, затронутых даже при том, что существует все еще циклическая ссылка:
elem [JS] -> element [DOM] -> elem.onclick [JS] -> elem [JS]
Мой Вопрос: Как jQuery останавливает утечку, если существует все еще циклическая ссылка?
Самая последняя вещь в коде jQuery (перед кодом для Sizzle, его механизма выбора) это (код для предотвращения утечек):
// Prevent memory leaks in IE
// Window isn't included so as not to unbind existing unload events
// More info:
// - http://isaacschlueter.com/2006/10/msie-memory-leaks/
if ( window.attachEvent && !window.addEventListener ) {
window.attachEvent("onunload", function() {
for ( var id in jQuery.cache ) {
if ( jQuery.cache[ id ].handle ) {
// Try/Catch is to handle iframes being unloaded, see #4280
try {
jQuery.event.remove( jQuery.cache[ id ].handle.elem );
} catch(e) {}
}
}
});
}
Когда вы делаете что-либо в jQuery, он сохраняет то, что сделал (то есть функция) и что (то есть элемент DOM). onunload проходит через кеш jQuery, удаляя функции из обработчиков событий собственного внутреннего кеша (где события все равно хранятся, а не на отдельных узлах DOM).
Да, и строка:
if ( window.attachEvent && !window.addEventListener ) {
гарантирует, что он работает только в IE.
переписано для дальнейшего уточнения
На самом деле в предложенном примере есть 2 причины утечки памяти. Первая утечка памяти проявляется из-за создания прямой ссылки на живой узел DOM внутри закрытия. Сборщики мусора (JS и DOM) устаревших браузеров, таких как IE6, не могут обнулить такие ссылки. Следовательно, необходимо обнулять ссылки на узлы в конце вашей функции.
jQuery обходит это по умолчанию благодаря тому, что живые элементы DOM прикрепляются к объекту jQuery в качестве атрибутов/свойств, с которыми вышеупомянутые сборщики мусора не имеют проблем в определении нулевых ссылок. Если объект jQuery имеет нулевые ссылки, он просто очищается, а вместе с ним и его атрибуты/свойства (в данном случае ссылки на живые элементы DOM).
Поэтому, чтобы избежать этой утечки памяти, нужно, чтобы объект поддерживал ссылку на живой узел DOM, а затем ссылался на этот объект в ваших закрытиях. Закрытия будут поддерживать только ссылки на объект, а не на живой элемент DOM, поскольку эта ссылка принадлежит объекту.
// will still leak, but not due to closure references, thats solved.
function noLeak(){
var obj= {
elem: document.createElement('div')
}
obj.elem.onclick = function(){
obj.elem.innerHTML = obj.elem.innerHTML + ".";
}
}
Это очистило наиболее очевидную циркулярную ссылку, но все равно есть утечка (onclick). Узел имеет ссылку на функцию, которая имеет ссылку на объект, который, в свою очередь, имеет ссылку на узел. Единственный способ обойти эту утечку, это изучить опыт конкурса addEvent (многие люди работали над решением этой утечки ;) ). По совпадению, необходимый код можно найти там, так что приношу свои извинения за то, что не предоставил код для этого ;)
Создание обертки для системы событий добавляет еще немного кода, но это необходимо. Основная идея заключается в том, чтобы добавить общий eventHandler, который делегирует событие кэшу/системе событий, хранящей необходимые ссылки. При событии выгрузки кэш очищается, разрушая циклические ссылки, что позволяет сборщикам мусора (JS и DOM) навести порядок в своих собственных углах.
JQuery может гарантировать отсутствие утечек только тогда, когда вы выполняете все свои манипуляции через библиотеку. В jQuery есть процедуры, называемые «empty» и «cleanData», которые вы можете просмотреть, чтобы точно увидеть, что происходит, но в основном код просто отделяет все, о чем он знает, от элементов DOM перед их освобождением. Эта процедура вызывается, когда вы делаете что-то вроде перезаписи содержимого элемента с помощью «.html ()» или «.load ()».
Лично я с осторожностью отношусь к таким терминам, как «гарантия» в подобной ситуации.