WWW.DISS.SELUK.RU

БЕСПЛАТНАЯ ЭЛЕКТРОННАЯ БИБЛИОТЕКА
(Авторефераты, диссертации, методички, учебные программы, монографии)

 

Pages:     || 2 | 3 | 4 | 5 |   ...   | 7 |

«5 Включ е ает из Aja да x и D ни OM е JavaScript Подробное руководство Дэвид Флэнаган The Definitive Guide Fifth Edition David Flanagan Подробное руководство Пятое издание Дэвид Флэнаган Санкт Петербург–Москва 2008 ...»

-- [ Страница 1 ] --

Создание активных веб страниц

5 Включ

е ает

из Aja

да x и D

ни OM

е

JavaScript

Подробное руководство

Дэвид Флэнаган

The Definitive Guide

Fifth Edition

David Flanagan Подробное руководство Пятое издание Дэвид Флэнаган Санкт Петербург–Москва 2008 Дэвид Флэнаган JavaScript. Подробное руководство, 5 е издание Перевод А. Киселева Главный редактор А. Галунов Зав. редакцией Н. Макарова Научный редактор О. Цилюрик Редактор А. Жданов Корректор С. Минин Верстка Д. Орлова Флэнаган Д.

JavaScript. Подробное руководство. – Пер. с англ. – СПб: Символ Плюс, 2008. – 992 с., ил.

ISBN 10: 5 93286 ISBN 13: 978 5 93286 Пятое издание бестселлера «JavaScript. Подробное руководство» полностью обновлено. Рассматриваются взаимодействие с протоколом HTTP и примене ние технологии Ajax, обработка XML документов, создание графики на сторо не клиента с помощью тега, пространства имен в JavaScript, необхо димые для разработки сложных программ, классы, замыкания, Flash и встра ивание сценариев JavaScript в Java приложения.

Часть I знакомит с основами JavaScript. В части II описывается среда разра ботки сценариев, предоставляемая веб броузерами. Многочисленные примеры демонстрируют, как генерировать оглавление HTML документа, отображать анимированные изображения DHTML, автоматизировать проверку правиль ности заполнения форм, создавать всплывающие подсказки с использованием Ajax, как применять XPath и XSLT для обработки XML документов, загру женных с помощью Ajax. Часть III – обширный справочник по базовому Java Script (классы, объекты, конструкторы, методы, функции, свойства и кон станты, определенные в JavaScript 1.5 и ECMAScript v3). Часть IV – справоч ник по клиентскому JavaScript (API веб броузеров, стандарт DOM API Level и недавно появившиеся стандарты: объект XMLHttpRequest и тег ).

ISBN 10: 5 93286 ISBN 13: 978 5 93286 ISBN 0 596 10199 6 (англ) © Издательство Символ Плюс, Authorized translation of the English edition © 2006 O’Reilly Media, Inc. This trans lation is published and sold by permission of O’Reilly Media, Inc., the owner of all rights to publish and sell the same.

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

Издательство «Символ Плюс». 199034, Санкт Петербург, 16 линия, 7, тел. (812) 324 5353, www.symbol.ru. Лицензия ЛП N 000054 от 25.12.98.

Налоговая льгота – общероссийский классификатор продукции ОК 005 93, том 2; 953000 – книги и брошюры.

Подписано в печать 14.02.2008. Формат 701001/16. Печать офсетная.

Объем 62 печ. л. Тираж 2000 экз. Заказ N Отпечатано с готовых диапозитивов в ГУП «Типография «Наука»

199034, Санкт Петербург, 9 линия, 12.

Эта книга посвящается всем, кто учит жить мирно и противостоит насилию.

Оглавление Предисловие...................................................... 1. Введение в JavaScript.............................................. 1.1. Что такое JavaScript............................................ 1.2. Версии JavaScript............................................... 1.3. Клиентский JavaScript.......................................... 1.4. Другие области использования JavaScript.......................... 2.3. Символы разделители и переводы строк........................... 3.13. Объекты обертки для элементарных типов данных................. 3.14. Преобразование объектов в значения элементарных типов.......... Оглавление 6.20. Итоговая таблица JavaScript инструкций........................ 7.3. Объекты как ассоциативные массивы............................ 7.4. Свойства и методы универсального класса Object.................. 8.8. Область видимости функций и замыкания........................ 9. Классы, конструкторы и прототипы................................ 9.3. Объектно ориентированный язык JavaScript...................... 9.8. Пример: вспомогательный метод defineClass()..................... 10.1. Создание модулей и пространств имен........................... 10.2. Импорт символов из пространств имен.......................... 10.3. Модуль со вспомогательными функциями....................... 11. Шаблоны и регулярные выражения............................... 11.1. Определение регулярных выражений........................... 11.2. Методы класса String для поиска по шаблону.................... 12. Разработка сценариев для Java приложений..................... 13.2. Встраивание JavaScript кода в HTML документы................. 13.9. Другие реализации JavaScript во Всемирной паутине............. 14.8. Работа с несколькими окнами и фреймами....................... 15.1. Динамическое содержимое документа........................... 15.3. Ранняя упрощенная модель DOM: коллекции 15.9. Пример: динамическое создание оглавления..................... Оглавление 16.3. Использование стилей в сценариях............................. 17.2. Развитые средства обработки событий в модели DOM Level 2....... 17.3. Модель обработки событий Internet Explorer..................... 19. Cookies и механизм сохранения данных на стороне клиента....... 20.1. Использование объекта XMLHttpRequest........................ 20.2. Примеры и утилиты с объектом XMLHttpRequest................. 20.4. Взаимодействие с протоколом HTTP с помощью тега После загрузки в броузер, поддерживающий JavaScript, этот сценарий выдаст результат, показанный на рис. 1.1.



Рис. 1.1. Веб страница, сгенерированная с помощью JavaScript Как видно из этого примера, для встраивания JavaScript кода в HTML файл были использованы теги. О теге 1.4. Другие области использования JavaScript JavaScript – это язык программирования общего назначения, и его использова ние не ограничено веб броузерами. Изначально JavaScript разрабатывался с прицелом на встраивание в любые приложения и предоставление возможности исполнять сценарии. С самых первых дней веб серверы компании Netscape включали в себя интерпретатор JavaScript, что позволяло исполнять JavaScript сценарии на стороне сервера. Аналогичным образом в дополнение к Internet Ex plorer корпорация Microsoft использует интерпретатор JScript в своем веб сер вере IIS и в продукте Windows Scripting Host. Компания Adobe задействует про изводный от JavaScript язык для управления своим проигрывателем Flash фай лов. Компания Sun также встроила интерпретатор JavaScript в дистрибутив Java 6.0, что существенно облегчает возможность встраивания сценариев в лю бое Java приложение (о том, как это делается, рассказывается в главе 12).

И Netscape, и Microsoft сделали доступными свои реализации интерпретаторов JavaScript для компаний и программистов, желающих включить их в свои при ложения. Интерпретатор, созданный в компании Netscape, был выпущен как свободно распространяемое ПО с открытыми исходными текстами и ныне досту пен через организацию Mozilla (http://www.mozilla.org/js/). Mozilla фактически распространяет две разные версии интерпретатора JavaScript 1.5: один написан на языке C и называется SpiderMonkey, другой написан на языке Java и, что весьма лестно для автора книги, называется Rhino (носорог).

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

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

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

Например, чтобы вместо факториалов вывести последовательность чисел Фибо наччи, пример 1.1 можно переписать следующим образом:

Этот отрывок может показаться запутанным (и не волнуйтесь, если вы пока не понимаете его), но для того чтобы поэкспериментировать с подобными коротки ми программами, достаточно набрать код и запустить его в веб броузере в каче стве файла с локальным URL адресом. Обратите внимание, что для вывода ре зультатов вычислений используется метод document.write(). Это полезный прием при экспериментах с JavaScript. В качестве альтернативы для отображения тек стового результата в диалоговом окне можно применять метод alert():

alert("Fibonacci (" + i + ") = " + fib);

Отметьте, что в подобных простых экспериментах с JavaScript можно опускать теги, и в HTML файле.

Для еще большего упрощения экспериментов с JavaScript можно использовать URL адрес со спецификатором псевдопротокола javascript: для вычисления зна чения JavaScript выражения и получения результата. Такой URL адрес состоит из спецификатора псевдопротокола (javascript:), за которым указывается произ вольный JavaScript код (инструкции отделяются одна от другой точками с запя той). Загружая URL адрес с псевдопротоколом, броузер просто исполняет Java Script код. Значение последнего выражения в таком URL адресе преобразуется в строку, и эта строка выводится веб броузером в качестве нового документа. На пример, для того чтобы проверить свое понимание некоторых операторов и инст рукций языка JavaScript, можно набрать следующие URL адреса в адресном по ле веб броузера:

javascript:5% javascript:x = 3; (x < 5)? "значение x меньше": "значение x больше" javascript:d = new Date(); typeof d;

javascript:for(i=0,j=1,k=0,fib=1; i "+result+"\n";

// Типовое приглашение к вводу всегда должно выводиться:

message += "Введите выражение, которое следует вычислить:";

// Получить ввод пользователя, вывести приглашение и использовать // последнее выражение как значение по умолчанию.

expression = prompt(message, expression);

// Если пользователь ничего не ввел (или щелкнул на кнопке Отменить), // работу в точке останова можно считать оконченной // В противном случае вычислить выражение с использованием // замыкания в инспектируемом контексте исполнения.

// Результаты будут выведены на следующей итерации.

result = inspector(expression);

Обратите внимание: для вывода информации и ввода строки пользователя функ ция inspect() из примера 8.7 задействует метод Window.prompt() (подробнее об этом методе рассказывается в четвертой части книги).

Рассмотрим пример функции, вычисляющей факториал числа и использующей механизм точек останова:

function factorial(n) { // Создать замыкание для этой функции var inspector = function($) { return eval($); } inspect(inspector, "Вход в функцию factorial()");

inspect(inspector, "factorial( ) loop");

inspect(inspector, "Выход из функции factorial()");

8.8.4.2. Замыкания и утечки памяти в Internet Explorer В веб броузере Microsoft Internet Explorer используется достаточно слабая разно видность механизма сборки мусора для объектов ActiveX и DOM элементов на 8.9. Конструктор Function() стороне клиента. Для этих элементов на стороне клиента выполняется подсчет ссылок, и они утилизируются интерпретатором, только когда значение счетчика ссылок достигает нуля. Однако такая схема оказывается неработоспособной в случае циклических ссылок, например, когда базовый JavaScript объект ссыла ется на элемент документа, а этот элемент имеет свойство (например, обработчик события), которое, в свою очередь, хранит ссылку на базовый JavaScript объект.

Такого рода циклические ссылки часто возникают при работе с замыканиями.

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

Обсуждение этой проблемы выходит за рамки темы книги. За дополнительной информацией обращайтесь по адресу: http://msdn.microsoft.com/library/en us/ IETechCol/dnwebgen/ie_leak_patterns.asp.

8.9. Конструктор Function() Как уже говорилось ранее, функции обычно определяются с помощью ключево го слова function либо в форме определения функции, либо посредством функ ционального литерала. Однако помимо этого существует возможность создания функций с помощью конструктора Function(). Создание функций с помощью конструктора Function() обычно сложнее, чем с помощью функционального ли терала, поэтому такая методика распространена не так широко. Вот пример соз дания функции подобным образом:

var f = new Function("x", "y", "return x*y;");

Эта строка создает новую функцию, более или менее эквивалентную функции, определенной с помощью более привычного синтаксиса:

function f(x, y) { return x*y; } Конструктор Function() принимает любое количество строковых аргументов. По следний аргумент – это тело функции. Он может содержать произвольные Java Script инструкции, отделенные друг от друга точками с запятой. Все остальные аргументы конструктора представляют собой строки, задающие имена парамет ров определяемой функции. Если вы определяете функцию без аргументов, кон структору передается только одна строка – тело функции.

Обратите внимание: конструктору Function() не передается аргумент, задающий имя создаваемой им функции. Неименованные функции, созданные с помощью конструктора Function(), иногда называются анонимными функциями.

Есть несколько моментов, связанных с конструктором Function(), о которых сле дует упомянуть особо:

• Конструктор Function() позволяет динамически создавать и компилировать функции во время исполнения программы. В чем то он напоминает функцию eval() (за информацией обращайтесь к третьей части книги).

• Конструктор Function() компилирует и создает новую функцию при каждом вызове. Если вызов конструктора производится в теле цикла или часто вызы ваемой функции, это может отрицательно сказаться на производительности программы. В противовес этому функциональные литералы или вложенные функции, находящиеся внутри цикла, не перекомпилируются на каждой итерации, а кроме того, в случае литералов не создается новый объект функ ции. (Хотя, как уже отмечалось ранее, может создаваться новое замыкание, хранящее лексический контекст, в котором была определена функция.) • И последний очень важный момент: когда функция создается с помощью конструктора Function(), не учитывается текущая лексическая область види мости – функции, созданные таким способом, всегда компилируются как функции верхнего уровня, что наглядно демонстрируется в следующем фраг var y = "глобальная";

function constructFunction() { return new Function("return y"); // Не сохраняет локальный контекст!

// Следующая строка выведет слово "глобальная", потому что функция, // созданная конструктором Function(), не использует локальный контекст.

// Если функция была определена как литерал, // эта строка вывела бы слово "локальная".

alert(constructFunction()()); // Выводит слово "глобальная" Введение в JavaScript объекты было дано в главе 7, в которой каждый объект трактовался как уникальный набор свойств, отличающих его от любых других объектов. В большинстве объектно ориентированных языков программирования существует возможность определять классы объектов и затем создавать отдель ные объекты как экземпляры этих классов. Например, можно объявить класс Complex, призванный представлять комплексные числа и выполнять арифметиче ские действия с этими числами, тогда объект Complex представлял бы единствен ное комплексное число и мог бы создаваться как экземпляр этого класса.

Язык JavaScript не обладает полноценной поддержкой классов, как другие язы ки, например Java, C++ или C#.1 Тем не менее в JavaScript существует возмож ность определять псевдоклассы с помощью таких инструментальных средств, как функции конструкторы и прототипы объектов. В этой главе рассказывается о конструкторах и прототипах и приводится ряд примеров некоторых псевдо классов и даже псевдоподклассов JavaScript.

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

9.1. Конструкторы В главе 7 демонстрировался порядок создания новых пустых объектов как с по мощью литерала {}, так и с помощью следующего выражения:

new Object() Кроме того, была продемонстрирована возможность создания объектов других типов примерно следующим образом:

Полноценную поддержку классов планируется реализовать в JavaScript 2.0.

var array = new Array(10);

var today = new Date( );

За оператором new должно быть указано имя функции конструктора. Оператор создает новый пустой объект без каких либо свойств, а затем вызывает функ цию, передавая ей только что созданный объект в виде значения ключевого слова this. Функция, применяемая совместно с оператором new, называется функцией конструктором, или просто конструктором. Главная задача конструктора за ключается в инициализации вновь созданного объекта – установке всех его свойств, которые необходимо инициализировать до того, как объект сможет ис пользоваться программой. Чтобы определить собственный конструктор, доста точно написать функцию, добавляющую новые свойства к объекту, на который ссылается ключевое слово this. В следующем фрагменте приводится определе ние конструктора, с помощью которого затем создаются два новых объекта:

// Определяем конструктор.

// Обратите внимание, как инициализируется объект с помощью "this".

function Rectangle(w, h) { this.height = h;

// Вызываем конструктор для создания двух объектов Rectangle. Мы передаем ширину и высоту // конструктору, чтобы можно было правильно проинициализировать оба новых объекта.

var rect1 = new Rectangle(2, 4); // rect1 = { width:2, height:4 };

var rect2 = new Rectangle(8.5, 11); // rect2 = { width:8.5, height:11 };

Обратите внимание на то, как конструктор использует свои аргументы для ини циализации свойств объекта, на который ссылается ключевое слово this. Здесь мы определили класс объектов, просто создав соответствующую функцию кон структор – все объекты, созданные с помощью конструктора Rectangle(), гаран тированно будут иметь инициализированные свойства width и height. Это означа ет, что учитывая данное обстоятельство, можно организовать единообразную ра боту со всеми объектами класса Rectangle. Поскольку каждый конструктор опре деляет отдельный класс объектов, стилистически очень важно присвоить такое имя функции конструктору, которое будет явно отражать класс объектов, созда ваемых с ее помощью. Например, строка new Rectangle(1, 2), создающая объект прямоугольника, выглядит гораздо более понятно, нежели new init_rect(1, 2).

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

9.2. Прототипы и наследование В главе 8 говорилось, что метод – это функция, которая вызывается как свойст во объекта. Когда функция вызывается таким способом, объект, посредством ко торого производится вызов, становится значением ключевого слова this. Пред положим, что необходимо рассчитать площадь прямоугольника, представленно го объектом Rectangle. Вот один из возможных способов:

function computeAreaOfRectangle(r) { return r.width * r.height; } 9.2. Прототипы и наследование Эта функция прекрасно справляется с возложенными на нее задачами, но она не является объектно ориентированной. Работая с объектом, лучше всего вызывать методы этого объекта, а не передавать объекты посторонним функциям в качест ве аргументов. Этот подход демонстрируется в следующем фрагменте:

// Создать объект Rectangle var r = new Rectangle(8.5, 11);

// Добавить к объекту метод r.area = function() { return this.width * this.height; } // Теперь рассчитать площадь, вызвав метод объекта var a = r.area();

Конечно же, не совсем удобно добавлять новый метод к объекту перед его ис пользованием. Однако ситуацию можно улучшить, если инициализировать свойство area в функции конструкторе. Вот как выглядит улучшенная реализа ция конструктора Rectangle():

function Rectangle(w, h) { this.area = function( ) { return this.width * this.height; } С новой версией конструктора тот же самый алгоритм можно реализовать по другому:

// Найти площадь листа бумаги формата U.S. Letter в квадратных дюймах var r = new Rectangle(8.5, 11);

var a = r.area();

Такое решение выглядит гораздо лучше, но оно по прежнему не является опти мальным. Каждый созданный прямоугольник будет иметь три свойства. Свойст ва width и height могут иметь уникальные значения для каждого прямоугольни ка, но свойство area каждого отдельно взятого объекта Rectangle всегда будет ссылаться на одну и ту же функцию (разумеется, это свойство можно изменить в процессе работы, но, как правило, предполагается, что методы объекта не должны меняться). Применение отдельных свойств для хранения методов объ ектов, которые могли бы совместно использоваться всеми экземплярами одного и того же класса, – это достаточно неэффективное решение.

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

В предыдущем разделе было показано, как оператор new создает пустой объект и затем вызывает функцию конструктор. Но история на этом не заканчивается.

После создания пустого объекта оператор new устанавливает в этом объекте ссыл ку на прототип. Прототипом объекта является значение свойства prototype функции конструктора. Все функции имеют свойство prototype, которое ини циализируется в момент определения функции. Начальным значением этого свойства является объект с единственным свойством. Это свойство называется constructor и значением его является ссылка на саму функцию конструктор, с которой этот прототип ассоциируется. (Описание свойства constructor приводи лось в главе 7, здесь же объясняется, почему каждый объект обладает свойством constructor.) Любые свойства, добавленные к прототипу, автоматически стано вятся свойствами объектов, инициализируемых конструктором.

Более понятно это можно объяснить на примере. Вот новая версия конструктора Rectangle():

// Функция конструктор инициализирует те свойства, которые могут // иметь уникальные значения для каждого отдельного экземпляра.

function Rectangle(w, h) { // Прототип объекта содержит методы и другие свойства, которые должны // совместно использоваться всеми экземплярами этого класса.

Rectangle.prototype.area = function() { return this.width * this.height; } Конструктор определяет «класс» объектов и инициализирует свойства, такие как width и height, которые могут отличаться для каждого экземпляра класса. Объект прототип связан с конструктором, и каждый объект, инициализируемый конст руктором, наследует тот набор свойств, который имеется в прототипе. Это значит, что объект прототип – идеальное место для методов и других свойств констант.

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

Унаследованные свойства ничем не отличаются от обычных свойств объекта.

Они поддаются перечислению в цикле for/in и могут проверяться с помощью опе ратора in. Отличить их можно только с помощью метода Object.hasOwnProperty():

var r = new Rectangle(2, 3);

r.hasOwnProperty("width"); // true: width – непосредственное свойство "r" r.hasOwnProperty("area"); // false: area – унаследованное свойство "r" 9.2.1. Чтение и запись унаследованных свойств У каждого класса имеется один объект прототип с одним наборов свойств, но по тенциально может существовать множество экземпляров класса, каждый из кото рых наследует свойства прототипа. Свойство прототипа может наследоваться мно гими объектами, поэтому интерпретатор JavaScript должен обеспечить фундамен тальную асимметричность между чтением и записью значений свойств. Когда вы читаете свойство p объекта o, JavaScript сначала проверяет, есть ли у объекта o свойство с именем p. Если такого свойства нет, то проверяется, есть ли свойство с именем p в объекте прототипе. Так работает наследование на базе прототипов.

9.2. Прототипы и наследование Однако когда свойству присваивается значение, JavaScript не использует объ ект прототип. Чтобы понять почему, подумайте, что произошло бы в этом слу чае: предположим, вы пытаетесь установить значение свойства o.p, а у объекта o нет свойства с именем p. Предположим теперь, что JavaScript идет дальше и ищет свойство p в объекте прототипе объекта o и позволяет вам изменить зна чение свойства прототипа. В результате вы изменяете значение p для всего клас са объектов, а это вовсе не то, что требовалось.

Поэтому наследование свойств происходит только при чтении значений свойств, но не при их записи. Если вы устанавливаете свойство p в объекте o, который на следует это свойство от своего прототипа, происходит создание нового свойства непосредственно в объекте p. Теперь, когда объект o имеет собственное свойство с именем p, он больше не наследует значение p от прототипа. Когда вы читаете значение p, JavaScript сначала ищет его в свойствах объекта o. Так как он нахо дит свойство p, определенное в o, ему не требуется искать его в объекте прототи пе, и JavaScript никогда не будет искать определенное в нем значение p. Мы ино гда говорим, что свойство p «затеняет» (скрывает) свойство p объекта прототипа.

Наследование прототипов может показаться запутанным, но все вышеизложен ное хорошо иллюстрирует рис. 9.1.

Рис. 9.1. Объекты и прототипы Свойства прототипа совместно используются всеми объектами класса, поэтому, как правило, их имеет смысл применять только для определения свойств, совпа дающих для всех объектов класса. Это делает прототипы идеальными для опре деления методов. Другие свойства с постоянными значениями (такими как ма тематические константы) также подходят для определения в качестве свойств прототипа. Если класс определяет свойство с очень часто используемым значе нием по умолчанию, то можно определить это свойство и его значение по умол чанию в объекте прототипе. Тогда те немногие объекты, которые хотят изме нить значение по умолчанию, могут создавать свои частные копии свойства и оп ределять собственные значения, отличные от значения по умолчанию.

9.2.2. Расширение встроенных типов Не только классы, определенные пользователем, имеют объекты прототипы.

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

// Возвращает true, если последним символом является значение аргумента c String.prototype.endsWith = function(c) { return (c == this.charAt(this.length 1)) Определив новый метод endsWith() в объекте прототипе String, мы сможем обра титься к нему следующим образом:

var message = "hello world";

message.endsWith('h') // Возвращает false message.endsWith('d') // Возвращает true Против такого расширения возможностей встроенных типов можно привести достаточно сильные аргументы: в случае расширения некоторого встроенного типа, по сути, создается самостоятельная версия базового прикладного Java Script интерфейса. Любые другие программисты, которые будут читать или со провождать ваш код, могут прийти в недоумение, встретив методы, о которых они ранее не слышали. Если только вы не собираетесь создавать низкоуровне вую JavaScript платформу, которая будет воспринята многими другими про граммистами, лучше оставить прототипы встроенных объектов в покое.

Обратите внимание: никогда не следует добавлять свойства к объекту Object.pro totype. Любые добавляемые свойства и методы становятся перечислимыми для цикла for/in, поэтому добавив их к объекту Object.prototype, вы сделаете их до ступными во всех JavaScript объектах. Пустой объект {}, как предполагается, не имеет перечислимых свойств. Любое расширение Object.prototype превратит ся в перечислимое свойство пустого объекта, что, скорее всего, приведет к нару шениям в функционировании программного кода, который работает с объекта ми как с ассоциативными массивами.

Техника расширения встроенных объектов, о которой сейчас идет речь, гаранти рованно работает только в случае применения к «родным» объектам базового языка JavaScript. Когда JavaScript встраивается в некоторый контекст, напри мер в веб броузер или в Java приложение, он получает доступ к дополнитель ным «платформозависимым» объектам, таким как объекты веб броузера, пред 9.2. Прототипы и наследование ставляющие содержимое документа. Эти объекты, как правило, не имеют ни конструктора, ни прототипа и потому недоступны для расширения.

Один из случаев, когда можно расширять прототипы встроенных объектов дос таточно безопасно и даже желательно, – это добавление стандартных методов прототипов в старых несовместимых реализациях JavaScript, где эти свойства и методы отсутствуют. Например, метод Function.apply() в Microsoft Internet Exp lorer версий 4 и 5 не поддерживается. Это достаточно важная функция, поэтому иногда вам может встретиться код, который добавляет эту функцию:

// Если функция Function.apply() не реализована, можно добавить // этот фрагмент, основанный на разработках Аарона Будмана (Aaron Boodman).

if (!Function.prototype.apply) { // Вызвать эту функцию как метод заданного объекта с указанными // параметрами. Для этих целей здесь используется функция eval() Function.prototype.apply = function(object, parameters) { var o = object || window; // Объект, через который выполняется вызов var args = parameters || []; // Передаваемые аргументы // Временно превратить функцию в метод объекта o.

// Для этого выбирается имя метода, которое скорее всего отсутствует // Вызов метода выполняется с помощью eval().

// Для этого необходимо сконструировать строку вызова.

// В первую очередь собирается список аргументов.

// Объединить строки с аргументами в единый список, // разделив аргументы запятыми.

var arglist = stringArgs.join(",");

// Теперь собрать всю строку вызова метода var methodcall = "o._$_apply_$_(" + arglist + ");";

// С помощью функции eval() вызвать метод var result = eval(methodcall);

В качестве еще одного примера рассмотрим новые методы массивов, реализован ные в Firefox 1.5 (см. раздел 7.7.10). Если необходимо задействовать метод Ar ray.map() и при этом желательно сохранить совместимость с платформами, где этот метод не поддерживается, можно воспользоваться следующим фрагментом:

// Array.map() вызывает функцию f для каждого элемента массива // и возвращает новый массив, содержащий результаты каждого вызова функции.

// Если map() вызывается с двумя аргументами, функция f вызывается как метод // второго аргумента. Функции f() передается 3 аргумента. Первый представляет // значение элемента массива, второй – индекс элемента, третий – сам массив.

// В большинстве случаев достаточно передать только первый аргумент.

if (!Array.prototype.map) { Array.prototype.map = function(f, thisObject) { results.push(f.call(thisObject, this[i], i, this));

9.3. Объектно ориентированный язык JavaScript Хотя JavaScript поддерживает тип данных, который мы называем объектом, в нем нет формального понятия класса. Это в значительной степени отличает его от классических объектно ориентированных языков программирования, таких как C++ и Java. Общая черта объектно ориентированных языков – это их стро гая типизация и поддержка механизма наследования на базе классов. По этому критерию JavaScript легко исключить из числа истинно объектно ориентиро ванных языков. С другой стороны, мы видели, что JavaScript активно использу ет объекты и имеет особый тип наследования на базе прототипов. JavaScript – это истинно объектно ориентированный язык. Он был реализован под влиянием некоторых других (относительно малоизвестных) объектно ориентированных языков, в которых вместо наследования на основе классов реализовано наследо вание на базе прототипов.

Несмотря на то что JavaScript – это объектно ориентированный язык, не бази рующийся на классах, он неплохо имитирует возможности языков на базе клас сов, таких как Java и C++. Я употребил термин «класс» в этой главе неформаль но. В данном разделе проводятся более формальные параллели между JavaScript и истинным наследованием на базе классов в таких языках, как Java и C++. Начнем с того, что определим некоторые базовые термины. Объект, как мы уже видели, – это структура данных, которая содержит различные фрагменты име нованных данных, а также может содержать методы для работы с этими фраг ментами данных. Объект группирует связанные значения и методы в единый удобный набор, который, как правило, облегчает процесс программирования, увеличивая степень модульности и возможности для многократного использова ния кода. Объекты в JavaScript могут иметь произвольное число свойств, и свой ства могут добавляться в объект динамически. В строго типизированных язы ках, таких как Java и C++, это не так. В них любой объект имеет предопределен ный набор свойств2, а каждое свойство имеет предопределенный тип. Имитируя объектно ориентированные приемы программирования при помощи JavaScript Этот раздел рекомендуется прочитать даже тем, кто незнаком с этими языками и упомянутым стилем объектно ориентированного программирования.

Обычно в Java и C++ они называются «полями», но здесь мы будем называть их свойствами, поскольку такая терминология принята в JavaScript.

9.3. Объектно ориентированный язык JavaScript объектов, мы, как правило, заранее определяем набор свойств для каждого объ екта и тип данных, содержащихся в каждом свойстве.

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

И JavaScript, и объектно ориентированные языки, основывающиеся на классах, допускают наличие множества объектов одного класса. Мы часто говорим, что объект – это экземпляр класса. Таким образом, одновременно может существо вать множество экземпляров любого класса. Иногда для описания процесса созда ния объекта (т. е. экземпляра класса) используется термин создание экземпляра.

В Java обычная практика программирования заключается в присвоении клас сам имен с первой прописной буквой, а объектам – со всеми строчными. Это со глашение помогает отличать классы и объекты в исходных текстах. Этому же соглашению желательно следовать и при написании программ на языке Java Script. Например, в предыдущих разделах мы определили класс Rectangle и соз давали экземпляры этого класса с именами, такими как rect.

Члены Java класса могут принадлежать одному из четырех основных типов:

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

9.3.1. Свойства экземпляра Каждый объект имеет собственные копии свойств экземпляра. Другими слова ми, если имеется 10 объектов данного класса, то имеется и 10 копий каждого свойства экземпляра. Например, в нашем классе Rectangle любой объект Rectang le имеет свойство width, определяющее ширину прямоугольника. В данном слу чае width представляет собой свойство экземпляра. А поскольку каждый объект имеет собственную копию свойства экземпляра, доступ к этим свойствам можно получить через отдельные объекты. Если, например, r – это объект, представ ляющий собою экземпляр класса Rectangle, мы можем получить его ширину сле дующим образом:

r.width По умолчанию любое свойство объекта в JavaScript является свойством экземп ляра. Однако чтобы по настоящему имитировать объектно ориентированное программирование, мы будем говорить, что свойства экземпляра в JavaScript – это те свойства, которые создаются и/или инициализируются функцией конст руктором.

9.3.2. Методы экземпляра Метод экземпляра во многом похож на свойство экземпляра, за исключением того, что это метод, а не значение. (В Java функции и методы не являются дан ными, как это имеет место в JavaScript, поэтому в Java данное различие выра жено более четко.) Методы экземпляра вызываются по отношению к определен ному объекту, или экземпляру. Метод area() нашего класса Rectangle представ ляет собой метод экземпляра. Он вызывается для объекта Rectangle следующим образом:

a = r.area( );

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

9.3.2.1. Методы экземпляра и ключевое слово this Если у вас есть опыт работы с такими языками, как Java или C++, вы наверняка заметили одно важное отличие между методами экземпляров в этих языках и методами экземпляров в JavaScript. В Java и C++ область видимости методов экземпляров включает объект this. Так, например, метод area в Java может быть реализован проще:

return width * height;

Однако в JavaScript приходится явно вставлять ключевое слово this перед име нами свойств:

return this.width * this.height;

Если вам покажется неудобным вставлять this перед каждым именем свойства эк земпляра, можно воспользоваться инструкцией with (описываемой в разделе 6.18), например:

Rectangle.prototype.area = function( ) { 9.3.3. Свойства класса Свойство класса в Java – это свойство, связанное с самим классом, а не с каж дым экземпляром этого класса. Независимо от того, сколько создано экземпля ров класса, есть только одна копия каждого свойства класса. Так же, как свойст ва экземпляра доступны через экземпляр класса, доступ к свойствам класса можно получить через сам класс. Запись Number.MAX_VALUE – это пример обраще ния к свойству класса в JavaScript, означающая, что свойство MAX_VALUE доступно через класс Number. Так как имеется только одна копия каждого свойства класса, свойства класса по существу являются глобальными. Однако их достоинство со стоит в том, что они связаны с классом и имеют логичную нишу, позицию в про странстве имен JavaScript, где они вряд ли будут перекрыты другими свойства ми с тем же именем. Очевидно, что свойства класса имитируются в JavaScript 9.3. Объектно ориентированный язык JavaScript простым определением свойства самой функции конструктора. Например, свой ство класса Rectangle.UNIT для хранения единичного прямоугольника с размера ми 1x1 можно создать так:

Rectangle.UNIT = new Rectangle(1,1);

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

9.3.4. Методы класса Метод класса – это метод, связанный с классом, а не с экземпляром класса; он вызывается через сам класс, а не через конкретный экземпляр класса. Метод Date.parse() (описываемый в третьей части книги) – это метод класса. Он всегда вызывается через объект конструктора Date, а не через конкретный экземпляр класса Date.

Поскольку методы класса вызываются через функцию конструктор, они не могут использовать ключевое слово this для ссылки на какой либо конкретный экземп ляр класса, поскольку в данном случае this ссылается на саму функцию конст руктор. (Обычно ключевое слово this в методах классов вообще не используется.) Как и свойства класса, методы класса являются глобальными. Методы класса не работают с конкретным экземпляром, поэтому их, как правило, проще рассмат ривать в качестве функций, вызываемых через класс. Как и в случае со свойст вами класса, связь этих функций с классом дает им в пространстве имен Java Script удобную нишу и предотвращает возникновение конфликтов имен. Для то го чтобы определить метод класса в JavaScript, требуется сделать соответствую щую функцию свойством конструктора.

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

Пример 9.1. Класс Circle // Начнем с конструктора.

function Circle(radius) { // r – свойство экземпляра, оно определяется // и инициализируется конструктором.

// Circle.PI – свойство класса, т. е. свойство функции конструктора.

Circle.PI = 3.14159;

// Метод экземпляра, который рассчитывает площадь круга.

Circle.prototype.area = function( ) { return Circle.PI * this.r * this.r; } // Метод класса – принимает два объекта Circle и возвращает объект с большим радиусом.

Circle.max = function(a,b) { // Примеры использования каждого из этих полей:

var c = new Circle(1.0); // Создание экземпляра класса Circle var a = c.area(); // Вызов метода экземпляра area() var x = Math.exp(Circle.PI); // Обращение к свойству PI класса для выполнения расчетов var d = new Circle(1.2); // Создание другого экземпляра класса Circle var bigger = Circle.max(c,d); // Вызов метода класса max() 9.3.6. Пример: комплексные числа В примере 9.2 представлен еще один способ определения класса объектов в Java Script, но несколько более формальный, чем предыдущий. Код и комментарии достойны тщательного изучения.

Пример 9.2. Класс комплексных чисел * Complex.js:

* В этом файле определяется класс Complex для представления комплексных чисел.

* Вспомним, что комплексное число – это сумма вещественной и мнимой * частей числа, и что мнимое число i – это квадратный корень из 1.

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

function Complex(real, imaginary) { this.x = real; // Вещественная часть числа this.y = imaginary; // Мнимая часть числа * Второй шаг в определении класса – это определение методов экземпляра * (и возможно других свойств) в объекте прототипе конструктора.

* Любые свойства, определенные в этом объекте, будут унаследованы всеми * экземплярами класса. Обратите внимание, что методы экземпляра * неявно работают с ключевым словом this. Для многих методов никаких * других аргументов не требуется.

// Возвращает модуль комплексного числа. Он определяется как расстояние // на комплексной плоскости до числа от начала координат (0,0).

Complex.prototype.magnitude = function() { return Math.sqrt(this.x*this.x + this.y*this.y);

// Возвращает комплексное число с противоположным знаком.

Complex.prototype.negative = function() { 9.3. Объектно ориентированный язык JavaScript return new Complex( this.x, this.y);

// Складывает данное комплексное число с заданным и возвращает // сумму в виде нового объекта.

Complex.prototype.add = function(that) { return new Complex(this.x + that.x, this.y + that.y);

// Умножает данное комплексное число на заданное и возвращает // произведение в виде нового объекта.

Complex.prototype.multiply = function(that) { return new Complex(this.x * that.x this.y * that.y, // Преобразует объект Complex в строку в понятном формате.

// Вызывается, когда объект Complex используется как строка.

Complex.prototype.toString = function() { return "{" + this.x + "," + this.y + "}";

// Проверяет равенство данного комплексного числа с заданным.

Complex.prototype.equals = function(that) { return this.x == that.x && this.y == that.y;

// Возвращает вещественную часть комплексного числа.

// Эта функция вызывается, когда объект Complex рассматривается // как числовое значение.

Complex.prototype.valueOf = function() { return this.x; } * Третий шаг в определении класса – это определение методов класса, * констант и других необходимых свойств класса как свойств самой * функции конструктора (а не как свойств объекта прототипа * конструктора). Обратите внимание, что методы класса не используют * ключевое слово this, они работают только со своими аргументами.

// Складывает два комплексных числа и возвращает результат.

Complex.add = function (a, b) { return new Complex(a.x + b.x, a.y + b.y);

// Умножает два комплексных числа и возвращает полученное произведение.

Complex.multiply = function(a, b) { return new Complex(a.x * b.x a.y * b.y, // Несколько предопределенных комплексных чисел.

// Они определяются как свойства класса, в котором могут использоваться как "константы".

// (Хотя в JavaScript невозможно определить свойства, доступные только для чтения.) Complex.ZERO = new Complex(0,0);

Complex.ONE = new Complex(1,0);

Complex.I = new Complex(0,1);

9.3.7. Частные члены Одна из наиболее общих характеристик традиционных объектно ориентирован ных языков программирования, таких как C++, заключается в возможности объявления частных (private) свойств класса, обращаться к которым можно только из методов этого класса и недоступных за пределами класса. Распростра ненная техника программирования, называемая инкапсуляцией данных, заклю чается в создании частных свойств и организации доступа к этим свойствам толь ко через специальные методы чтения/записи. JavaScript позволяет имитировать такое поведение посредством замыканий (эта тема обсуждается в разделе 8.8), но для этого необходимо, чтобы методы доступа хранились в каждом экземпляре класса и по этой причине не могли наследоваться от объекта прототипа.

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

function ImmutableRectangle(w, h) { // Этот конструктор не создает свойства объекта, где может храниться // ширина и высота. Он просто определяет в объекте методы доступа // Эти методы являются замыканиями и хранят значения ширины и высоты // в своих цепочках областей видимости.

this.getWidth = function() { return w; } this.getHeight = function() { return h; } // Обратите внимание: класс может иметь обычные методы в объекте прототипе.

ImmutableRectangle.prototype.area = function( ) { return this.getWidth( ) * this.getHeight( );

Первенство открытия этой методики (или, по крайней мере, первенство публи кации), вообще говоря, принадлежит Дугласу Крокфорду (Douglas Crockford).

Его обсуждение этой темы можно найти на странице http://www.crockford.com/ javascript/private.html.

9.4. Общие методы класса Object Когда в JavaScript определяется новый класс, некоторые из его методов следует считать предопределенными. Эти методы подробно описываются в следующих подразделах.

9.4.1. Метод toString() Идея метода toString() состоит в том, что каждый класс объектов должен иметь собственное особое строковое представление и поэтому определять соответствую щий метод toString() для преобразования объектов в строковую форму. То есть определяя класс, необходимо определить для него специальный метод toString(), чтобы экземпляры класса могли быть преобразованы в осмысленные строки.

Строка должна содержать информацию о преобразуемом объекте, т. к. это может потребоваться для нужд отладки. Если способ преобразования в строку выбран правильно, он также может быть полезным в самих программах. Кроме того, 9.4. Общие методы класса Object можно создать собственную реализацию статического метода parse() для преоб разования строки, возвращаемой методом toString(), обратно в форму объекта.

Класс Complex из примера 9.2 уже содержит реализацию метода toString(), а в сле дующем фрагменте приводится возможная реализация метода toString() для класса Circle:

Circle.prototype.toString = function () { return "[Круг радиуса " + this.r + " с центром в точке (" После определения такого метода toString() типичный объект Circle может быть преобразован в следующую строку:

"Круг радиуса 1 с центром в точке (0, 0)."

9.4.2. Метод valueOf() Метод valueOf() во многом похож на метод toString(), но вызывается, когда Java Script требуется преобразовать объект в значение какого либо элементарного ти па, отличного от строкового – обычно в число. Когда это возможно, функция должна возвращать элементарное значение, каким либо образом представляю щее значение объекта, на который ссылается ключевое слово this.

По определению объекты не являются элементарными значениями, поэтому большинство объектов не имеют эквивалентного элементарного типа. Вследст вие этого метод valueOf(), определяемый по умолчанию классом Object, не вы полняет преобразования, а просто возвращает объект, с которым он был вызван.

Такие классы, как Number и Boolean, имеют очевидные элементарные эквивален ты, поэтому они переопределяют метод valueOf() так, чтобы он возвращал соот ветствующие значения. Именно поэтому объекты Number и Boolean могут вести се бя во многом так же, как эквивалентные им элементарные значения.

Иногда можно определить класс, имеющий какой то разумный элементарный эквивалент. В этом случае может потребоваться определить для этого класса специальный метод valueOf(). Если мы вернемся к примеру 9.2, то увидим, что метод valueOf() определен для класса Complex. Этот метод просто возвращает ве щественную часть комплексного числа. Поэтому в числовом контексте объект Complex ведет себя так, как будто является вещественным числом без мнимой со ставляющей. Рассмотрим, например, следующий фрагмент:

var c = Complex.sum(a,b); // c это комплексное число {7,5} При наличии метода valueOf() следует соблюдать одну осторожность: в случае преобразования объекта в строку метод valueOf() иногда имеет приоритет перед методом toString(). Поэтому, когда для класса определен метод valueOf() и надо, чтобы объект этого класса был преобразован в строку, может потребоваться явно указать на это, вызвав метод toString(). Продолжим пример с классом Complex:

alert("c = " + c); // Используется valueOf(); выводит "c = 7" alert("c = " + c.toString()); // Выводит "c = {7,5}" 9.4.3. Методы сравнения Операторы сравнения в JavaScript сравнивают объекты по ссылке, а не по значе нию. Так, если имеются две ссылки на объекты, то выясняется, ссылаются они на один и тот же объект или нет, но не выясняется, обладают ли разные объекты оди наковыми свойствами с одинаковыми значениями.1 Часто бывает удобным иметь возможность выяснить эквивалентность объектов или даже определить порядок их следования (например, с помощью операторов отношения < и >). Если вы опре деляете класс и хотите иметь возможность сравнивать экземпляры этого класса, вам придется определить соответствующие методы, выполняющие сравнение.

В языке программирования Java сравнение объектов производится с помощью методов, и подобный подход можно с успехом использовать в JavaScript. Чтобы иметь возможность сравнивать экземпляры класса, можно определить метод эк земпляра с именем equals(). Этот метод должен принимать единственный аргу мент и возвращать true, если аргумент эквивалентен объекту, метод которого был вызван. Разумеется, вам решать, что следует понимать под словом «эквива лентен» в контексте вашего класса. Обычно для определения того, равны ли объ екты, сравниваются значения свойств экземпляров двух объектов. Класс Complex из примера 9.2 как раз обладает таким методом equals().

Иногда возникает необходимость реализовать операции сравнения, чтобы выяс нить порядок следования объектов. Так, для некоторых классов вполне можно сказать, что один экземпляр «меньше» или «больше» другого. Например, поря док следования объектов класса Complex определяется на основе значения, воз вращаемого методом magnitude(). В то же время для объектов класса Circle слож но определить смысл слов «меньше» и «больше» – следует ли сравнивать вели чину радиуса или нужно сравнивать координаты X и Y? А может быть, следует учитывать величины всех трех параметров?

При попытке сравнения JavaScript объектов с помощью операторов отношения, таких как < и = Вот одна из возможных реализаций метода compareTo() для класса Complex из при мера 9.2, в которой сравниваются комплексные числа по их модулям:

Complex.prototype.compareTo = function(that) { // Если аргумент не был передан или он не имеет метода // magnitude(), необходимо сгенерировать исключение.

// Как вариант – можно было бы вернуть значение 1 или 1, // чтобы как то обозначить, что комплексное число всегда меньше // или больше, чем любое другое значение.

if (!that || !that.magnitude || typeof that.magnitude != "function") throw new Error("неверный аргумент в Complex.compareTo()");

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

// Этот прием можно использовать во многих реализациях метода compareTo().

return this.magnitude() that.magnitude();

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

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

complexNumbers.sort(new function(a,b) { return a.compareTo(b); });

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

Complex.compare = function(a,b) { return a.compareTo(b); };

При наличии этого метода сортировка массива может быть реализована еще проще:

complexNumbers.sort(Complex.compare);

Обратите внимание: реализации методов compare() и compareTo() не были включе ны в определение класса Complex из примера 9.2. Дело в том, что они не согласу ются с методом equals(), который был определен в этом примере. Метод equals() утверждает, что два объекта класса Complex эквивалентны, если их веществен ные и мнимые части равны. Однако метод compareTo() возвращает нулевое значе ние для любых двух комплексных чисел, которые имеют равные модули. Числа 1+0i и 0+1i имеют одинаковые модули и эти два числа будут объявлены равны ми при вызове метода compareTo(), но метод equals() утверждает, что они не рав ны. Таким образом, если вы собираетесь реализовать методы equals() и compa reTo() в одном и том же классе, будет совсем нелишним их как то согласовать.

Несогласованность в понимании термина «равенство» может стать источником пагубных ошибок. Рассмотрим реализацию метода compareTo(), который согла суется с существующим методом equals(): // При сравнении комплексных чисел в первую очередь сравниваются // их вещественные части. Если они равны, сравниваются мнимые части Complex.prototype.compareTo = function(that) { var result = this.x that.x; // Сравнить вещественные части result = this.y that.y; // тогда сравнить мнимые части // Теперь результат будет равен нулю только в том случае, // если равны и вещественные, и мнимые части 9.5. Надклассы и подклассы В Java, C++ и других объектно ориентированных языках на базе классов имеет ся явная концепция иерархии классов. Каждый класс может иметь надкласс, от которого он наследует свойства и методы. Любой класс может быть расширен, т. е. иметь подкласс, наследующий его поведение. Как мы видели, JavaScript поддерживает наследование прототипов вместо наследования на базе классов.

Тем не менее в JavaScript могут быть проведены аналогии с иерархией классов.

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

Мы узнали, что объекты наследуют свойства от объекта прототипа их конструк тора. Как они могут наследовать свойства еще и от класса Object? Вспомните, что объект прототип сам представляет собой объект; он создается с помощью конст Но при таком определении достаточно «странную» семантику обретают операто ры отношения < и >. – Примеч. науч. ред.

9.5. Надклассы и подклассы руктора Object(). Это значит, что объект прототип наследует свойства от Ob ject.prototype! Поэтому объект класса Complex наследует свойства от объекта Com plex.prototype, который в свою очередь наследует свойства от Object.prototype.

Когда выполняется поиск некоторого свойства в объекте Complex, сначала выпол няется поиск в самом объекте. Если свойство не найдено, поиск продолжается в объекте Complex.prototype. И наконец, если свойство не найдено и в этом объек те, выполняется поиск в объекте Object.prototype.

Обратите внимание: поскольку в объекте прототипе Complex поиск происходит раньше, чем в объекте прототипе Object, свойства объекта Complex.prototype скрывают любые свойства с тем же именем из Object.prototype. Так, в классе, по казанном в примере 9.2, мы определили в объекте Complex.prototype метод toString(). Object.prototype также определяет метод с этим именем, но объекты Complex никогда не увидят его, поскольку определение toString() в Complex.proto type будет найдено раньше.

Все классы, которые мы показали в этой главе, представляют собой непосредст венные подклассы класса Object. Это типично для программирования на Java Script; обычно в создании более сложной иерархии классов нет никакой необхо димости. Однако когда это требуется, можно создать подкласс любого другого класса. Предположим, что мы хотим создать подкласс класса Rectangle, чтобы до бавить в него свойства и методы, связанные с координатами прямоугольника.

Для этого мы просто должны быть уверены, что объект прототип нового класса сам является экземпляром Rectangle и потому наследует все свойства Rectang le.prototype. Пример 9.3 повторяет определение простого класса Rectanle и затем расширяет это определение за счет создания нового класса PositionedRectangle.

Пример 9.3. Создание подкласса в JavaScript // Определение простого класса прямоугольников.

// Этот класс имеет ширину и высоту и может вычислять свою площадь function Rectangle(w, h) { Rectangle.prototype.area = function( ) { return this.width * this.height; } // Далее идет определение подкласса function PositionedRectangle(x, y, w, h) { // В первую очередь необходимо вызвать конструктор надкласса // для инициализации свойств width и height нового объекта.

// Здесь используется метод call, чтобы конструктор был вызван // как метод инициализируемого объекта.

// Это называется вызов конструктора по цепочке.

Rectangle.call(this, w, h);

// Далее сохраняются координаты верхнего левого угла прямоугольника // Если мы будем использовать объект прототип по умолчанию, // который создается при определении конструктора PositionedRectangle(), // был бы создан подкласс класса Object.

// Чтобы создать подкласс класса Rectangle, необходимо явно создать объект прототип.

PositionedRectangle.prototype = new Rectangle();

// Мы создали объект прототип с целью наследования, но мы не собираемся // наследовать свойства width и height, которыми обладают все объекты // класса Rectangle, поэтому удалим их из прототипа.

delete PositionedRectangle.prototype.width;

delete PositionedRectangle.prototype.height;

// Поскольку объект прототип был создан с помощью конструктора // Rectangle(), свойство constructor в нем ссылается на этот // конструктор. Но нам нужно, чтобы объекты PositionedRectangle // ссылались на другой конструктор, поэтому далее выполняется // присваивание нового значения свойству constructor PositionedRectangle.prototype.constructor = PositionedRectangle;

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

PositionedRectangle.prototype.contains = function(x,y) { return (x > this.x && x < this.x + this.width && Как видно из примера 9.3, создание подклассов в JavaScript выглядит более сложным, чем наследование от класса Object. Первая проблема связана с необхо димостью вызова конструктора надкласса из конструктора подкласса, причем конструктор надкласса приходится вызывать как метод вновь созданного объек та. Затем приходится хитрить и подменять конструктор объекта прототипа под класса. Нам потребовалось явно создать этот объект прототип как экземпляр надкласса, после чего надо было явно изменить свойство constructor объекта прототипа.1 Может также появиться желание удалить любые свойства, которые создаются конструктором надкласса в объекте прототипе, поскольку очень важ но, чтобы свойства объекта прототипа наследовались из его прототипа.

Имея такое определение класса PositionedRectangle, его можно использовать в сво их программах примерно так:

var r = new PositionedRectangle(2,2,2,2);

print(r.contains(3,3)); // Вызывается метод экземпляра print(r.area( )); // Вызывается унаследованный метод экземпляра // Работа с полями экземпляра класса:

print(r.x + ", " + r.y + ", " + r.width + ", " + r.height);

// Наш объект может рассматриваться как экземпляр всех 3 классов В версии Rhino 1.6r1 и более ранних (интерпретатор JavaScript, написанный на языке Java) имеется ошибка, которая делает свойство constructor неудаляемым и доступным только для чтения. В этих версиях Rhino в программном коде, вы полняющем настройку свойства constructor, происходит сбой без вывода сообще ний об ошибке. В результате экземпляры класса PositionedRectangle наследуют значение свойства constructor, которое ссылается на конструктор Rectangle(). На практике эта ошибка почти не проявляется, потому что свойства наследуются правильно и оператор instanceof корректно различает экземпляры классов Positi onedRectangle и Rectangle.

9.5. Надклассы и подклассы print(r instanceof PositionedRectangle && r instanceof Rectangle && r instanceof Object);

9.5.1. Изменение конструктора В только что продемонстрированном примере функция конструктор Positioned Rectangle() должна явно вызывать функцию конструктор надкласса. Это назы вается вызовом конструктора по цепочке и является обычной практикой при создании подклассов. Вы можете упростить синтаксис конструктора, добавив свойство superclass в объект прототип подкласса:

// Сохранить ссылку на конструктор надкласса.

PositionedRectangle.prototype.superclass = Rectangle;

Однако следует заметить, что такой прием можно использовать только при усло вии неглубокой иерархии наследования. Так, если класс В является наследником класса А, а класс С – наследником класса В, и в обоих классах В и С используется прием с обращением к свойству superclass, то при попытке создать экземпляр класса С ссылка this.superclass будет указывать на конструктор В(), что в резуль тате приведет к бесконечному рекурсивному зацикливанию конструктора В().

Поэтому для всего, что не является простым подклассом, используйте методику вызова конструктора по цепочке, которая продемонстрирована в примере 9.3.

После того как свойство определено, синтаксис вызова конструктора по цепочке становится значительно проще:

function PositionedRectangle(x, y, w, h) { this.superclass(w,h);

Обратите внимание: функция конструктор явно вызывается в контексте объек та this. Это означает, что можно отказаться от использования метода call() или apply() для вызова конструктора надкласса как метода данного объекта.

9.5.2. Вызов переопределенных методов Когда в подклассе определяется метод, имеющий то же самое имя, что и метод надкласса, подкласс переопределяет (overrides) этот метод. Ситуация, когда подкласс порождается от существующего класса, встречается достаточно часто.

Например, в любой момент можно определить метод toString() класса и тем са мым переопределить метод toString() класса Object.

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

Рассмотрим следующий пример. Предположим, что класс Rectangle определяет метод toString() (что должно быть сделано чуть ли не в первую очередь) следую щим образом:

Rectangle.prototype.toString = function( ) { return "[" + this.width + "," + this.height + "]";

Если уж вы реализовали метод toString() в классе Rectangle, то тем более его не обходимо переопределить в классе PositionedRectangle, чтобы экземпляры под класса могли иметь строковое представление, отражающее значения не только ширины и высоты, но и всех остальных их свойств. PositionedRectangle – очень простой класс и для него достаточно, чтобы метод toString() просто возвращал значения всех его свойств. Однако ради примера будем обрабатывать значения свойств координат в самом классе, а обработку свойств width и height делегируем надклассу. Сделать это можно примерно следующим образом:

PositionedRectangle.prototype.toString = function() { return "(" + this.x + "," + this.y + ") " + // поля этого класса Rectangle.prototype.toString.apply(this); // вызов надкласса по цепочке Реализация метода toString() надкласса доступна как свойство объекта прото типа надкласса. Обратите внимание: мы не можем вызвать метод напрямую – нам пришлось воспользоваться методом apply(), чтобы указать, для какого объ екта вызывается метод.

Однако если в PositionedRectangle.prototype добавить свойство superclass, можно сделать так, чтобы этот код не зависел от типа надкласса:

PositionedRectangle.prototype.toString = function( ) { return "(" + this.x + "," + this.y + ") " + // поля этого класса this.superclass.prototype.toString.apply(this);

Еще раз обратите внимание, что свойство superclass может использоваться в ие рархии наследования только один раз. Если оно будет задействовано классом и его подклассом, это приведет к бесконечной рекурсии.

9.6. Расширение без наследования Предыдущее обсуждение проблемы создания подклассов описывает порядок создания новых классов, наследующих методы других классов. Язык JavaScript настолько гибкий, что создание подклассов и использование механизма насле дования – это не единственный способ расширения функциональных возможно стей классов. Поскольку функции в JavaScript – это просто значения данных, они могут легко копироваться (или «заимствоваться») из одного класса в дру гой. В примере 9.4 демонстрируется функция, которая заимствует все методы одного класса и создает их копии в объекте прототипе другого класса.

Пример 9.4. Заимствование методов одного класса для использования в другом // Заимствование методов одного класса для использования в другом.

// Аргументы должны быть функциями конструкторами классов.

// Методы встроенных типов, таких как Object, Array, Date и RegExp // не являются перечислимыми и потому не заимствуются этой функцией.

function borrowMethods(borrowFrom, addTo) { var from = borrowFrom.prototype; // прототип источник 9.6. Расширение без наследования var to = addTo.prototype; // прототип приемник for(m in from) { // Цикл по всем свойствам прототипа источника if (typeof from[m] != "function") continue; // Игнорировать все, Многие методы настолько тесно связаны с классом, в котором они определены, что нет смысла пытаться использовать их в другом классе. Однако некоторые методы могут быть достаточно универсальными и пригодяться в любом классе.

В примере 9.5 приводятся определения двух классов, ничего особо полезного не делающих, зато реализующих методы, которые могут быть заимствованы дру гими классами. Подобные классы, разрабатываемые специально с целью заим ствования, называются классами смесями, или просто смесями.

Пример 9.5. Классы смеси с универсальными методами, предназначенными // Сам по себе этот класс не очень хорош. Но он определяет универсальный // метод toString(), который может представлять интерес для других классов.

function GenericToString() {} GenericToString.prototype.toString = function( ) { for(var name in this) { if (!this.hasOwnProperty(name)) continue;

return "{" + props.join(", ") + "}";

// Следующий класс определяет метод equals(), который сравнивает простые объекты.

function GenericEquals() {} GenericEquals.prototype.equals = function(that) { if (this == that) return true;

// объекты равны, только если объект this имеет те же свойства, // что и объект that, и не имеет никаких других свойств // Обратите внимание: нам не требуется глубокое сравнение.

// Значения просто должны быть === друг другу. Из этого следует, // если есть свойства, ссылающиеся на другие объекты, они должны ссылаться // на те же самые объекты, а не на объекты, для которых equals() возвращает true var propsInThat = 0;

for(var name in that) { if (this[name] !== that[name]) return false;

// Теперь необходимо убедиться, что объект this не имеет дополнительных свойств var propsInThis = 0;

for(name in this) propsInThis++;

// Если объект this обладает дополнительными свойствами, // следовательно, объекты не равны if (propsInThis != propsInThat) return false;

// Два объекта выглядят равными.

Вот как выглядит простой класс Rectangle, который заимствует методы toString() и equals(), определенные в классах смесях:

// Простой класс Rectangle function Rectangle(x, y, w, h) { this.height = h;

Rectangle.prototype.area = function( ) { return this.width * this.height; } // Заимствование некоторых методов borrowMethods(GenericEquals, Rectangle);

borrowMethods(GenericToString, Rectangle);

Ни один из представленных здесь классов смесей не имеет собственного конст руктора, однако это не значит, что конструкторы нельзя заимствовать. В сле дующем фрагменте приводится определение нового класса с именем ColoredRec tangle. Он наследует функциональность класса Rectangle и заимствует конструк тор и метод из класса смеси Colored:

// Эта смесь содержит метод, зависящий от конструктора. Оба они, // и конструктор, и метод должны быть заимствованы.

function Colored(c) { this.color = c; } Colored.prototype.getColor = function() { return this.color; } // Определение конструктора нового класса function ColoredRectangle(x, y, w, h, c) { this.superclass(x, y, w, h); // Вызов конструктора надкласса Colored.call(this, c); // и заимствование конструктора Colored // Настройка объекта прототипа на наследование методов от Rectangle ColoredRectangle.prototype = new Rectangle();

ColoredRectangle.prototype.constructor = ColoredRectangle;

ColoredRectangle.prototype.superclass = Rectangle;

9.7. Определение типа объекта // Заимствовать методы класса Colored в новый класс borrowMethods(Colored, ColoredRectangle);

Класс ColoredRectangle расширяет класс Rectangle (и наследует его методы), а так же заимствует методы класса Colored. Сам класс Rectangle наследует класс Object и заимствует методы классов GenericEquals и GenericToString. Хотя подобные ана логии здесь неуместны, можно воспринимать это как своего рода множественное наследование. Так как класс ColoredRectangle заимствует методы класса Colored, экземпляры класса ColoredRectangle можно одновременно рассматривать как эк земпляры класса Colored. Оператор instanceof не сможет сообщить об этом, но в разделе 9.7.3 мы создадим более универсальный метод, который позволит оп ределять, наследует или заимствует некоторый объект методы заданного класса.

9.7. Определение типа объекта Язык JavaScript – это слабо типизированный язык, а JavaScript объекты еще менее типизированы. Тем не менее в JavaScript существует несколько приемов, которые могут служить для определения типа произвольного значения.

Конечно же, самый распространенный прием основан на использовании опера тора typeof (подробности см. в разделе 5.10.2). В первую очередь typeof позволя ет различать объекты и элементарные типы, однако он обладает некоторыми странностями. Во первых, выражение typeof null дает в результате строку "ob ject", тогда как выражение typeof undefined возвращает строку "undefined". По мимо этого в качестве типа любого массива возвращается строка "object", по скольку все массивы – объекты, однако для любой функции возвращается стро ка "function", хотя функции фактически также являются объектами.

9.7.1. Оператор instanceof и конструктор После того как выяснится, что некоторое значение является объектом, а не эле ментарным значением и не функцией, его можно передать оператору instanceof, чтобы подробнее выяснить его природу. Например, если x является массивом, тогда следующее выражение вернет true:

x instanceof Array Слева от оператора instanceof располагается проверяемое значение, справа – имя функции конструктора, определяющей класс объектов. Обратите внимание:

объект расценивается как экземпляр собственного класса и всех его надклассов.

Таким образом, для любого объекта o выражение o instanceof Object всегда вер нет true. Интересно, что оператор instanceof может работать и с функциями, так, все нижеследующие выражения возвращают значение true:

typeof f == "function" f instanceof Function f instanceof Object В случае необходимости можно убедиться, что некоторый объект является эк земпляром определенного класса, а не одного из подклассов – для этого доста точно проверить значение свойства constructor. В следующем фрагменте выпол няется такая проверка:

var d = new Date(); // Объект Date; Date – подкласс Object var isobject = d instanceof Object; // Возвращает true var realobject = d.constructor==Object; // Возвращает false 9.7.2. Определение типа объекта с помощью метода Object.toString() Недостаток оператора instanceof и свойства constructor заключается в том, что они позволяют проверять объекты на принадлежность только известным вам классам, но не дают никакой полезной информации при исследовании неизвест ных объектов, что может потребоваться, например, при отладке. В такой ситуа ции на помощь может прийти метод Object.toString().

Как уже говорилось в главе 7, класс Object содержит определение метода toString() по умолчанию. Любой класс, который не определяет собственный ме тод, наследует реализацию по умолчанию. Интересная особенность метода по умолчанию toString() состоит в том, что он выводит некоторую внутреннюю ин формацию о типе встроенных объектов. Спецификация ECMAScript требует, чтобы метод по умолчанию toString() всегда возвращал строку в формате:

[object class] Здесь class – это внутренний тип объекта, который обычно соответствует имени функции конструктора этого объекта. Например, для массивов class – это "Array", для функций – "Function", и для объектов даты/времени – "Date". Для встроенно го класса Math возвращается "Math", а для всех классов семейства Error – строка "Error". Для объектов клиентского языка JavaScript и любых других объектов, определяемых реализацией JavaScript, в качестве строки class возвращается строка, определяемая реализацией (например, "Window", "Document" или "Form").

Однако для типов объектов, определяемых пользователем, таких как Circle и Complex, описанных ранее в этой главе, в качестве строки class всегда возвра щается строка "Object". То есть метод toString() способен определять только встроенные типы объектов.

Поскольку в большинстве классов метод по умолчанию toString() переопределя ется, не следует ожидать, что вызвав его непосредственно из объекта, вы полу чите имя класса. Поэтому необходимо обращаться к функции по умолчанию Ob ject.prototype явно и использовать для этого метод apply() с указанием объекта, тип которого требуется узнать:

Object.prototype.toString.apply(o); // Всегда вызывается метод по умолчанию toString() Этот прием используется в примере 9.6 в определении функции, реализующей расширенные возможности по выяснению типа. Как уже отмечалось ранее, ме тод toString() не работает с пользовательскими классами, в этом случае показан ная далее функция проверяет строковое значение свойства classname и возвраща ет его значение, если оно определено.

Пример 9.6. Улучшенные возможности определения типа function getType(x) { // Если значение x равно null, возвращается "null" if (x == null) return "null";

// Попробовать определить тип с помощью оператора typeof 9.7. Определение типа объекта // Если получен непонятный результат, вернуть его if (t != "object") return t;

// В противном случае, x – это объект. Вызвать метод toString() // по умолчанию и извлечь подстроку с именем класса.

var c = Object.prototype.toString.apply(x); // В формате "[object class]" // Если имя класса не Object, вернуть его.

if (c != "Object") return c;

// Если получен тип "Object", проверить, может быть x // действительно принадлежит этому классу.

if (x.constructor == Object) return c; // Тип действительно "Object" // Для пользовательских классов извлечь строковое значение свойства // classname, которое наследуется от объекта прототипа if ("classname" in x.constructor.prototype && // наследуемое имя класса typeof x.constructor.prototype.classname == "string") // это строка return x.constructor.prototype.classname;

// Если определить тип так и не удалось, так и скажем об этом.

9.7.3. Грубое определение типа Существует старое высказывание: «Если оно ходит как утка и крякает как утка, значит, это утка!». Перевести этот афоризм на язык JavaScript довольно сложно, однако попробуем: «Если в этом объекте реализованы все методы некоторого класса, значит, это экземпляр данного класса». В гибких языках программиро вания со слабой типизацией, таких как JavaScript, это называется «грубым оп ределением типа»: если объект обладает всеми свойствами класса X, его можно рассматривать как экземпляр класса X, даже если на самом деле этот объект не был создан с помощью функции конструктора X(). Грубое определение типа особенно удобно использовать для классов, «заимст вующих» методы у других классов. Ранее в этой главе демонстрировался класс Rectangle, заимствующий метод equals() у класса с именем GenericEquals. В ре зультате любой экземпляр класса Rectangle можно рассматривать как экземпляр класса GenericEquals. Оператор instanceof не может определить этот факт, но в на ших силах создать для этого собственный метод (пример 9.7).

Пример 9.7. Проверка факта заимствования объектом методов заданного класса // Возвращает true, если каждый из методов c.prototype был // заимствован объектом o. Если o – это функция, а не объект, // вместо самого объекта o производится проверка его прототипа.

// Обратите внимание: для этой функции необходимо, чтобы методы были // скопированы, а не реализованы повторно. Если класс заимствовал метод, // а затем переопределил его, данная функция вернет значение false.

function borrows(o, c) { Термин «грубое определение типа» появился благодаря языку программирова ния Ruby. Точное его название – алломорфизм.

// Если объект o уже является экземпляром класса c, можно вернуть true if (o instanceof c) return true;

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

// В этом случае вместо того, чтобы генерировать, исключение возвращается // значение undefined, как своего рода ответ "Я не знаю".

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

if (c == Array || c == Boolean || c == Date || c == Error || if (typeof o == "function") o = o.prototype;

var proto = c.prototype;

// Игнорировать свойства, не являющиеся функциями if (typeof proto[p] != "function") continue;

if (o[p] != proto[p]) return false;

Метод borrows() из примера 9.7 достаточно ограничен: он возвращает значение true, только если объект o имеет точные копии методов, определяемых классом c.

В действительности грубое определение типа должно работать более гибко: объ ект o должен рассматриваться как экземпляр класса c, если содержит методы, напоминающие методы класса c. В JavaScript «напоминающие» означает «имею щие те же самые имена» и (возможно) «объявленные с тем же количеством аргу ментов». В примере 9.8 демонстрируется метод, реализующий такую проверку.

Пример 9.8. Проверка наличия одноименных методов // Возвращает true, если объект o обладает методами с теми же именами // и количеством аргументов, что и класс c.prototype. В противном случае // возвращается false. Генерирует исключение, если класс с принадлежит // встроенному типу с методами, не поддающимися перечислению.

function provides(o, c) { // Если o уже является экземпляром класса c, он и так будет "напоминать" класс c if (o instanceof c) return true;

// Если вместо объекта был передан конструктор объекта, использовать объект прототип if (typeof o == "function") o = o.prototype;

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

if (c == Array || c == Boolean || c == Date || c == Error || var proto = c.prototype;

for(var p in proto) { // Цикл по всем свойствам в c.prototype // Игнорировать свойства, не являющиеся функциями if (typeof proto[p] != "function") continue;

// Если объект o не имеет одноименного свойства, вернуть false 9.7. Определение типа объекта // Если это свойство, а не функция, вернуть false if (typeof o[p] != "function") return false;

// Если две функции объявлены с разным числом аргументов, вернуть false.

if (o[p].length != proto[p].length) return false;

// Если были проверены все методы, можно смело возвращать true.

В качестве примера грубого определения типа и использования метода provide() рассмотрим метод compareTo(), описанный в разделе 9.4.3. Как правило, метод compareTo() не предназначен для заимствования, но иногда бывает желательно выяснить, обладают ли некоторые объекты возможностью сравнения с помощью метода compareTo(). С этой целью определим класс Comparable:

function Comparable( ) {} Comparable.prototype.compareTo = function(that) { throw "Comparable.compareTo() – абстрактный метод. Не подлежит вызову!";

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

// Проверить, допускается ли сравнение объектов o и p // Они должны принадлежать одному типу и иметь метод compareTo() if (o.constructor == p.constructor && provides(o, Comparable)) { var order = o.compareTo(p);

Обратите внимание: обе функции, представленные в этом разделе, borrows() и provides(), возвращают значение undefined, если им передается объект одного из встроенных типов JavaScript, например Array. Сделано это по той простой причине, что свойства объектов прототипов встроенных типов не поддаются пе речислению в цикле for/in. Если бы функции не могли выполнять проверку на принадлежность встроенным типам и возвращать undefined, тогда обнаружилось бы, что встроенные типы не имеют методов, и для них всегда возвращалось бы значение true.

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

Пример 9.9. Проверка объектов, напоминающих массивы function isArrayLike(x) { if (x instanceof Array) return true; // Настоящий массив if (!("length" in x)) return false; // Массивы имеют свойство length if (typeof x.length != "number") return false; // Свойство length должно быть число, // Если массив непустой, в нем как минимум должно быть свойство с именем length if (!((x.length 1) in x)) return false;

9.8. Пример: вспомогательный метод defineClass() Данная глава заканчивается определением вспомогательного метода define Class(), воплощающего в себе обсуждавшиеся темы о конструкторах, прототи пах, подклассах, заимствовании и предоставлении методов. Реализация метода приводится в примере 9.10.

Пример 9.10. Вспомогательная функция для определения классов * defineClass() – вспомогательная функция для определения JavaScript классов.

* Эта функция ожидает получить объект в виде единственного аргумента.

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

* Объект, передаваемый в качестве аргумента, должен иметь все * или некоторые из следующих свойств:

* name: Имя определяемого класса.

* Если определено, это имя сохранится в свойстве classname объекта прототипа.

* extend: Конструктор наследуемого класса. В случае отсутствия будет * использован конструктор Object(). Это значение сохранится * в свойстве superclass объекта прототипа.

* construct: Функция конструктор класса. В случае отсутствия будет использована новая * пустая функция. Это значение станет возвращаемым значением функции, * а также сохранится в свойстве constructor объекта прототипа.

* methods: Объект, который определяет методы (и другие свойства, * совместно используемые разными экземплярами) экземпляра класса.

* Свойства этого объекта будут скопированы в объект прототип класса.

* В случае отсутствия будет использован пустой объект.

* Свойства с именами "classname", "superclass" и "constructor" * зарезервированы и не должны использоваться в этом объекте.

* statics: Объект, определяющий статические методы (и другие статические * свойства) класса. Свойства этого объекта станут свойствами * функции конструктора. В случае отсутствия будет использован пустой объект.

* borrows: Функция конструктор или массив функций конструкторов.

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

9.8. Пример: вспомогательный метод defineClass() * Конструкторы обрабатываются в порядке их следования, вследствие * этого методы классов, стоящих в конце массива, могут переопределить * Обратите внимание: заимствуемые методы сохраняются * в объекте прототипе до того, как будут скопированы свойства * Поэтому методы, определяемые этими объектами, могут * переопределить заимствуемые. При отсутствии этого свойства * заимствование методов не производится.

* provides: Функция конструктор или массив функций конструкторов.

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

* Ни один из методов не будет скопирован, она просто убедится, * что данный класс "предоставляет" функциональность, обеспечиваемую * указанным классом. Если проверка окажется неудачной, данный метод * сгенерирует исключение. В противном случае любой экземпляр нового класса * может рассматриваться (с использованием методики грубого определения типа) * как экземпляр указанных типов. Если данное свойство не определено, function defineClass(data) { // Извлечь значения полей из объекта аргумента.

// Установить значения по умолчанию.

var classname = data.name;

var superclass = data.extend || Object;

var constructor = data.construct || function( ) {};

var methods = data.methods || {};

var statics = data.statics || {};

// Заимствование может производиться как из единственного конструктора, // так и из массива конструкторов.

if (!data.borrows) borrows = [];

else if (data.borrows instanceof Array) borrows = data.borrows;

else borrows = [ data.borrows ];

// То же для предоставляемых свойств.

if (!data.provides) provides = [];

else if (data.provides instanceof Array) provides = data.provides;

else provides = [ data.provides ];

// Создать объект, который станет прототипом класса.

var proto = new superclass();

// Удалить все неунаследованные свойства из нового объекта прототипа.

if (proto.hasOwnProperty(p)) delete proto[p];

// Заимствовать методы из классов смесей, скопировав их в прототип.

for(var i = 0; i < borrows.length; i++) { // Скопировать методы из прототипа объекта c в наш прототип if (typeof c.prototype[p] != "function") continue;

// Скопировать методы экземпляра в объект прототип // Эта операция может переопределить методы, скопированные из классов смесей for(var p in methods) proto[p] = methods[p];

// Установить значения зарезервированных свойств "constructor", // "superclass" и "classname" в прототипе proto.constructor = constructor;

proto.superclass = superclass;

// Свойство classname установить, только если оно действительно задано.

if (classname) proto.classname = classname;

// Убедиться, что прототип предоставляет все предполагаемые методы.

for(var i = 0; i < provides.length; i++) { // для каждого класса if (typeof c.prototype[p] != "function") continue; // только методы if (p == "constructor" || p == "superclass") continue;

// Проверить наличие метода с тем же именем и тем же количеством // объявленных аргументов. Если метод имеется, продолжить цикл proto[p].length == c.prototype[p].length) continue;

// В противном случае возбудить исключение throw new Error("Класс " + classname + " не предоставляет метод "+ // Связать объект прототип с функцией конструктором constructor.prototype = proto;

// Скопировать статические свойства в конструктор for(var p in statics) constructor[p] = data.statics[p];

// И в заключение вернуть функцию конструктор return constructor;

В примере 9.11 приводится фрагмент, который демонстрирует использование метода defineClass().

Пример 9.11. Использование метода defineClass() // Класс Comparable с абстрактным методом, благодаря которому // можно определить классы, "предоставляющие" интерфейс Comparable.



Pages:     || 2 | 3 | 4 | 5 |   ...   | 7 |


Похожие работы:

«1 ГБОУ СПО РО Таганрогский музыкальный колледж Основная образовательная программа среднего профессионального образования по специальности 073002 Теория музыки Таганрог 2013 2 Утверждаю: Директор ГБОУ СПО РО Таганрогский музыкальный колледж Н.В. Карнаухов Основная образовательная программа среднего профессионального образования по специальности 073002 Теория музыки Форма обучения – очная Нормативный срок обучения – 3 года 10 месяцев Специальность утверждена приказом Минобрнауки России от...»

«Управление образования администрации города Иванова Муниципальное бюджетное общеобразовательное учреждение средняя общеобразовательная школа № 65 УТВЕРЖДЕНО решение Педагогического Совета Протокол от 30 августа 2013 года № 194 Введено в действие приказом от 30 августа 2013 года № 105 - ОД Председатель Педагогического Совета Директор В.А.Степович РАБОЧАЯ ПРОГРАММА факультативного курса Решение олимпиадных задач для 5 класса Ступень обучения (классы): основное общее образование (5 класс)...»

«1 2 Основная профессиональная образовательная программа областного государственного бюджетного образовательного учреждения среднего профессионального образования Димитровградский технический колледж разработана на основе Федерального государственного образовательного стандарта по специальности 190631 Техническое обслуживание и ремонт автомобильного транспорта (утвержден приказом МО и НРФ от 17 марта 2010 г. № 184, зарегистрирован в Минюсте РФ от 28 апреля 2010 г. N 17041), в соответствии с...»

«Программа секционных заседаний Секция 1+2 Секция 1 Достижения и перспективы химической науки Секция 2 Химия материалов, наноструктуры и нанотехнологии (Совместное заседание) 24 сентября Большой зал Президиума РАН Вечернее заседание (14:30-19:15) Председатели: академик Нефедов О.М., академик Третьяков Ю.Д. Ковальчук М.В. (Институт кристаллографии им. А.В. Шубникова РАН, 14:30 РНЦ Курчатовский институт, Москва) НАНОТЕХНОЛОГИИ ОСНОВА НОВОЙ НАУКОЕМКОЙ ЭКОНОМИКИ 21 ВЕКА Алдошин С.М. (Институт...»

«Аннотация Рабочая программа дисциплины Иностранный язык (английский) составлена в соответствии с требованиями ФГОС ВПО с учетом рекомендаций и ООП ВПО по направлению 250100 Лесное дело, профиль подготовки 250100.62 Лесное дело. Общая трудоемкость изучения дисциплины составляет 9 зачетных единиц, 324 часа. Цель и задачи дисциплины Цель изучения дисциплины: содействовать формированию у студентов межкультурной, коммуникативной и языковой компетенций, расширению кругозора, повышению уровня их общей...»

«Приложение 1: Рабочая программа обязательной дисциплины История и философия науки ФЕДЕРАЛЬНОЕ ГОСУДАРСТВЕННОЕ БЮДЖЕТНОЕ ОБРАЗОВАТЕЛЬНОЕ УЧРЕЖДЕНИЕ ВЫСШЕГО ПРОФЕССИОНАЛЬНОГО ОБРАЗОВАНИЯ ПЯТИГОРСКИЙ ГОСУДАРСТВЕННЫЙ ЛИНГВИСТИЧЕСКИЙ УНИВЕРСИТЕТ Утверждаю Проректор по научной работе и развитию интеллектуального потенциала университета профессор З.А. Заврумов _2013 г. Аспирантура по специальности: 09.00.13 Философская антропология, философия культуры отрасль науки: 09.00.00 Философские науки Кафедра...»

«Муниципальное казенное общеобразовательное учреждение Новоусманский лицей Новоусманского муниципального района Воронежской области Рассмотрено на заседании МО Принято на заседании Утверждено _директор лицея Орловцева Г.И. Протокол №_1 от _27__августа_ 2013 г. педсовета руководитель МО Протокол № от 29.08.2013г. Приказ № 2.09. 2013г. Воронова Г. П. РАБОЧАЯ ПРОГРАММА среднего (полного) общего образования по химии Базовый уровень Для 10 Б класса Разработал: учитель химии Воронова Г.П. 2013 – 2014...»

«ПРОЕКТ на 24-12-2012 ред. Приложение к постановлению Правительства Ханты-Мансийского автономного округа - Югры от декабря 2012 г. № ЦЕЛЕВАЯ ПРОГРАММА ХАНТЫ-МАНСИЙСКОГО АВТОНОМНОГО ОКРУГА - ЮГРЫ РАЗВИТИЕ ЛЕСНОГО ХОЗЯЙСТВА ХАНТЫ-МАНСИЙСКОГО АВТОНОМНОМНОГО ОКРУГА – ЮГРЫ НА 2014 - 2020 ГОДЫ (ДАЛЕЕ – ЦЕЛЕВАЯ ПРОГРАММА) Паспорт Целевой программы Наименование Развитие лесного хозяйства Ханты-Мансийского автономного округа – Югры на целевой программы 2014 - 2020 годы Дата принятия Соглашение о...»

«Приложение 3 к БУП на 2013-2014 учебный год Реализуемые образовательные программы 2013 -2014 учебный год. Класс Кол – во Предметы по Учебники Программы обучающ БУП ихся 1. Обучение Букварь Р.Н.Бунеев УМК Школа-2100 1a.б 72 Лилева Л.В. грамоте Москва Баласс, 2012г Москва Баласс 2009г Малышева О.А. авт. Р.Н.Бунеев УМК Школа- 2. Русский язык Бунеев Р.Н. Москва Баласс,2012 г. Москва Баласс 2009г авт. Р.Н.Бунеев Маленькая дверь в большой УМК Школа- 3. Литературное чтение мир Москва Баласс 2009г...»

«МИНИСТЕРСТВО ОБРАЗОВАНИЯ И НАУКИ РОССИЙСКОЙ ФЕДЕРАЦИИ Государственное образовательное учреждение высшего профессионального образования САМАРСКИЙ ГОСУДАРСТВЕННЫЙ УНИВЕРСИТЕТ Факультет механико-математический_ Кафедра математического моделирования в механике_ УТВЕРЖДАЮ Проректор по учебной работе В.П. Гарькин __2011г. РАБОЧАЯ ПРОГРАММА ДИСЦИПЛИНЫ Механика анизотропных жидкостей Профессиональная образовательная программа направления 010800 МЕХАНИКА И МАТЕМАТИЧЕСКОЕ МОДЕЛИРОВАНИЕ цикл Б3...»

«Министерство образования и науки Российской Федерации ПРОГРАММА - МИНИМУМ КАНДИДАТСКОГО ЭКЗАМЕНА по курсу История и философия науки История биологии Программа-минимум содержит 11 стр. 2007 2 Введение. В основу настоящей программы положены следующие дисциплины: история биологии. Программа-минимум разработана Институтом истории естествознания и техники им. С. И. Вавилова РАН и биолого-почвенным факультетом Санкт-Петербургского государственного университета. Проблемы историографии биологии....»

«МИНИСТЕРСТВО СЕЛЬСКОГО ХОЗЯЙСТВА РОССИЙСКОЙ ФЕДЕРАЦИИ Федеральное государственное бюджетное образовательное учреждение высшего профессионального образования Саратовский государственный аграрный университет имени Н.И. Вавилова СОГЛАСОВАНО УТВЕРЖДАЮ Заведующий кафедрой Декан факультета /Глухарев В.А./ _ /Трушкин В.А./ _ _2013 г. _ 2013 г. РАБОЧАЯ ПРОГРАММА ДИСЦИПЛИНЫ (МОДУЛЯ) РЕЖИМЫ РАБОТЫ И ЭКСПЛУАТАЦИЯ Дисциплина КОТЕЛЬНЫХ УСТАНОВОК И ПАРОГЕНЕРАТОРОВ Направление подготовки 140100.62...»

«ФЕДЕРАЛЬНОЕ АГЕНТСТВО СВЯЗИ ФЕДЕРАЛЬНОЕ ГОСУДАРСТВЕННОЕ ОБРАЗОВАТЕЛЬНОЕ БЮДЖЕТНОЕ УЧРЕЖДЕНИЕ ВЫСШЕГО ПРОФЕССИОНАЛЬНОГО ОБРАЗОВАНИЯ САНКТ-ПЕТЕРБУРГСКИЙ ГОСУДАРСТВЕННЫЙ УНИВЕРСИТЕТ ТЕЛЕКОММУНИКАЦИЙ им. проф. М. А. БОНЧ-БРУЕВИЧА Факультет ЭиУ_ Допустить к защите Зав. кафедрой Вольфсон М.Б._ _28 _июня2013 г. Дипломная работа на тему Применение архитектурных методов и средств при разработке ИТ стратегии компании Студент _ Трофимцева А.С. (подпись) (ФИО) Руководитель работы _ Арзуманян М.Ю. (подпись)...»

«Федеральное государственное бюджетное образовательное учреждение высшего профессионального образования Липецкий государственный технический университет УТВЕРЖДАЮ” Декан инженерно-строительного факультета _В.И. Бабкин 2013 г. РАБОЧАЯ ПРОГРАММА ДИСЦИПЛИНЫ Геодезическое обеспечение строительства Направление подготовки: 270800.62 Строительство Профиль подготовки: Городское строительство и хозяйство Квалификация выпускника: Бакалавр Форма обучения: Очная г. Липецк – 2013 г. 1. Цели освоения...»

«Russia Ключевые ориентиры для разработки и реализации образовательных программ в предметной области Образование КЛЮЧЕВЫЕ ОРИЕНТИРЫ ДЛЯ РАЗРАБОТКИ И РЕАЛИЗАЦИИ ОБРАЗОВАТЕЛЬНЫХ ПРОГРАММ В ПРЕДМЕТНОЙ ОБЛАСТИ ОБРАЗОВАНИЕ © University of Deusto - ISBN 978-84-15772-11-8 © University of Deusto - ISBN 978-84-15772-11-8 Тюнинг Россия КЛЮЧЕВЫЕ ОРИЕНТИРЫ ДЛЯ РАЗРАБОТКИ И РЕАЛИЗАЦИИ ОБРАЗОВАТЕЛЬНЫХ ПРОГРАММ В ПРЕДМЕТНОЙ ОБЛАСТИ ОБРАЗОВАНИЕ Университет Деусто...»

«Программа дисциплины Молекулярно-генетические основы биоразноообразия Авторы: н.с. А.И. Константинова Цель освоения дисциплины - получение фундаментальных знаний о принципах наследственности и изменчивости организмов как об основных характеристиках живых систем, лежащих в основе биологической эволюции; свободное владение теоретическими основами общебиологических знаний, возможность понимать современную естественнонаучную литературу различной междисциплинарной тематической направленности,...»

«АНАЛИЗ деятельности Центра дополнительного образования по предметам художественно-эстетической направленности МОУ СОШ № 9, 2010/2011 учебный год Дополнительное образование на базе школы призвано решать задачи гуманистической педагогики, направленные на развитие творческой одарнности, на принятие ребенка как личности и индивидуальности, на защиту его права на саморазвитие и самоопределение. Оно по самой своей сути является личностно ориентированным, в отличие от базового образования,...»

«Профориентационная работа ЦСТАиПО Ясенево: интеграция различных подходов ГОУ Центр социально-трудовой адаптации и профессиональной ориентации Ясенево создан на базе Межшкольного Учебного комбината N 22 в 2008 году. Центр ведет лицензированную деятельность в сфере образования (возраст от 10 до 18 лет) по программам основного общего и среднего (полного) общего образования (Технология, Экономика, Информатика и ИКТ, а также по программам дополнительного образования). ЦСТАиПО Ясенево проводит...»

«МИНИСТЕРСТВО СЕЛЬСКОГО ХОЗЯЙСТВА РОССИЙСКОЙ ФЕДЕРАЦИИ Федеральное государственное образовательное учреждение высшего профессионального образования КУБАНСКИЙ ГОСУДАРСТВЕННЫЙ АГРАРНЫЙ УНИВЕРСИТЕТ УТВЕРЖДАЮ Декан факультета защиты растений, профессор _ Л.М. Онищенко _ _ 2011 г. РАБОЧАЯ ПРОГРАММА дисциплина ОСНОВЫ КАРАНТИНА для специальности 110203 Защита растений факультет Защиты растений Ведущая кафедра фитопатологии, энтомологии и защиты растений Дневная форма Заочная форма обучения обучения Вид...»

«ПРОГРАММА вступительных испытаний в магистратуру по направлению 08.04.01 – Строительство Магистерская программа – Теория и проектирование зданий и сооружений Общие положения, регламентирующие порядок проведения вступительных I. испытаний в магистратуру по направлению 08.04.01 Строительство Лица, желающие освоить магистерскую программу 08.04.01 Теория и проектирование зданий и сооружений, должны иметь высшее профессиональное образование определенной ступени, подтвержденное документом...»






 
2014 www.av.disus.ru - «Бесплатная электронная библиотека - Авторефераты, Диссертации, Монографии, Программы»

Материалы этого сайта размещены для ознакомления, все права принадлежат их авторам.
Если Вы не согласны с тем, что Ваш материал размещён на этом сайте, пожалуйста, напишите нам, мы в течении 1-2 рабочих дней удалим его.