Коллекция мусора JQuery - будет ли это чистым?
Многие статьи (например, msdn) сказали, что круговая ссылка не может быть очищена в некоторых браузерах, когда она включает DOM и объект JS.
(IE 6 вообще не может этого делать, и IE7 может делать это только между запросами страницы):
Javascript Native (утечки):
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 = $('<div></div>');
$(document.body).append(elem);
elem.click(function () {
elem.html(elem.html() + ".");
// ...
};
}
В этом случае jQuery останавливает утечку во всех задействованных браузерах, несмотря на то, что все еще существует круговая ссылка:
elem [JS] -> element [DOM] -> elem.onclick [JS] -> elem [JS]
Мой вопрос: как jQuery останавливает утечку, если есть еще круговая ссылка?
Ответы
Ответ 1
Самая последняя вещь в коде 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
JQuery может только гарантировать отсутствие утечек, когда вы делаете все свои манипуляции через библиотеку. В jQuery есть подпрограммы "empty" и "cleanData", которые вы можете просмотреть, чтобы точно увидеть, что происходит, но в основном код просто отделяет все, что он знает, от элементов DOM, прежде чем освобождать их. Эта процедура вызывается, когда вы делаете что-то вроде перезаписывания содержимого элемента с помощью ".html()" или ".load()".
Лично я довольно осторожен в таких терминах, как "гарантия" в такой ситуации.
Ответ 3
переписано для дальнейшего уточнения
Фактически 2 причины утечек памяти в предлагаемом примере. Первая утечка памяти проявляется из-за создания ссылки direct на живую DOM node внутри закрытия. Сборщики мусора (JS и DOM) старых браузеров, таких как IE6, не могут аннулировать такие ссылки. Следовательно, необходимо исключить ссылки node в конце вашей функции.
jQuery обходит это по умолчанию из-за того, что живые элементы DOM привязаны к объекту jQuery как атрибуты/свойства, с которыми вышеупомянутые сборщики мусора не имеют проблем при определении нулевых ссылок. Если объект jQuery имеет нулевые ссылки, он просто очищается и он атрибуты/свойства (в данном случае ссылки на живые элементы DOM) вместе с ним.
Таким образом, чтобы избежать утечки памяти, необходимо, чтобы объект поддерживал ссылку на живой DOM node, а затем ссылался на объект в ваших замыканиях. Закрытие будет поддерживать ссылки только на объект, а не на живой элемент 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). node имеет ссылку на функцию, которая ссылается на объект, который, в свою очередь, имеет ссылку на node. Единственный способ обойти эту утечку - учиться на конкурсе addEvent (многие люди работали над этой утечкой;)). Coincidentaly, необходимый код можно найти в нем, поэтому мои приложения не предоставляют код для этого;)
Создание оболочки для системы событий добавляет еще несколько кодов, но имеет важное значение. Основная идея состоит в том, чтобы добавить общий eventHandler, который делегирует событие кешу/системе событий, в котором хранятся требуемые ссылки. В событии разгрузки кеш очищается от круговых ссылок, позволяя сборщикам мусора (JS и DOM) убирать собственные углы.