Добавил:
Опубликованный материал нарушает ваши авторские права? Сообщите нам.
Вуз: Предмет: Файл:
Скачиваний:
107
Добавлен:
02.05.2014
Размер:
6.34 Mб
Скачать

Приложение Б Инструменты для профессиональной работы с Ajax 603

задаем дату публикации с помощью JavaScript-объекта Date. При присвоении значения можно использовать любой код JavaScript, даже определенную нами функцию.

function

gunpowderPlot(){

return

new Date(1605,11,05);

}

 

 

var

volNum=2;

var

turnipVol2={

title : "Turnip Cultivation through the Ages, vol. " +volNum, authors : [

{ name: "Jim Brown", age: 35 }

h

publicationDate : gunpowderPlot()

I}

};

Здесь название книги вычисляется динамически с помощью включенного выражения, а свойству publicationDate присваивается значение, возвращаемое функцией.

В предыдущем примере мы определили функцию gunpowderPlot (), которая выполнялась при создании объекта. Мы также можем определить функцию-член для объекта, создаваемого средствами JSON, так, чтобы она выполнялась при непосредственном обращении.

var turnipVol2={

 

 

t i t l e

:

"Turnip Cultivation

through the Ages, vol. "+volNum,

authors

: [

 

 

{ name: "Jim Brown", age:

35 }

L

 

 

 

 

publicationDate :

gunpowderPlot()

}

 

 

 

 

],

 

 

 

 

summarize:function(len)(

 

if (!len){ len=7;

}

 

var summary=this.title+" by " +this.authors[0].name

+" and his cronies is very boring. Z";

for

(var i=0;i<len;i++){

 

summary+="z";

}

alert(summary);

}

};

t u r n i p V o l 2 . s u m m a r i z e { 6 ) ;

Функция summarize*) обладает всеми характеристиками стандартной функции JavaScript; для нее определены параметры и объект контекста, идентифицируемый с помощью ключевого слова this. Созданный объект ничем не отличается от любого другого JavaScript-объекта, поэтому по мере необходимости мы можем использовать для его формирования и запись JSON, и обычные выражения JavaScript. В частности, средствами JavaScript можно изменить объект, созданный с использованием JSON.

604 Часть V. Приложения

v a r numbers={ o n e : 1 , two:2, t h r e e : 3

n u m b e r s . f i v e = 5 ;

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

var cookbook=new Object(); cookbook.pageCount=321; cookbook.author={

firstName: "Harry", secondName: "Christmas",

birthdate: new Date(1900,2,29), interests: ["cheese","whistling",

"history of lighthouse keeping"]

};

Используя только встроенные JavaScript-классы Object и Array и средства JSON, можно сформировать сколь угодно сложную иерархию объектов. JavaScript также предоставляет средства, позволяющие выполнять действия, тодобные определению классов. Рассмотрим их и выясним, чем они могут томочь в работе над приложениями.

S.2.2. Функции-конструкторы, классы и прототипы

3 объектно-ориентированном программировании при создании объекта обыч- ю указывается класс, экземпляром которого он является. И в Java, i в JavaScript присутствует ключевое слово new, позволяющее создавать объ- :кты требуемых типов. Этим данные языки похожи друг на друга.

В языке Java нет ничего, кроме объектов (исключением являются простые "ипы), причем каждый объект является потомком класса java.lang.Object. Тоддержка классов, полей и методов реализована в виртуальной машине lava. Когда мы записываем приведенное ниже выражение, то сначала задаем ^ип переменной, а затем создаем экземпляр класса, используя конструктор.

MyObject myObj=new MyObject{argl,arg2);

Данное выражение корректно только в том случае, если определен класс lyObject и в нем предусмотрен соответствующий конструктор.

В JavaScript также используются объекты и классы, но понятие наследования в этом языке отсутствует. Каждый объект JavaScript — это экземп1яр одного и того же базового класса, который позволяет формировать поля i функции в процессе выполнения программы. Пример определения свойства фи веден ниже.

MyJavaScriptObj ect.completelyNewProperty="something";

Для организации .объектов в таких условиях могут быть использованы [рототипы, с помощью которых объявляются свойства и функции. Эти свойтва и функции автоматически присоединяются к объекту при его создании. Ложно написать объектную программу на JavaScript, и не применяя прото-

Приложение Б. Инструменты для профессиональной работы с Ajax 605

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

Мы можем написать на JavaScript нечто, напоминающее Java-определение. var myObj=new MyObject();

Однако вместо класса MyObject возможно определить лишь функцию с таким именем. Ниже приведен пример простого конструктора.

function MyObject(name,size){ this . name=naine; this.size=size;

}

Вызвать ее можно следующим образом:

var myObj=new MyObject("tiddles","7.5 meters"); alert("size of "+myObj.name+" is "+myObj.size);

Все свойства this, установленные в конструкторе, впоследствии доступны как свойства объекта. При необходимости можно включить в состав конструктора и вызов a l e r t (), в результате tiddles будет сообщать свой размер. Часто в составе конструкторов встречаются определения функций.

function MyObject{name,size){ this.name=name; this.size=size; this.tellSize=function(){

alert("size of "+this.name+" is "+this.size);

}

}

v a r myObj=new O b j e c t ( " t i d d l e s " , " 7 . 5 m e t e r s " ) ; m y O b j . t e l l S i z e t ) ;

Такое решение допустимо, но оно далеко от идеала по двум причинам. Во-первых, при формировании каждого экземпляра MyObject создается новая функция. Мы, как и все грамотные программисты, следим за использованием памяти, и если нам надо много таких объектов, то данный подход становится для нас неприемлемым. Во-вторых, мы непредумышленно создали то, что принято называть замыканием (closure). В данном случае оно вполне безобидно, но как только мы включим в конструктор выражения, работающие с узлами DOM, оно может стать причиной серьезных проблем. С замыканиями вы ознакомитесь далее в этом приложении. А сейчас рассмотрим альтернативный подход, основанный на использовании прототипов.

Прототип — это свойство JavaScript-объектов, аналогов которому в обычных объектных языках нет. С прототипом конструктора связываются функции и свойства. После этого прототип и ключевое слово new начинают работать совместно, и при вызове функции посредством new к объекту присоединяются все свойства и метода прототипа. Это звучит несколько непривычно, но использовать прототип на практике достаточно просто.

function MyObject{name,size){ this.name=name;

this.size=size;

>06 Часть V. Приложения

>

MyObject.prototype.tellSize=function(){ alert("size of "+this.name+" is "+this.size);

J

var myObj=new MyObject{"tiddles","7.5 meters"); myObj.tellSize();

Сначала мы объявляем конструктор, а затем добавляем функции к проотипу. Функции присоединяются к объекту при его создании. Посредством :лючевого слова this в процессе выполнения программы можно ссылаться га экземпляр объекта.

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

MyOb j ect.prototype.color="red"; var objl=new MyObject();

MyObj e c t . p r o t o t y p e . c o l o r = " b l u e " ;

M y O b j e c t . p r o t o t y p e . s o u n d E f f e c t = " b o O O O o i n g ! ! " ; v a r obj2=new MyObject{);

В данном примере для obj 1 задается красный цвет, а звуковые эффекты тсутствуют. Для ob j 2 цвет уже синий и, кроме того, с ншС1 связан доволь- :о неприятный звук. Изменять прототип во время работы программы вряд и имеет смысл. Необходимо знать, что подобное возможно, но в своих прораммах лучше применять прототипы для того, чтобы приблизить поведение avaScript-объектов к обычным классам.

Заметьте, что прототипы встроенных классов, т.е. классов реализуемых раузером и доступных посредством JavaScript, также можно расширять встроенные классы принято также называть рабочими объектами (host bject)). Рассмотрим, как можно сделать это на практике.

к2.3. Расширение встроенных классов

Разработчики JavaScript планировали, что сценарии на данном языке будут ыполняться под управлением программ, предоставляющих доступ к собгвенным объектам, написанным, например, на C++ или Java. Эти объекты азывают встроенными или рабочими. Они несколько отличаются от объеков, определяемых разработчиками, которые мы рассматривали выше. Одна- о механизм прототипов дает возможность работать и со встроенными класами. Браузер Internet Explorer не позволяет расширять DOM-узлы; другие <е основные классы доступны для расширения во всех основных браузерах, 'ассмотрим в качестве примера класс Array и определим несколько вспомоательных функций.

Array.prototype.indexOf=function(obj){ var result=-l;

for (var i=O;i<this.length;i++){ if (this[i]==obj){

Приложение Б. Инструменты для профессиональной работы с Ajax 607

result=i;

break;

}

}

return result;

}

Мы определили для объекта Array дополнительную функцию, которая возвращает числовой индекс объекта в данном массиве или значение -1, если этот объект отсутствует. Далее мы можем написать метод, проверяющий, содержит ли массив указанный объект.

Array.prototype.contains=function{obj){ return (this.indexOf(obj)>=0);

}

Ниже приведен пример функции, включающей в массив новые элементы, предварительно убедившись в том, что данный элемент отсутствует.

Array.prototype.append=function(obj,nodup){ if (!(nodup && this.contains(obj)))f

this[this.length]=obj;

}

}

После того как новые функции определены, они будут присутствовать в каждом новом объекте Array, независимо от того, применялся ли для его создания оператор new или выражение JSON.

var numbers=[l,2,3,4,5];

var got8=numbers.contains(8); numbers.append("cheese",true);

Как и для объектов, определяемых разработчиком, прототипы встроенных функций можно изменять в процессе работы программы, в результате объекты, созданные в разное время, будут иметь различные наборы функций и свойств. Несмотря на наличие подобной возможности, мы рекомендуем модифицировать прототип только один раз в начале выполнения программы. Поступая так, вы избежите недоразумений; в особенности это важно в том случае, если в написании программы берет участие рабочая группа.

Прототипы могут оказаться очень полезными при разработке модели для клиентской части Ajax-приложения. Не исключено, что в этом случае потребуется не только определить объекты различных типов, но и использовать наследование. В отличие от C++, Java и С#, язык JavaScript непосредственно не обеспечивает наследование, но его можно реализовать посредством прототипов. Рассмотрим этот вопрос подробнее.

Б.2.4. Наследование прототипов

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

608Часть V. Приложения

Снаследованием тесно связано понятие области видимости. Область видимости метода или свойства определяет, кто может пользоваться ими. Обычно в языках программирования предусмотрены области видимости public, private и protected.

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

Дуг Крокфорд (ссылка на его работу приведена в конце данного приложения) предложил оригинальное решение, позволяющее имитировать в объектах JavaScript и наследование, и область видимости. Его работа заслуживает внимания, но, к сожалению, мы не имеем возможности обсуждать ее здесь. Для того чтобы воспользоваться результатами работы Крокфорда, необходимо потратить достаточно длительное время на их изучение, поэтому если вы ие собираетесь пользоваться данной технологией постоянно, а проект надо выполнить в сжатые сроки, тратить время на знакомство с ней не следует. В аналогичном положении оказываются те разработчики, которые решают, стоит ли им применять Java-библиотеку Struts или Tapestry. Подобные средства надо либо использовать постоянно, либо не пользоваться ими вовсе. Те, кого заинтересовал данный вопрос, могут найти дополнительную информацию на Web-узле Крокфорда.

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

Продолжая обсуждение объектов JavaScript, необходимо обратить внимание на отражение.

Б.2.5. Отражение в JavaScript-объектах

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

Если нам надо выяснить, существует ли в составе JavaScript-объекта некоторое свойство или метод, мы можем выполнить несложную проверку.

if (MyObject.someProperty){

}

Приложение Б. Инструменты для профессиональной работы с Ajax

609

Однако, если MyObject.someProperty содержит логическое значение false, или число 0, или специальное значение null, тело оператора if не будет выполнено. Более строгая проверка выглядит следующим образом:

if (typeof(MyObject.someProperty) ! = "undefined"){

Если нас интересует тип свойства, мы можем использовать оператор instanceof, который позволяет определить основные встроенные типы,

if (myObj instanceof Array){

}else if (myObj instanceof Object){

}

Он также дает возможность распознавать классы, которые мы определили посредством конструкторов.

if (myObj instanceof MyObject){

}

Если вы хотите использовать instanceof для проверки своих классов, вам необходимо учитывать некоторые особенности. Во-первых, любой объект, созданный посредством JSON, — это либо Object, либо Array. Во-вторых, между встроенными объектами могут существовать отношения "родительскийдочерний". Например, Function и Array являются потомками Object, поэтому порядок проверки на соответствие тому или иному типу имеет значение. Предположим, что мы написали следующий фрагмент кода и передали функции объект Array:

function testType(myObj){

if {myObj instanceof Array){ alert{"it's an array");

}else if {myObj instanceof Object){ alert("it's an object");

}

}

testType([l,2,3,4J);

В результате мы получим сообщение о том, что объект имеет тип Array. Эта информация вполне корректна. Внесем небольшие изменения в тот же код.

function

testType(myObj){

if (myObj instanceof Object){

a l e r t ( " i t ' s an

object");

}else if

(myObj

instanceof Array){

a l e r t ( " i t ' s an

array");

}

 

 

}

testType([l,2,3,4]);

Теперь при тех же условиях мы получим ответ, что объект имеет тип Object. Эта информация также формально правильна.

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

610 Часть V. Приложения

f u n c t i o n MyObject(){ t h i s . c o l o r = ' r e d 1 ;

t h i s . f l a v o r - ' s t r a w b e r r y ' ; t h i s . a z i m u t h = ' 4 5 d e g r e e s ' ; t h i s . f a v o r i t e D o g » 1 c o l l i e 1 ;

}

v a r my0bj=new M y O b j e c t O ;

v a r d e b u g = " d i s c o v e r i n g . . . \ n " ; for (var i in myObj){

debug+=i+" -> " + m y O b j [ i ] + " \ n " ;

}

a l e r t ( d e b u g ) ;

Вданном случае тело цикла выполняется четыре раза, предоставляя все значения, заданные в конструкторе. Такой подход применим и для встроенных объектов, однако для узла D0M размер окна с сообщением оказывается слишком большим. Более профессиональный способ подобной проверки рассматривался в главах 5 и 6 при создании пользовательского интерфейса ObjectViewer.

Вобъектно-ориентированных языках существует еще одна возможность, которую нам необходимо обсудить, — виртуальные классы или интерфейсы.

В.2.6. Интерфейсы и виртуальные типы

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

Виртуальные классы C + + и интерфейсы Java предоставляют разработчикам средства описания подобных данных в программе. Термин "интерфейс" мы часто используем для описания взаимодействия различных компонентов программного обеспечения. Чтобы обеспечить подобное взаимодействие, автор библиотеки для работы с геометрическими фигурами не должен принимать во внимание конкретные реализации этих фигур. С другой стороны, программист, реализующий интерфейс Shape, не должен учитывать особенности библиотечного кода или существующие реализации данного интерфейса.

Интерфейсы обеспечивают разделение понятий, кроме того, они широко используются в описании образов разработки. Применять их желательно и при написании Ajax-приложений. Однако в JavaScript понятие интерфейса отсутствует, поэтому нам необходимо найти ему замену.

Проще всего неформально определить взаимодействие и считать, что разработчики каждого объекта знают, как поддерживать его. Дейв Томас (Dave Thomas) назвал данный подход фиктивными типами (duck typing): если нечто описано как тип, следовательно, оно и является типом. Возвращаясь

Приложение Б. Инструменты для профессиональной работы с Ajax

611

к нашему примеру интерфейса Shape, можно сказать, что если для некоторого элемента можно вычислить периметр и площадь, то этот элемент является геометрической фигурой.

Предположим, что нам надо сложить площади двух геометрических фигур. Используя язык Java, мы можем написать следующий метод:

public double addAreas{Shape si, Shape s2){ return si.getArea()+s2.getArea();

}

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

function addAreas(sl,s2) {

return si.getArea()+s2.getArea{);

}

Если в объекте, переданном в качестве параметра, отсутствует функция getArea (), интерпретатор JavaScript сгенерирует сообщение об ошибке. Перед вызовом функции можно проверить, присутствует ли она.

function

hasArea(obj){

return

obj && obj.getArea && obj.getArea instanceof Function;

}

 

Код нашей функции необходимо модифицировать с учетом данной проверки.

function addAreas(si,s2){ var total=null;

if (hasArea(sl) && hasArea(s2)){ total-sl.getArea()+s2.getArea();

}

return total;

}

Используя отражение JavaScript, мы можем написать универсальную функцию, проверяющую, имеется ли в составе объекта функция с конкретным именем.

function implements(obj,funcName){

return obj && obj[funcName] && obj[funcName] instanceof Function;

}

Мы можем также присоединить эту функцию к прототипу класса Object.

Obj ect.prototype.implements=function{funcName){

return this && this[funcName] && this[funcName] instanceof Function,

}

Это позволяет организовать проверку функции по имени.

function hasArea(obj){

return obj.implements("getArea");

}

Можно даже проверить соответствие параметра интерфейсу.

612 Часть V. Приложения

f u n c t i o n i s S h a p e ( o b j ) {

r e t u r n o b j . i m p l e m e n t s ( " g e t A r e a " ) && o b j . i m p l e m e n t s ( " g e t P e r i m e t e r " ) ;

}

Данный подход обеспечивает определенную степень надежности, однако не такую, как в программах на Java. Нам может встретиться объект, в котором имеется функция getAreaO, возвращающая, например, вместо числового значения строку. Тип значения, возвращаемого JavaScript-функцией, неизвестен до тех пор, пока мы не вызовем ее. (Мы можем написать, например, функцию, которая будет возвращать в рабочие дни числовое значение, а в выходные — строку символов.) Создать набор функций для проверки возвращаемых значений достаточно просто. Пример функции из подобного набора приведен ниже.

function isNum(arg){

return parseFloat(arg)!=NaN;

}

где NaN — сокращение от "not a number" ("не число"). Это специальная переменная JavaScript, предназначенная для обработки ошибок числовых форматов. Приведенная выше функция возвращает значение true для строки, начинающейся с цифр. Функции parseFloat О и parselntO пытаются извлечь числовое значение из переданного им параметра. Однако выражение parseFloat("64 hectares") вернет значение 64, а не NaN.

Продолжим работу над функцией addAreas {).

function

addAreas(si,s2){

var total=null;

 

if (hasArea(sl)

&& hasArea(s2)){

var

al=sl.getAreaf); var a2=s2.getArea();

if {isNum(al)

&& isNum(a2)){total=parseFloat(al)+parseFloat(a2);

}

 

 

}

 

 

return

total;

 

}

 

 

В данном случае для обоих параметров вызывается функция parseFloat (). Это сделано для того, чтобы обеспечить по возможности обработку любых строк, которые могут быть случайно переданы функции. Если si содержит значение 32, a s2 — значение 64 hectares, то функция addAreas () вернет число 96. Если бы функция parseFloat () не использовалась, то возвращаемым значением была бы строка 3264 hectares!

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

Мы обсудили язык JavaScript с точки зрения объектов. Теперь перейдем на уровень функций и рассмотрим их особенности.