Ajax в действии
.pdfПриложение Б. Инструменты для профессиональной работы с Ajax |
613 |
||
Б.З. Методы и функции |
|
|
__ |
В предыдущем разделе, как и в большинстве глав данной книги, мы определяли функции и вызывали их. Программисты, имеющие опыт работы на Java или С#, могут посчитать, что функции JavaScript — это нечто вроде знакомых им методов, отличающиеся правилами записи. В этом разделе мы уделим функциям больше внимания и покажем, каких результатов можно добиться с их помощью.
Б.3.1. Функции как независимые элементы
Функции похожи на Java-методы тем, что для них определены параметры и возвращаемое значение. В тоже время между функциями JavaScript и Javaметодами есть существенные различия. Java-метод связан с классом, в котором он определен, и не может существовать вне этого класса. Функция JavaScript — это независимый элемент, функционирующий по собственным правилам. (Статические Java-методы занимают промежуточное положение — они принадлежат лишь классу и не связаны ни с одним его экземпляром.)
Программисты, использовавшие языки семейства С, скажут, что функции JavaScript — это то же самое, что указатели на функции в C++ . Они будут правы, но не совсем.
В JavaScript Function — это встроенный объект. Конечно же, он содержит исполняемый код и допускает вызов, но он также является потомком Object и может делать то же, что и любой объект JavaScript, в том числе и хранить значения свойств. К объекту Function можно присоединить в качестве методов другие объекты Function (многие разработчики поступают так).
Вы уже знаете, как получить ссылку на объект Function. Чаще всего требуется одновременно и сослаться на функцию, и вызвать ее.
var result=MyObject.doSomething(x,у, z)
Однако Function — это также независимый объект, и к нему можно обратиться с помощью метода call() (а также метода apply (), предоставляющего аналогичные возможности).
var result=MyObj ect.doSomething.call(MyOtherObject, x, y, z) Допустимо даже следующее выражение:
v a r r e s u l t = M y O b j e c t [ ' d o S o m e t h i n g 1 ] . c a l l ( M y O t h e r O b j e c t , x , у , z )
Первый параметр метода Function.call{) — это объект, выполняющий роль контекста функции при ее вызове. Последующие параметры рассматриваются как аргументы самой функции. Метод apply () действует несколько по-другому, в частности, второй параметр представляет собой массив аргументов, передаваемых функции. Такой подход обеспечивает несколько большую гибкость.
Следует заметить, что число параметров функции JavaScript не фиксировано. В Java или С# при попытке вызывать метод, указав количество параметров больше или меньше объявленного, на этапе компиляции генерируется сообщение об ошибке. JavaScript лишь игнорирует лишние параметры и при-
614 Часть V. Приложения
зваивает недостающим значение undefined. Искусно реализованные функции чогут даже запрашивать список параметров посредством свойства arguments л присваивать недостающим параметрам осмысленные значения по умолчанию. Они также могут генерировать исключения и предпринимать другие черы по обеспечению работоспособности программы. Например, можно реачизовать в рамках одной функции get- и set-метод.
function area(value){ if (value){
this.area=value;
}
return this.area;
}
Если мы вызовем агеа{) без параметров, значение value будет не определено, присвоение не будет иметь смысла и функция выступит в роли getлетода. Указав параметр, можно превратить функцию в set-метод. Данный годход широко использовал Майк Фостер в своей библиотеке х (см. ссылку в сонце данного приложения и главу 3). Если вы поработаете с библиотекой х, данный подход вскоре станет привычен вам.
Тот факт, что функции могут быть независимыми от конкретных объекюв, открывает ряд интересных возможностей.
5.3.2.Присоединениефункцийкобъектам
Тоскольку JavaScript — функциональный язык, он позволяет определять функции в отсутствии объектов, например:
f u n c t i o n doSomething(x,у,z){ . . . }
Тело функции также можно определить непосредственно при вызове. v a r d o S o m e t h i n g = f u n c t i o n ( х , у , z ) { . . . }
Чтобы отдать должное объектному подходу, разработчики JavaScript [редусмотрели возможность присоединения функций к объектам. При этом |ни становятся похожими на методы Java или С# . Связать функцию с объктом можно несколькими способами.
Можно присоединить существующую функцию к существующему объек- у (в этом случае вызвать функцию может только сам объект, но не другой, врожденный с использованием того же прототипа).
myObj.doSomethingNew=doSomething;
myObj.doSomethingNew(x,y,z);
Мы также можем присоединить функцию таким образом, что к ней бу- ,ет иметь доступ любой экземпляр класса. Для этого надо добавить функ- ,ию (либо предопределенную, либо объявленную в процессе присоединения) конструкторе (см. раздел Б.2.2) или связать ее с помощью прототипа.
Несмотря на возможность присоединения функции к объекту, связь меж- у ними нельзя считать очень прочной.
Приложение Б. Инструменты для профессиональной работы с Ajax 615
Б.3.3. Заимствование функций из других объектов
Способность функций выступать в роли независимых элементов существенно влияет на возможности языка. Очень важно представлять себе это влияние при программировании обработчиков событий, связанных с элементами внутреннего интерфейса; именно этим часто приходится заниматься программистам, разрабатывающим Ajax-приложения.
В чем же состоят эти новые возможности? Каждый объект может заимствовать функцию другого объекта и вызывать ее. Определим класс, описывающий некоторое дерево.
function Tree(name, leaf, bark){ this.name=name;
this.leaf=leaf; this.bark=bark;
}
Свяжем с этим классом функцию, предоставляющую описание дерева.
Tree.prototype.describe=function(){
return this.name+": leaf="+this.leaf+", bark="+this.bark;
}
Если мы создадим экземпляр объекта Tree и запросим у него описание, то получим вполне предсказуемый результат.
var Beech=new Tree("green, serrated edge","smooth"); alert{Beech.describe());
В окне отображается текст Beech: leaf=green, serrated edge, bark=- smooth. Пока никаких неожиданностей не предвидится. Теперь определим класс, представляющий собаку.
function Dog(name,bark){ this.name=name; this.bark=bark;
}
Создадим экземпляр класса Dog.
var Snowy=new Dog("snowy","wau! wau!");
Объект мог бы продемонстрировать описание звуков, издаваемых собакой (к тому же мы определили их), однако функция, с помощью которой можно было бы сделать это, отсутствует. Тем не менее можно воспользоваться функцией класса Tree.
var tmpFunc=Beech.describe; tmpFunc.call(Snowy);
Как вы помните, первый параметр, передаваемый function.call(), — это объект контекста, на который ссылается специальная переменная this. Приведенный выше фрагмент кода генерирует окно с сообщением, в котором отображается текст Snowy: leaf=undefined, bark=wau! wau!. Формат сообщения нельзя назвать идеальным, но все же это лучше, чем ничего.
Но как это происходит? Почему собака получает возможность вызвать функцию, которая на самом деле принадлежит дереву? Ответ несколько неожиданный: на самом деле функция не принадлежит дереву. При при-
516 Часть V. Приложения
оединении функции к прототипу Tree связывание осуществляется лишь гастолько, насколько это необходимо для поддержки выражений типа lyTree.describe (). Если рассмотреть внутреннюю реализацию программы, •о можно увидеть, что функция хранится как фрагмент текста и выполнятся при каждом вызове. При этом конкретное значение this изменяется от
.бращения к обращению.
Заимствование функций — интересный прием; освоив, вы можете применять его при написания своего кода. Однако для повышения надежности мы ,се же рекомендуем реализовать для Snowy собственный метод bark(). Мы бсуждаем данный вопрос лишь потому что при написании кода для поддержки событий, вам необходимо знать, какие действия браузер выполняет ез вашего участия.
13.4.Обработка событий в Ajax-программах
иконтексты функций
)бработчики событий в Ajax-приложениях похожи на обработчики, реализумые инструментальными средствами создания пользовательских интерфейов для многих языков. Как было показано в главе 4, события, связанные с [ышью и клавиатурой, выделяются в отдельные категории. В рассмотренном ами примере использовался обработчик onclick; это событие генерируется о щелчку мышью на видимом элементе. Подробное обсуждение обработки обытий в DHTML не входит в круг задач, рассматриваемых в данной книге; [ы ограничимся лишь одной особенностью, которая может стать ловушкой ля неопытных программистов. Обработчик события может определяться в [TML-дескрипторе, например:
<div id='rayDiv' onclick='alert:alert(this.id)'x/div> Его также можно задать из программы.
function clickHandler(){ a l e r t ( t h i s . i d ) ; } myDiv.onclick=clickHandler;
Заметьте, что во втором случае мы передаем ссылку на объект Funcion (после clickHandler круглые скобки не указываются). При объявлении >ункции в HTML-дескрипторе создается безымянная функция. Эта запись <вивалентна приведенной ниже.
myDiv.onclick=function{){ a l e r t ( t h i s . i d ) ; }
Заметьте, что в обоих случаях параметры функции не предусмотрены: е существует способа задать их при щелчке мышью. Если же мы щелкаtf на элементе DOM, при вызове функции передается в качестве параметра 5ъект Event, а сам элемент выполняет роль объекта контекста. Зная это, ожно избежать многих неприятностей, в особенности при написании объстного кода. Источником проблем может стать тот факт, что узел DOM -егда используется в качестве контекста, даже если функция присоединена ^средством прототипа к другому объекту.
В приведенном ниже примере мы определяем простой объект с обработаком события для видимого интерфейсного элемента. В данном случае объ-
Рис. Б.19. Присоединение ссылки на модель непосредственно к узлу DOM упрощает поиск модели функцией обработки события
Чтобы вызвать метод f оо (), функция обработчика события должна найти экземпляр myob j. Для этой цели мы создаем специальный метод.
function getMyObj(id){
var key-id.split(•_")[0]; return MyObjectsfkey];
}
Этот метод использует свойство id DOM-узла для того, чтобы извлечь ключ, с помощью которого можно получить объект модели из глобального массива.
Метод ссылки на модель по имени хорошо зарекомендовал себя на практике, однако существует более простой путь решения той же задачи, при котором нет необходимости снабжать узлы DOM длинными идентификаторами. (Трудно сказать, хорош этот способ или нет. С одной стороны, он приводит к излишнему использованию памяти, а с другой стороны — упрощает отладку в Mozilla DOM Inspector.)
Присоединение модели к узлу DOM
Согласно второму подходу к обработке событий DOM, все необходимые действия выполняются посредством ссылки на объекты, а дополнительные строки и поиск в глобальном массиве не требуются. Именно этот подход в основном используется в данной книге (рис. Б.2).
Данный подход существенно упрощает обработку события. Функция конструктора для объекта модели не выполняет специальных действий с идентификатором, а метод f оо () определяется так же, как и ранее. Создавая узлы DOM, мы используем особенность JavaScript, позволяющую присоединять к любому объекту произвольные атрибуты, и связываем объект модели непосредственно с узлом, получающим событие.
myObj.prototype.createView=function(){
this.body=document.createElement("div");
620 Часть V. Приложения
this.body.modelObj=thi s;
this.titleBar=document.createElement("div");
this.titleBar.modelObj=this;
this.titleBar.onclick=fooEventHandler;
}
При написании обработчика события мы можем непосредственно получить ссылку на модель.
function fooEventHandler(event){ var modelObj=this.modelObj;
if (modelObj){ modelObj.foo(); }
}
}
При этом не нужно специально выполнять поиск.
Необходимо, однако, учитывать одну важную особенность. При использовании данного подхода создается циклическая ссылка между DOM-узлом и переменной, не имеющей отношения к DOM. В некоторых браузерах это может вызвать проблемы в работе системы "сборки мусора". Если корректно реализовать данное решение, можно избежать неоправданного расходования памяти, но мы настоятельно рекомендуем вам изучить главу 7, перед тем как выполнять связывание модели с DOM-узлом.
Понимая, как функции JavaScript определяют свой контекст, можно реализовывать элегантные обработчики событий, пригодные для повторного использования. Возможность переключаться между контекстами может поначалу приводить к недоразумениям, но если хорошо освоить данный механизм, то им очень удобно пользоваться в работе.
Последняя особенность функций JavaScript, которую нам необходимо рассмотреть, — это способность создавать замыкания. В языках Java и С# понятие замыкания отсутствует, однако в некоторых языках сценариев, например Groovy и Boo, оно используется. С замыканиями также приходится сталкиваться в языке С# 2.0. Рассмотрим, что такое замыкания и как их использовать.
S.3.5. Замыкания в JavaScript
Сам по себе объект Function — неполный, и чтобы вызвать функцию, нам надо передать ей объект контекста и набор параметров (этот набор может быть пустым). Замыкание можно рассматривать как объект Function, связанный со всеми ресурсами, необходимыми для выполнения функции.
В JavaScript замыкания создаются неявно. Не существует конструктора, вызываемого с помощью выражения new ClosureO, и нет возможности получить ссылку на объект замыкания. Для создания замыкания достаточно объявить функцию в блоке кода (например, в теле другой функции) и обеспечить доступ к ней из-за пределов блока.
Вышесказанное может показаться несколько непривычным, поэтому стоит рассмотреть простой пример. Предположим, что мы определили объект,