
Ajax в действии
.pdf•#*«» часть in. Создание профессиональных Ajax-приложений
ботают в управляемой среде, контейнере, который предоставляет некото! рьге функции и запрещает пользоваться другими. Это несколько меняет общую картину.
В главе 4 мы разделили Aj ах-приложение, на три подсистемы — модель I представление и контроллер. Модель, как правило, состоит из обычных объектов JavaScript. Разработчик определяет эти объекты и предусматривает создание их конкретных экземпляров. Представление в основном состоит из узлов DOM — объектов, которые браузер предоставляет JavaScript-программе. Контроллер является связующим звеном между моделью и представлением. Реализуя контроллер, необходимо уделять особое внимание управлению памятью.
Устранение циклических ссылок
В разделе 4.3.1 мы рассмотрели принцип обработки событий, позволяющий связать объектную модель предметной области (часть подсистемы модели) с узлами DOM (частью подсистемы представления). Попробуем применить данный подход для задач, связанных с управлением памятью. Ниже приведен конструктор модели предметной области, соответствующей кнопке.
function Button(value,domEl){ this,domEl=domEl;
this.value=value;
this.domEl.buttonObj=this;
this.domEl.onclick=this.clickHandler;
}
Заметьте, что между элементом DOM domEl и сахмим объектом создаются двусторонние ссылки. Ниже приведена функция обработки событий, на которую ссылается конструктор
Button.prototype.clickHandler=function(event){ var buttonObj=this.buttonObj;
var value=(buttonObj && buttonObj.value) ? buttonObj.value : "unknown value";
alert(value);
}
Как вы помните, функция обработки события вызывается не объектом Button, а узлом DOM; этот факт влияет на формирование контекста. Ссылка представления на модель нужна нам для того, чтобы обеспечить взаимодействие с уровнем модели. В данном случае мы читаем свойство value. В других ситуациях вызываются функции объектов, составляющих модель предметной области.
Объект типа Button доступен до тех пор, пока хотя бы один из остальных доступных объектов содержит ссылку на него. Точно так же элемент DOM доступен до тех пор, пока на него ссылаются другие доступные элементы. Если элемент DOM присоединен к главному документу, он остается доступен, даже если в программе нет на него ни одной ссылки. Таким образом, если элемент DOM, являющийся частью документа, связан с объектом Button, последний не будет удален системой "сборки мусора" до тех нор, пока мы явным образом не разорвем эту связь.
Глава 8 Производительность приложения 329
Если модели предметной области взаимодействуют со структурой DOM, зозможно существование локального объекта JavaScript, на который не ссылается ни одна глобальная переменная, определенная в программе, но который, тем не менее, доступен посредством DOM. Для того, чтобы создать условия для удаления этого объекта подсистемой "сборки мусора", нам надо раписать простую функцию очистки. (Поскольку такая функция вызывается Бручную, ее можно рассматривать как шаг в сторону объектов C++.) Для объекта Button эта функция будет иметь следующий вид:
Button.prototype.cleanUp=function(){
this.domEl.buttonObj=null;
this.domEl=null;
}
Первая строка в теле функции удаляет ссылку узла DOM на объект. Вторая строка удаляет ссылку объекта на узел DOM. Данная функция не разрушает узел, а лишь присваивает переменным, в которых ранее хранились ссылки, значение null. В данном случае узел DOM передавался конструктору нашего объекта в виде параметра, поэтому мы не отвечаем за его дальнейшую судьбу. В других ситуациях такая ответственность может быть возложена на нас, поэтому рассмотрим вопрос управления этим узлом.
Управление элементами DOM
В приложениях Ajax, в особенности тех, в которых используются сложные модели предметной области, узлы DOM создаются и включаются в состав дерева документа из программы. В классических Web-приложениях формирование узлов DOM чаще всего производится при первой загрузке документа на основании HTML-описания. Объект Objectviewer, рассмотренный в главах 4 и 5, и подсистема оповещения (см. главу 6) содержат объекты модели предметной области, способные к отображению. Отображение осуществляется путем создания элементов DOM и присоединения их к главному документу. Дополнительные возможности предполагают дополнительную ответственность. Если узел создан программой, мы обязаны позаботиться и о его удалении.
Ни спецификация DOM, разработанная W3C, ни ее реализации в популярных браузерах не предусматривают способа удаления созданного узла DOM. Единственное, что мы можем сделать, — это разорвать связь между узлом и деревом документа и надеяться, что после этого узел будет обнаружен и удален системой "сборки мусора".
Рассмотрим простой пример. Приведенный ниже фрагмент сценария демонстрирует всплывающее окно с сообщением. Для его обнаружения используется выражение document.getElementById().
function Message(txt, timeout){
var box=document.createElement("div"); box.id="messagebox"; box.classname="messagebox";
var txtNode=document.createTextNode(txt); box.appendChild(txtNode);
setTimeout("removeBox('messagebox1)", timeout);
}
330 Часть III Создание профессиональных Ajax-приложений
f u n c t i o n removeBox(id){
v a r b o x = d o c u m e n t . g e t E l e m e n t B y l d ( i d ) ; i f (box){
b o x . s t y l e . d i s p l a y s ' n o n e 1 ;
}
}
При обращении к функции Message () отображается окно с сообщением и устанавливается таймер, который по прошествии определенного времени вызывает другую функцию, удаляющую окно.
Переменные box и txtNode создаются локально и исчезают из области видимости сразу после того, как выполнение функции Message () завершается, но созданные узлы документа остаются доступными, так как они была присоединены к дереву DOM.
ФункцияremoveBox () удаляет узел DOM по окончании работы с ним. С технической точки зрения сделать это можно различными способами. В примере, рассмотренном выше, мы удалили окно, запретив его отображение. В этом случае элемент, хотя и не отображается, продолжает занимать память. Если нам потребуется снова отобразить его, мы сделаем это без труда.
В случае необходимости мы также можем исключить узел DOM из документа и считать, что др того момента, когда система "сборки мусора" обнаружит и удалит его, пройдет немного времени. Заметьте, что мы не разрушаем переменную и не можем контролировать период, в течение которого объект останется в памяти.
function removeBox(id){
var box=document.getElementByld(id); if (box && box.parentNode){
box.parentNode.removeChild(box);
}
}
Мы можем сформулировать два принципа удаления элемента пользовательского интерфейса: удаление путем сокрытия (Remove By Hiding) и удаление путем отсоединения (Remove By Detachment). Для объекта Message не определены обработчики событий — по прошествии определенного времени он исчезает. Если мы свяжем объект модели предметной области и узлы DOM в двух направлениях, как мы сделали это для объекта Button, и захотим выполнить удаление путем отсоединения, мы должны будем явным образом вызвать функцию cleanUpO.
Оба подхода имеют свои преимущества и недостатки. Главный аргумент в пользу выбора одного из них — это необходимость повторно использовать узел DOM. Если речь идет об окне, предназначенном для отображения сообщения, то, вероятнее всего, оно понадобится нам в дальнейшем, поэтому целесообразно прибегнуть к удалению путем сокрытия. Если же речь идет о специфическом элементе, например об узле сложной древовидной структуры, проще будет разрушить элемент по окончании работы с ним, чем хранить ссылки на неиспользуемые узлы.
Глава 8. Производительность приложения |
331 |
Выбрав принцип удаления путем еокрытия, мы должны принять соответствующие меры для того, чтобы повторное использование узлов DOM стало возможным. Модифицируем функцию создания окна с сообщением так, чтобы она сначала проверяла, существует ли узел, и создавала новый только в случае необходимости. Перепишем конструктор объекта Message следующим образом:
function Message(txt, timeout){
var box=document.geElementById{"messagebox"); var txtNode=document.createTextNode(txt);
if (box==null){ box=document.createElement("div"); box.id="messagebox";
box.classname="messagebox"; box.style.display='block1; box.appendChild(txtNode);
}else{
var oldTxtNode=box.firstChild; box.replaceChild(txtNode,oldTxtNode);
}
setTimeout("removeBox('messagebox')",timeout);
}
Теперь мы можем сравнить два принципа создания элемента пользовательского интерфейса. Назовем их созданием при любых условиях (Create Always) и созданием при отсутствии (Create If Not Exists). Создание при любых условиях использовалось нами в исходном примере, а создание при отсутствии — в его модифицированном варианте. Поскольку идентификатор заложен в коде программы, в каждый момент времени может отображаться одно сообщение (такое ограничение нас вполне устраивает). Если мы имеем возможность связать объект модели предметной области с узлом DOM, предназначенным для повторного применения, объект может быть использован для хранения первоначальной ссылки на узел DOM, в результате принцип создания при отсутствии не противоречит наличию нескольких экземпляров объекта.
На заметку При написании приложения Ajax важно помнить о том, что занимать память могут не только объекты, ссылки на которые хранятся в переменных, но и элементы DOM. Также необходимо принимать во внимание тот факт, что элементами DOM можно управлять и удалять их по окончании работы. Если в программе используются как элементы DOM, так и обычные переменные, желательно создавать специальные функции очистки для того, чтобы не допускать существования циклических ссылок.
В следующем разделе мы рассмотрим еще один вопрос, который необходимо принимать во внимание, создавая Ajax-программу, предназначенную для работы в Internet Explorer.
332 Часть III. Создание профессиональных Ajax-приложений
Особенности работы в среде Internet Explorer
В разных браузерах реализованы различные варианты "сборки мусора", ков торые могут отличаться друг от друга. Конкретный механизм работы данной системы в Internet Explorer мало кому известен в деталях, но участники группы новостей comp. lang. JavaScript сходятся во мнении о том. что эта система испытывает серьезные трудности при обработке циклических ссылок между элементами DOM и обычными объектами JavaScript. По этой причине желательно вручную удалять подобные ссылки.
В качестве примера рассмотрим следующий код, в котором создаются циклические ссылки:
function MyObject(id){ this.id=id;
this.front=document.createElement("div");
this.front.backingObj=this;
1
Здесь тип MyObject определен пользователем. Каждый экземпляр объекта ссылается на узел DOM с помощью свойства this.front, а в узле DOM
спомощью this .backingObj поддерживается обратная ссылка на объект JavaScript.
Для того чтобы удалить циклическую ссылку после окончания работы
собъектом, мы можем использовать метод, подобный следующему:
MyObject.prototype.finalize=function(){
this.front.backingObj=null; this.front=null;
}
Устанавливая значения обоих свойств равным null, мы разрушаем циклическую ссылку.
Для очистки дерева DOM может быть применен универсальный способ, который состоит в обходе дерева и удалении ссылок с определенным именем или определенного типа. Ричард Корнфорд (Richard Cornford) предложил подобную функцию для обработчиков событий, связанных с элементами DOM (ссылка на нее приведена в конце главы).
На наш взгляд, универсальные подходы можно применять лишь в крайних случаях, поскольку обработка больших древовидных структур, типичных для богатых клиентов Ajax, будет происходить слишком медленно. Специальные функции, предусмотренные в коде программы, позволяют выполнять очистку тогда, когда она действительно необходима.
Еще одна особенность Internet Explorer — это наличие высокоуровневой недокументированной функции CollectGarbageO- В Internet Explorer б эта функция существует и может быть вызвана, но похоже, что она не выполняет никаких действий. Нам, во всяком случае, не удалось выявить разницу в объеме используемой памяти до и после обращения к пей.
Теперь, когда вы имеете сведения об управлении памятью, научимся измерять ее объем и применять результаты измерений в реальных приложениях.
Глава 8. Производительность приложения |
ззл |
8 4. Разработка с учетом производительности
В начале данной главы мы выяснили, что производительность приложения определяется скоростью его выполнения и объемом занимаемой памяти. Мы также упомянули о том, что стандартные программные решения могут помочь добиться высокой производительности программ.
В этом разделе вы узнаете, как измерить объем памяти, занимаемой реальным приложением. Здесь же мы рассмотрим простой пример, показывающий, что причины слишком большого потребления памяти программой иногда могут быть выявлены лишь в результате глубокого анализа используемых алгоритмов.
8.4.1. Измерение объема памяти, занимаемой приложением
Для измерения скорости выполнения программы мы могли применять как JavaScript-код, а именно объекты Date, так и специальные программы. В JavaScript отсутствуют встроенные средства для определения объема используемой памяти, поэтому для решения этой задачи надо полагаться на внешние инструменты. К счастью, такие инструменты существуют.
Выяснить, какой объем памяти потребляет браузер в процессе выполнения приложения, можно различными способами. Самый простой, способ сделать это — анализировать выполняемые процессы с помощью утилит, ориентированных не конкретную операционную систему. В Windows это Task Manager, а в Unix — команда top, вызываемая с консоли. Рассмотрим каждый из этих инструментов.
Windows Task Manager
Программа Windows Task Manager (рис. 8.5) существует для различных версий системы, однако пользователям Windows 95 и 98 она недоступна. Windows Task Manager предоставляет информацию обо всех процессах, выполняемых в операционной системе, и об используемых ими ресурсах. Обычно данная программа вызывается из меню, отображаемого после нажатия комбинации клавиш <Ctrl+Alt+Delete>. Интерфейс Task Manager состоит из нескольких вкладок. Нас интересует вкладка Processes.
Строка, выделенная на рисунке, сообщает нам, что в текущий момент Firefox использует около 38 Мбайт памяти. Информация о потребляемой памяти отображается в столбце Mem Usage. В некоторых версиях Windows пользователь может добавить дополнительные столбцы, используя меню View=>Select Columns (рис. 8.6).
Отображая наряду с Memory Usage значение Virtual Memory Size, можно получить полезную информацию о работе программы. Если Memory Usage представляет активизированную память, выделенную приложению, то Virtual Memory Size отображает информацию о неактивизированной памяти, записанной в раздел свопинга. При минимизации Windows-приложения значение в столбце Mem Usage существенно уменьшается, a Virtual Memory Size становится более или менее постоянным, показывая, какой объем ресурсов приложение может потреблять в дальнейшем.

334 Часть II! Создание профессиональных Ajax-приложений
Рис. 8.5. Программа Windows Task Manager отображает список выполняемых процессов и используемую ими память Процессы отсортированы по убыванию объема занимаемой памяти
Утилита top
Консольная программа top, предназначенная для выполнения в системе Unix (а также в Mac OS X), предоставляет приблизительно ту же информацию, что и Windows Task Manager (рис. 8.7).
Как и в случае программы Task Manager, в каждой строке представлены активизированный процесс, объем потребляемой памяти, нагрузка на центральный процессор и некоторые другие сведения. Управление программой top осуществляется с клавиатуры, команды описаны в справочной системе, кроме того, информацию о них можно найти в Интернете. Мы не будем рассматривать подробно программу top, а также аналогичные средства, предоставляющие графический интерфейс, например GNOME System Manager.
Инструменты с расширенными возможностями
Помимо рассмотренных простых инструментов, существуют более мощные средства, которые позволяют контролировать использование памяти, а также предоставляют подробные сведения о внутреннем состоянии операционной системы. Мы не можем уделить внимание всем существующим инструментам, поэтому вкратце рассмотрим лишь два из них. Эти программы распространяются бесплатно, и, на наш взгляд, их использование может помочь разработчикам.


Рис. 8.8. Process Explorer предоставляет подробные сведения об использовании памяти и процессора каждым процессом и позволяет отслеживать особенности работы различных браузеров на машине под управлением Windows. В окне, приведенном на этом рисунке, отображается информация о выполнении в среде Mozilla f irefox теста, олисанного в разделе 8 4.2
Инструмент Process Explorer производства Sysinternal.com (рис. 8.8) часто в шутку называют "диспетчером задач на стероидах". Он выполняет те же функции, что и Task Manager, но предоставляет подробную информацию об использовании памяти и центрального процессора каждым процессом. С его помощью мы можем, например, изучить особенности работы браузера Internet Explorer или Firefox.
Дж. Г. Веббер (J. G. Webber) разработал Drip (ссылка на соответствующий ресурс приведена в конце данной главы) — простой, но мощный инструмент, ориентированный на Internet Explorer и предоставляющий информацию об использовании памяти. Он непосредственно запрашивает Web-браузер об известных ему узлах D0M, в том числе о тех, которые не связаны с деревом документа (рис. 8.9).
Даже такие простые инструменты могут много сказать о состоянии выполняющегося Ajax-приложения.
Мы рассмотрели детали и особенности, влияющие на производительность

Рис. 8.9. Инструмент Drip предоставляет подробную информацию о внутреннем состоянии дерева DOM браузера Internet Explorer
отдельных фрагментов кода. Если мы напишем Aj ах-приложение хотя бы среднего размера, различные программные решения начнут взаимодействовать друг с другом, давая подчас неожиданные результаты. В следующем разделе мы рассмотрим пример, подчеркивающий, насколько важно знать конкретные особенности выполнения кода.
8.4.2. Простой пример управления памятью
На данный момент вы уже имеете основные сведения об управлении памятью и знакомы с некоторыми подходами, упрощающими создание интерфейсных элементов из программы. При разработке реального приложения Ajax мы используем ряд приемов, результаты применения которых могут в общем случае влиять друг на друга. Каждое программное решение оказывает влияние на производительность; то же справедливо для их совместного примененияРезультаты использования различных подходов удобно проиллюстрировать на конкретном примере, который мы и рассмотрим в данном разделе.
При работе программы, рассматриваемой здесь в качестве примера, создаются и удаляются компоненты ciickBox. Они называются так потому, что представляют собой небольшие квадраты, реагирующие на щелчки мышью. Поведение каждого компонента определяется следующим кодом:
function CiickBox(container){ this.x=5+Math.floor(Math.random*)*370); this.y=5+Math.floor(Math.random()*370); this.id="box"+contaiлег-boxes.length; this.state=0;
this.render();
container.add(this);