Стоит посмотреть, как данный шаблон будет выглядеть в HTML. Это может вызвать утечку, используя глобальную переменную и DOM-объект, как показано ниже.
<script type="text/javascript">
var myGlobalObject;
function SetupLeak()
{
// Для начала создадим ссылку из скрипта на DOM-элемент
myGlobalObject = document.getElementById("LeakedDiv");
// Потом установим ссылку из DOM на глобальную переменную
document.getElementById("LeakedDiv").expandoProperty =
myGlobalObject;
}
function BreakLeak()
{
document.getElementById("LeakedDiv").expandoProperty = null;
}
window.onload = SetupLeak;
window.onunload = BreakLeak;
</script>
Чтобы разрушить этот шаблон, можно использовать явное присвоение null тому свойству, которое «течет». Таким образом, при закрытии документа мы сообщаем скриптовому движку, что между DOM-элементом и глобальной переменной нет больше никакой связи. В результате все ссылки будут очищены, и сам DOM-элемент будет освобожден. В таком случае веб-разработчик знает больше о внутренних отношениях между объектами, чем сам скрипт, и может поделиться этой информацией со скриптом.
Более сложный случай
Хотя это только базовый шаблон, для более сложных ситуаций может оказаться нелегко выяснить первопричину утечки. Распространенной практикой по написанию объектно-ориентированного JScript является расширение DOM-элементов путем инкапсуляции их внутри JScript-объекта. В процессе создания такого объекта в большинстве случаев получается ссылка на желаемый DOM-элемент, а затем она сохраняется в только что созданном объекте, при этом экземпляр этого объекта оказывается прикрепленным к DOM-элементу. Таким способом модель приложения всегда получает доступ ко всему что нужно. Проблема заключается в том, что это явная циклическая ссылка, но из-за использования других аспектов языка она может остаться незамеченной. Устранение шаблонов такого рода может быть весьма затруднительным, но вы вполне можете использовать простые методы, обсужденные ранее.
<script type="text/javascript">
function Encapsulator(element)
{
// Создаем элемент
this.elementReference = element;
// Создаем циклическую ссылку
element.expandoProperty = this;
}
function SetupLeak()
{
// Утечка: все в одном
new Encapsulator(document.getElementById("LeakedDiv"));
}
function BreakLeak()
{
document.getElementById("LeakedDiv").expandoProperty = null;
}
window.onload = SetupLeak;
window.onunload = BreakLeak;
</script>
<div id="LeakedDiv"></div>
Более комплексным решением заявленной проблемы может стать создание процесса регистрации ссылок, чтобы соответствующим образом уведомлять приложение, какие из них могут быть удалены. В этом случае по закрытию документа нужно будет пройтись по всем таким элементам и убрать с них ссылки. Однако при неверном подходе можно не только не уменьшить, а даже увеличить количество циклических ссылок, не избавляясь от заявленной проблемы.
Замыкания
Замыкания очень часто являются причиной утечек из-за того, что они создают циклические ссылки практически без ведома программиста. Ведь не так очевидно, что параметры родительской функции могут быть «заморожены» во времени, на них может быть создана ссылка, которая будет удерживаться до полного освобождения замыкания. Это стало уже широко распространенной практикой программирования, и пользователи достаточно часто сталкиваются с нехваткой доступных ресурсов.
В силу того, что данные содержат некоторую часть истории выполнения скрипта, мы не можем так просто с ними расправиться. Давайте сейчас рассмотрим обновленную диаграмму циклических ссылок с учетом модели замыканий, чтобы понять, откуда берутся дополнительные ссылки.
Рис. 7.4. Циклические ссылки с самозамыканием
В случае обычной циклической ссылки у нас есть 2 постоянных объекта, которые содержат ссылки друг на друга, — но замыкания отличаются от этой модели. Вместо создания прямых ссылок они наследуют информацию из пространства порождающих их объектов. В обычном случае локальные переменные в функции и используемые параметры существуют только в течение времени жизни самой функции. В случае замыканий эти переменные и параметры имеют внешнюю ссылку на весь период жизни замыкания, который может быть значительно дольше, чем у породившей ее функции.
В качестве примера можно рассмотреть Объект 2, который был бы освобожден по окончанию вызова функции, в общем случае. Однако после добавления замыкания была создана вторая ссылка на этот параметр, которая не может быть освобождена, пока не будет закрыто замыкание. Если вы прикрепили замыкание к событию, то вам придется его у события в конце концов убрать. Если замыкание прикреплено к расширенному параметру, нужно будет его занулить (приравнять этот параметр к null).