Добавил:
Upload Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Ajax_v_deystvii.pdf
Скачиваний:
34
Добавлен:
05.03.2016
Размер:
5.83 Mб
Скачать

324 Часть III. Создание профессиональных Ajax-припожений

ситуациях дело обстоит так же, но выяснить это можно, лишь выполнив измерения, причем желательно сделать их для разных платформ и браузеров.

На этом мы заканчиваем разговор о профилировании и скорости выполнения программ. Рассмотренные примеры дают полное представление о том. какую пользу можно извлечь, правильно выполняя профилирование при работе над проектом Ajax. Предположим, что благодаря профилированию вы до бились приемлемой скорости выполнения кода. Однако производительность зависит не только от скорости работы, но и от объема памяти, используемой приложением. Этому вопросу и будет посвящен следующий раздел.

8.3. Использование памяти JavaScript-кодом

Этот раздел посвящен вопросам управления памятью в Ajax-программах. Некоторые из рассмотренных здесь решений применимы для любой системы и любого языка программирования, другие специфичны для Ajaxприложений и даже для конкретных Web-браузеров.

Выполняющееся приложение запрашивает у операционной системы определенный объем памяти. В идеальном случае программа должна получить столько памяти, сколько необходимо для эффективного решения поставленной задачи, и вернуть неиспользуемую память системе. Если приложение написано непрофессионально, оно может потреблять неоправданно много памяти либо не возвращать память системе по окончании работы.

По мере перехода от простых Web-страниц к богатым клиентам Ajax качество управления памятью все больше влияет на время отклика программы

инадежность ее работы. Применение стандартных программных решений помогает создать код, простой для восприятия и сопровождения, а также выявить причины утечки памяти и устранить их. (Утечкой памяти мы будем называть ситуацию, при которой программа запрашивает память у системы

ине возвращает ее после завершения работы.)

Начнем с рассмотрения общих вопросов управления памятью.

8.3.1, Борьба с утечкой памяти

В любой программе может иметь место утечка памяти. Вопросы выделения памяти и ее освобождения требуют пристального внимания разработчиков, применяющих языки, в которых не предусмотрено автоматическое управление памятью, например язык С. В JavaScript имеется подсистема "сборки мусора", которая автоматически следит за выделенной памятью и освобождает ее, если она не используется. Это позволяет избежать многих проблем, типичных для языков, в которых отсутствуют подобные средства, однако было бы ошибкой считать, что при наличии подсистемы управления утечка памяти не может произойти.

Процедура "сборки мусора" пытается выяснить, можно ли удалить неиспользуемую переменную, при этом она учитывает возможность доступа к ней по сети или посредством ссылок, содержащихся в других переменных. Если получить доступ к переменной невозможно, она помечается как разрешен-

ная к удалению, и на следующем шаге занимаемая ею память освобождается (момент времени, когда это произойдет, предсказать невозможно). Забывая удалить ссылки на переменную после того, как работа с ней окончена, мы создаем условия для утечки памяти, которая может иметь место даже в языках с управлением памятью.

Рассмотрим простой пример. Определим объектную модель, описывающую домашних животных и их хозяев. Начнем с хозяев; для их описания используем объект Person.

function Person(name){ this.name=name; this.pets=new Array();}

Хозяину может принадлежать один или несколько домашних любимцев. Когда человек приобретает животное, он сообщает о том, что у того теперь есть конкретный хозяин.

Person.prototype.addPet=function(pet){

this.pets[pet.name]=pet; if (pet.assignOwner){

pet.assignOwner(this);}

}

Аналогично, когда человек удаляет животное из списка своих любимцев, он сообщает животному о том, что оно больше никому не принадлежит.

this.removePet{petName)=function{ var orphan=this.pets[petName];

t h i s . pets[petNarrLe]=null,•

i £ ( o r p h a n . u n a s s i g n O w n e r ) {

o r p h a n . u n a s s i g n O w n e r ( t h i s ) ; )

}

Влюбой момент времени хозяин знает, какие животные ему принадлежат,

иможет управлять списком своих любимцев посредством методов addPet ()

иremovePet (). Человек информирует животное о принадлежности хозяину или ее отсутствии, считая, что животное автоматически начнет считать человека своим хозяином (подобное поведение можно предполагать по умолчанию, а необходимую проверку выполнять в процессе работы программы).

Определим два типа домашних животных: кошку и собаку. Эти животные различаются по своему отношению к хозяину, кошка не обращает внимания на тот факт, кому она принадлежит, а собака привязывается к человеку на всю жизнь. (Мы приносим извинения всему животному миру за столь вольное обобщение.)

Таким образом, определение кошки может выглядеть как показано ниже:

function Cat(name){this.name=name;} Cat.prototype.assignOwner=function(person){} Cat.prototype.unassignOwner=function(person){}

Кошка не интересуется фактом своей принадлежности кому-либо, поэтому тело соответствующих методов пусто.

Опишем теперь собаку. Она помнит, кому она принадлежит, и после того, как человек откажется от нее, по-прежнему считает его своим хозяином (многие собаки ведут себя именно так).

о*о часть III. Создание профессиональных Ajax-приложений

f u n c t i o n Dog(name){this.name=name;}

D o g . p r o t o t y p e . a s s i g n O w n e r = f u n c t i o n ( p e r s o n ) { t h i s . o w n e r = p e r s o n ; }

D o g . p r o t o t y p e . u n a s s i g n O w n e r = f u n c t i o n ( p e r s o n ) { t h i s . o w n e r = p e r s o n ; }

Объекты Cat и Dog представляют собой реализации класса Pet, но поведение их нельзя назвать корректным. Оно соответствует букве соглашения о принадлежности, но не его "духу". В Java или С# мы можем явным образом определить интерфейс Pet, но это не спасет классы, реализующие его, от неправильного поведения. При работе над реальными проектами специалисты, занимающиеся объектными моделями, тратят много времени, исправляя некорректное поведение реализаций интерфейсов.

Поработаем немного над моделью. Создадим три объекта.

1.jim — Person

2.whiskers — Cat

3.fido — Dog

Впервую очередь создадим экземпляр Person (этап 1). var jim=new Person("jim");

Далее сделаем человека владельцем кошки (этап 2). Экземпляр whiskers создается в процессе вызова метода addPet (), поэтому ссылка на объект Cat существует лишь до завершения метода. Однако объект jim также создает ссылку на whiskers, а она доступна в течение всего времени существования объекта jim.

jim.addPet(new Cat("whiskers"));

Пусть теперь jim владеет также собакой (этап 3). В отличие от whiskers, мы объявим для этого объекта глобальную переменную.

var fido=new Dog("fido"); j im.addPet(fido);

Однажды jim решает избавиться от кошки (этап 4). jim.removepet("whiskers") ;

Затем он решает, что собака ему тоже не нужна (этап 5). Наверное, он собирается переехать в другой город.

j im.removePet("fido");

Мы также больше не интересуемся этим человеком и удаляем ссылку на него (этап 6).

jim=null,-

И наконец, мы удаляем ссылку на fido (этап 7). fido=null;

По окончании этапа 6 мы считаем, что избавились от объекта jim, поскольку присвоили соответствующей переменной значение null. На самом деле ссылка на него существует в fido и доступна посредством выражения fido. owner.

Глава 8 Производительность приложения 327

Система "сборки мусора" не затронет этот объект, и он останется незамеченным в области памяти, называемой "куча" (heap), интерпретатора JavaScript. Т1йшь на этапе 7, когда переменной fido присваивается значение null, объект -i^m становится недоступным, и память может быть освобождена.

В нашем простом сценарии проблема не является серьезной, к тому же о Н а быстро решается, но данный пример иллюстрирует, как решения, не зависящие друг от друга, на первый взгляд, влияют на процесс "сборки мусора'. Объект fido не обязательно будет удален сразу же после jim, и если мы предус м°т Ри м в н е м возможность хранить информацию о нескольких предыдущих владельцах, то в памяти может остаться целый набор объектов Person, ведущих призрачное существование, но занимающих реальные ресурсы. Если бы мы объявили fido при вызове метода, а ссылку на объект Cat поместили в глобальную переменную, то такое явление не возникло бы. Для того чтобы оценить, насколько серьезную проблему может создать подобное поведение объекта fido, нам надо ответить на следующие вопросы.

1. Какой объем памяти он может занимать с учетом ссылок на объекты, которые в других обстоятельствах были бы удалены? Мы знаем, что в данном примере fido может ссылаться только на один объект Person. Но даже в этом случае не исключено, что объект Person содержит ссылки на 500 объектов Cat, поэтому объем расходуемой памяти может быть существенным.

2. В течение какого времени память будет оставаться занятой? В рассмотренном примере ответом на этот вопрос будет "не очень долго". Однако впоследствии между удалением jim и fido могут появиться команды, выполнение которых будет занимать длительное время. Более того, язык JavaScript позволяет создавать программы, управляемые событиями, и удаление j im и fido может происходить при обработке различных событий. В этом случае, чтобы предсказать, в течение какого времени будет занята дополнительная память, пришлось бы анализировать типичные сценарии работы пользователя.

Ни на один из приведенных выше вопросов непросто ответить. Самое лучшее, что можно сделать, — это задавать их себе в процессе создания и модификации кода и выяснять в процессе тестирования, правильны ли наши предположения.

Мы рассмотрели основные принципы управления памятью. Однако существуют проблемы, специфические для приложений Ajax. Обсудим их в следующем разделе.

8.3.2. Особенности управления памятью в приложениях Ajax

Мы рассмотрели понятия, лежащие в основе управления памятью, для многих языков программирования. Отслеживать объем используемой памяти и доступность переменных важно при работе над любым приложением, однако есть также вопросы, специфические для Ajax. Ajax-программы pa-

•#*«» часть 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 б эта функция существует и может быть вызвана, но похоже, что она не выполняет никаких действий. Нам, во всяком случае, не удалось выявить разницу в объеме используемой памяти до и после обращения к пей.

Теперь, когда вы имеете сведения об управлении памятью, научимся измерять ее объем и применять результаты измерений в реальных приложениях.

Соседние файлы в предмете [НЕСОРТИРОВАННОЕ]