HTML 5, CSS 3 и Web 2.0. Разработка современных Web-сайтов.

Дронов Владимир

ЧАСТЬ 3. Поведение Web-страниц. Web-сценарии

 

 

ГЛАВА 14. Введение в Web-программирование. Язык JavaScript

Web-дизайн состоит из трех частей: содержимого, представления и поведения. Это мы узнали еще в главе 1. Содержимому была посвящена часть I, представлению — часть II. Теперь настала очередь обсудить поведение.

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

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

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

 

Примеры Web-сценариев

Язык JavaScript лучше всего изучать, имея перед глазами пару хороших примеров. Поэтому давайте сразу создадим их.

 

Простейший Web-сценарий

Первый Web-сценарий, который мы напишем, будет совсем простым. Он выведет на Web-страницу текущую дату.

В самом начале этой книги, приступив к изучению HTML, мы создали небольшую Web-страничку 1.1.htm. Найдем ее и откроем в Блокноте. В самом конце ее HTML- кода, перед закрывающим тегом , вставим код листинга 14.1.

Листинг 14.1

<SCRIPT>

var dNow = new Date();

var sNow = dNow.toString();

document.write(sNow);

</SCRIPT>

Это Web-сценарий. Мы поместили его прямо в HTML-код Web-страницы.

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

Наш первый Web-сценарий — поведение Web-страницы — работает!

Теперь немного исправим его так, чтобы он выводил дату в привычном для нас формате <число>.<месяц>.<год> (листинг 14.2).

Листинг 14.2

<SCRIPT>

var dNow = new Date();

var sNow = dNow.getDate() + "." + dNow.getMonth() + "." + dNow.getFullYear();

document.write(sNow);

</SCRIPT>

Обновим открытую в Web-обозревателе Web-страницу, нажав клавишу . Вот теперь дата отображается в привычном формате.

 

Более сложный Web-сценарий

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

Сначала откроем таблицу стилей и внесем в ее CSS-код некоторые изменения. Прежде всего, исправим комбинированные стили #navbar LI и #navbar LI UL LI так, как показано в листинге 14.3.

Листинг 14.3

#navbar LI { padding: 5px 10px; margin: 10px 0px; border: thin solid #B1BEC6; cursor: pointer }

. . .

#navbar LI UL LI { font-size: 12pt; padding: 2px; margin: 0px 0px; border: thin solid #F8F8F8; cursor: pointer }

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

Во-вторых, мы создали у пунктов вложенного списка тонкую сплошную рамку того же цвета, что и фон Web-страницы. Такая рамка будет невидима.

Далее добавим в таблицу стилей вот такой стиль:

.hovered { border-color: #3B4043 !important }

Мы создали стилевой класс, задающий цвет рамки. Поскольку стилевой класс является менее конкретным, чем комбинированный стиль, мы сделали атрибут стиля, задающий цвет рамки, важным. (О важных атрибутах стиля см. главу 7.)

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

После этого отправимся по интернет-адресу  и загрузим оттуда архивный файл с именем вида ext-core-<номер версии>.zip — библиотеку Ext Core, упрощающую написание даже очень сложных Web-сценариев. (Подробнее речь о ней пойдет в главе 15.) Распакуем этот файл, найдем в распакованном содержимом файл ext-core.js и скопируем в корневую папку нашего Web-сайта, где хранится Web-страница index.htm и таблица стилей main.css. Теперь библиотека Ext Core готова к использованию.

ВНИМАНИЕ !

Если по указанному интернет-адресу архивный файл библиотеки Ext Core загрузить не удается, следует открыть Web-страницу http://www.extjs.com/products/core/download.php , найти на ней гиперссылку загрузки этого файла (на данный момент она имеет текст "Download") и щелкнуть на ней.

Теперь откроем в Блокноте Web-страницу index.htm и поместим в ее секцию заголовка (тег ) такой тег:

А  в  самом  конце  HTML-кода  этой  Web-страницы,  перед  закрывающим  тегом

, вставим такой тег:

Напоследок в корневой папке нашего Web-сайта создадим текстовый файл main.js и запишем в него содержимое листинга 14.4.

Листинг 14.4

Ext.onReady(function()

{var ceLinks = Ext.select("UL[id=navbar] LI");

ceLinks.on("mouseover", function(e, t) { Ext.get(this).addClass("hovered");});

ceLinks.on("mouseout", function(e, t) { Ext.get(this).removeClass("hovered");});

});

Это  тоже  Web-сценарий. Но  поместили  его  мы уже  в отдельный файл main.js.

Сохраним его, выбрав кодировку UTF-8 (как это сделать, было описано в главе 1).

Все, поведение создано. Откроем Web-страницу index.htm в Web-обозревателе. Наведем курсор мыши на любой пункт полосы навигации и увидим, как вокруг него появится темная рамка.

 

Как Web-сценарии помещаются в Web-страницу

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

Для вставки Web-сценария в HTML-код в любом случае применяется парный тег

Тег

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

В нашем втором Web-сценарии мы использовали библиотеку Ext Core, значительно облегчающую труд Web-программиста. Во всех языках программирования библиотекой называется набор готовых языковых конструкций (функций и объектов, о которых речь пойдет потом), написанных сторонними программистами, чтобы облегчить труд их коллег. Так вот, все библиотеки для языка JavaScript, в том числе и Ext Core, реализованы в виде внешних Web-сценариев.

И еще. Web-сценарий всегда выполняется в том месте HTML-кода Web-страницы, где присутствует. При этом не имеет значения, помещен он прямо в HTML-код или находится в отдельном файле.

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

 

Язык программирования JavaScript

Настала  пора  рассмотреть  язык  программирования  JavaScript.  Ведь  в  Web-программировании без него никуда.

 

Основные понятия JavaScript

Давайте рассмотрим пример еще одного Web-сценария, совсем небольшого:

x = 4;

y = 5;

z = x * y;

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

Разберем приведенный Web-сценарий по выражениям. Вот первое из них:

x = 4;

Здесь мы видим число 4. В JavaScript такие числа, а также строки и прочие величины, значения которых никогда не изменяются, называются константами. В самом деле, значение числа 4 всегда равно четырем!

Еще мы видим здесь латинскую букву x. А она что означает?

О, это весьма примечательная вещь! Это переменная, которую можно описать как участок памяти компьютера, имеющий уникальное имя и предназначенный для хранения  какой-либо  величины —  константы  или  результата  вычисления.  Наша переменная имеет имя x.

Осталось выяснить, что делает символ равенства (=), поставленный между переменной и константой. А он здесь стоит не просто так! (Вообще, в коде любой про- граммы, в том числе и Web-сценария, каждый символ что-то да значит.) Это оператор —  команда,  выполняющая  определенные  действия  над  данными  Web- сценария. А если точнее, то символом = обозначается оператор присваивания. Он помещает значение, расположенное справа (операнд), в переменную, расположенную слева, в нашем случае — значение 4 в переменную x. Если же такой переменной еще нет, она будет создана.

Каждое выражение JavaScript должно оканчиваться символом точки с запятой (;), обозначающим конец выражения; его отсутствие вызывает ошибку обработки Web- сценария.

Рассмотрим следующее выражение:

y = 5;

Оно аналогично первому и присваивает переменной y константу 5. Подобные выражения часто называют математическими.

Третье выражение стоит несколько особняком:

z = x * y;

Здесь мы видим все тот же оператор присваивания, присваивающий что-то переменной z. Но что? Результат вычисления произведения значений, хранящихся в пе- ременных x и y. Вычисление произведения выполняет оператор умножения, который в JavaScript (и во многих других языках программирования) обозначается символом звездочки (*). Это арифметический оператор.

В результате выполнения приведенного ранее Web-сценария в переменной z окажется произведение значений 4 и 5 — 20.

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

y = y1 * y2 + x1 * x2;

Оно вычисляется в следующем порядке:

1. Значение переменной y1 умножается на значение переменной y2.

2. Перемножаются значения переменных x1 и x2.

3. Полученные на шагах 1 и 2 произведения складываются (оператор сложения обозначается привычным нам знаком +).

4. Полученная сумма присваивается переменной y.

Но почему на шаге 2 выполняется умножение x1 на x2, а не сложение произведения

y1 и y2 с x1. Дело в том, что каждый оператор имеет приоритет — своего рода номер в очереди их выполнения. Так вот, оператор умножения имеет более высокий приоритет, чем оператор сложения, поэтому умножение всегда выполняется перед сложением.

А вот еще одно выражение:

x = x + 3;

Оно абсолютно правильно с точки зрения JavaScript, хоть и выглядит нелепым. В нем сначала выполняется сложение значения переменной x и числа 3, после чего результат сложения снова присваивается переменной x. Такие выражения встречаются в Web-сценариях довольно часто.

 

Типы данных JavaScript

Любая программа при своей работе оперирует некими данными: именем стилевого класса, размерами элемента Web-страницы, цветом шрифта, величиной атмосферного давления и пр. Конечно, не составляют исключения и Web-сценарии — уже первый созданный нами Web-сценарий обрабатывал какие-то данные.

JavaScript может манипулировать данными, относящимися к разным типам. Тип данных описывает их возможные значения и набор применимых к ним операций. Давайте перечислим все типы данных, с которыми мы можем столкнуться.

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

Примеры строк:

"JavaScript" "1234567"

'Строковые данные — это последовательности символов.'

Строки могут иметь любую длину (определяемую количеством составляющих их символов), ограниченную лишь объемом свободной памяти компьютера. Теоретически существует предел в 2 Гбайт, но вряд ли в нашей практике встретятся столь длинные строки.

Кроме букв, цифр и знаков препинания, строки могут содержать специальные символы, служащие для особых целей (табл. 14.1).

Таблица 14.1. Специальные символы, поддерживаемые JavaScript, и их коды

Символ Описание
\f Прогон листа
\n Перевод строки
\r Возврат каретки
\t Табуляция
\" Двойная кавычка
\’ Одинарная кавычка
\\ Обратный слэш
\x <код> Любой символ по его коду в кодировке Unicode

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

"\"Этот фрагмент текста\" помещен в кавычки"

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

Примеры чисел:

13756

454.7873

0.5635

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

<мантисса>E<порядок>.

Вот примеры чисел, заданных таким образом (в скобках дано традиционное математическое представление):

1E-5 (10–5)

8.546E23 (8,546 1023)

Также имеется возможность записи целых чисел в восьмеричном и шестнадцатеричном виде. Восьмеричные числа записываются с нулем в начале (например, 047 или -012543624), а шестнадцатеричные — с символами 0x, также помещенными в начало (например, 0x35F). Отметим, что в JavaScript так можно записывать только целые числа.

Логическая величина может принимать только два значения: true и false — "истина" и "ложь", — обозначаемые соответственно ключевыми словами true и false. (Ключевое слово — это слово, имеющее в языке программирования особое значение.) Логические величины используются, как правило, в условных выражениях (о них речь пойдет позже).

JavaScript также поддерживает три специальных типа. Тип null может принимать единственное значение null и применяется в особых случаях. Тип NaN также может принимать единственное значение NaN и обозначает числовое значение, не являющееся числом (например, математическую бесконечность). Тип undefined указывает на то, что переменной не было присвоено никакое значение, и, опять же, принимает единственное значение undefined.

ВНИМАНИЕ!

undefined — это не то же самое, что null!

Еще два типа данных, поддерживаемые JavaScript и не описанные здесь, мы рассмотрим позже.

 

Переменные

В начале этой главы мы кое-что узнали о переменных. Сейчас настало время обсудить их детальнее.

 

Именование переменных

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

Прежде всего, в имени переменной могут присутствовать только латинские буквы, цифры и символы подчеркивания (_), причем первый символ имени должен быть либо   буквой,   либо   символом   подчеркивания.   Например,   pageAddress,   _link, userName — правильные имена переменных, а 678vasya и Имя пользователя — неправильные.

Язык JavaScript чувствителен к регистру символов, которыми набраны имена переменных. Это значит, что pageaddress и pageAddress — разные переменные.

Совпадение имени переменной с ключевым словом языка JavaScript не допускается.

 

Объявление переменных

Перед использованием переменной в коде Web-сценария рекомендуется выполнить ее объявление. Для этого служит оператор объявления переменной var, после которого указывают имя переменной:

var x;

Теперь объявленной переменной можно присвоить какое-либо значение:

x = 1234;

и использовать в Web-сценарии:

y = x * 2 + 10;

Значение переменной также можно присвоить прямо при ее объявлении:

var x = 1234; 

Также можно объявить сразу несколько переменных:

var x, y, textColor = "black";

Вообще, объявлять переменные с помощью оператора var не обязательно. Мы можем просто присвоить переменной какое-либо значение, и JavaScript сам ее создаст. Просто явное объявление переменных оператором var считается хорошим стилем программирования.

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

ВНИМАНИЕ !

Если обратиться к еще не созданной переменной, она вернет значение undefined.

Пока закончим с переменными. (Впоследствии, при рассмотрении функций, мы к ним еще вернемся.) И займемся операторами JavaScript.

 

Операторы

Операторов язык JavaScript поддерживает очень много — на все случаи жизни. Их можно разделить на несколько групп.

 

Арифметические операторы

Арифметические операторы служат для выполнения арифметических действий над числами. Все арифметические операторы, поддерживаемые JavaScript, перечислены в табл. 14.2.

Таблица 14.2. Арифметические операторы

Оператор Описание
- Смена знака числа
+ Сложение
- Вычитание
* Умножение
/ Деление
% Взятие остатка от деления
++ Инкремент (увеличение на единицу)
-- Декремент (уменьшение на единицу)

Арифметические операторы делятся на две группы: унарные и бинарные. Унарные операторы выполняются над одним операндом; к ним относятся операторы смены знака, инкремента и декремента. Унарный оператор извлекает из переменной значение, изменяет его и снова помещает в ту же переменную. Приведем пример выражения с унарным оператором:

++r;

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

s = ++r;

то значение r, увеличенное на единицу, будет помещено еще и в переменную s.

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

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

l = r * 3.14;

f = e / 2;

x = x + t / 3;

 

Оператор объединения строк

Оператор объединения строк + позволяет соединить две строки в одну. Например, сценарий:

s1 = "Java";

s2 = "Script";

s = s1 + s2;

поместит в переменную s строку JavaScript.

 

Операторы присваивания

Оператор присваивания = нам уже знаком. Его еще называют оператором простого присваивания, поскольку он просто присваивает переменной новое значение:

a = 2;

b = c = 3;

Второе выражение в приведенном примере выполняет присвоение значения 3 сразу двум переменным — b и c.

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

a = a + b;

a += b;

Два этих выражения эквивалентны по результату. Просто во втором указан оператор сложного присваивания +=.

Все операторы сложного присваивания, поддерживаемые JavaScript, и их эквиваленты приведены в табл. 14.3.

Таблица 14.3. Операторы сложного присваивания

 

Операторы сравнения

Операторы сравнения сравнивают два операнда согласно определенному условию и выдают (или, как говорят программисты, возвращают) логическое значение. Если условие сравнения выполняется, возвращается значение true, если не выполняется — false.

Все поддерживаемые JavaScript операторы сравнения приведены в табл. 14.4.

Пример:

a1 = 2 < 3;

a2 = -4 > 0;

a3 = r < t;

Переменная a1 получит значение true (2 меньше 3), переменная a2 — значение false (число –4 по определению не может быть больше нуля), а значение переменной a3 будет зависеть от значений переменных r и t.

Оператор Описание
Меньше
Больше
== Равно
<= Меньше или равно
>= Больше или равно
!= Не равно
=== Строго равно
!== Строго не равно

Можно сравнивать не только числа, но и строки:

a = "JavaScript" != "Java";

Переменная a получит значение true, т. к. строки "JavaScript" и "Java" не равны.

На двух последних операторах из табл. 14.4 — "строго равно" и "строго не равно" — нужно остановиться подробнее. Это операторы так называемого строгого сравнения. Обычные операторы "равно" и "не равно", если встречают операнды разных типов, пытаются преобразовать их к одному типу (о преобразованиях типов см. далее в этой главе). Операторы строгого равенства и строгого неравенства такого преобразования не делают, а в случае несовпадения типов операндов всегда воз- вращают false.

Пример:

a1 = 2 == "2";

a2 = 2 === "2";

Переменная a1 получит значение true, т. к. в процессе вычисления строка "2" будет преобразована в число 2, и условие сравнение выполнится. Но переменная a2 получит значение false, т. к. сравниваемые операнды принадлежат разным типам.

 

Логические операторы

Логические операторы выполняют действия над логическими значениями. Все они приведены в табл. 14.5. А в табл. 14.6 и 14.7 показаны результаты выполнения этих операторов.

Основная  область  применения  логических  операторов —  выражения  сравнения (о них см. далее в этой главе). Приведем примеры таких выражений:

a = (b > 0) && (c + 1 != d);

flag = !(status = 0);

Таблица 14.5. Логические операторы

Оператор Описание
! НЕ (логическая инверсия)
&& И (логическое умножение)
|| ИЛИ (логическое сложение)

Таблица 14.6. Результаты выполнения операторов И и ИЛИ

Операнд 1 Операнд 2 && (И) || (ИЛИ)
true true true true
true false false true
false true false true
false false false false

Таблица 14.7. Результаты выполнения оператора НЕ

Операнд ! (НЕ)
true false
false true

 

Оператор получения типа

typeof

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

s = typeof("str");

В  результате  выполнения  этого  выражения  в  переменной  s окажется  строка "string", обозначающая строковый тип.

Все значения, которые может вернуть оператор typeof, перечислены в табл. 14.8.

Таблица 14.8. Значения, возвращаемые оператором typeof

Тип данных Возвращаемая строка
Строковый "string"
Числовой "number"
Логический "boolean"
Объектный (см. далее) "object"
Функциональный (см. далее) "function"
null "null"
undefined "undefined"

 

Совместимость и преобразование типов данных

Настала пора рассмотреть еще два важных вопроса: совместимость типов данных и преобразование одного типа к другому.

Что получится, если сложить два числовых значения? Правильно — еще одно числовое значение. А если сложить число и строку? Трудно сказать... Тут JavaScript сталкивается с проблемой несовместимости типов данных и пытается сделать эти типы совместимыми, преобразуя один из них к другому. Сначала он пытается преобразовать строку в число и, если это удается, выполняет сложение. В случае неудачи число будет преобразовано в строку, и две полученные строки будут объединены. Например, в результате выполнения Web-сценария из листинга 14.6 значение переменной b при сложении с переменной a будет преобразовано в числовой тип; таким образом, переменная c будет содержать значение 23.

Листинг 14.6

var a, b, c, d, e, f;

a = 11;

b = "12";

c = a + b;

d = "JavaScript";

e = 2;

f = d + e;

Но поскольку значение переменной d нельзя преобразовать в число, значение e будет   преобразовано   в   строку,   и   результат —   значение   f —   станет   равным "JavaScript2".

Логические величины преобразуются либо в числовые, либо в строковые, в зависимости от конкретного случая. Значение true будет преобразовано в число 1 или строку "1", а значение false — в 0 или "0". И наоборот, число 1 будет преобразовано в значение true, а число 0 — в значение false. Также в false будут преобразованы значения null и undefined.

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

 

Приоритет операторов

Последний вопрос, который мы здесь разберем, — приоритет операторов. Как мы помним, приоритет влияет на порядок, в котором выполняются операторы в выражении.

Пусть имеется следующее выражение:

a = b + c — 10;

В этом случае сначала к значению переменной b будет прибавлено значение c, а потом из суммы будет вычтено 10. Операторы этого выражения имеют одинаковый приоритет и поэтому выполняются строго слева направо.

Теперь рассмотрим такое выражение:

a = b + c * 10;

Здесь сначала будет выполнено умножение значения c на 10, а уже потом к полученному произведению будет прибавлено значение b. Оператор умножения имеет больший приоритет, чем оператор сложения, поэтому порядок "строго слева направо" будет нарушен.

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

В общем, основной принцип выполнения всех операторов таков: сначала выполняются операторы с более высоким приоритетом, а уже потом — операторы с более низким. Операторы с одинаковым приоритетом выполняются в порядке их следования (слева направо).

В табл. 14.9 перечислены все изученные нами операторы в порядке убывания их приоритетов.

Таблица 14.9. Приоритет операторов (в порядке убывания)

Операторы Описание
++ -- — ~ ! typeof Инкремент, декремент, смена знака, логическое НЕ, определение типа
* / % Умножение, деление, взятие остатка
+ - Сложение и объединение строк, вычитание
< > <= >= Операторы сравнения
== != === !== Операторы сравнения
&& Логическое И
|| Логическое ИЛИ
? Условный оператор (см. ниже)
= <оператор> = Присваивание, простое и сложное

ВНИМАНИЕ !

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

Но что делать, если нам нужно нарушить обычный порядок выполнения операторов? Воспользуемся скобками. При такой записи заключенные в скобки операторы выполняются первыми:

a = (b + c) * 10;

Здесь сначала будет выполнено сложение значений переменных b и c, а потом по- лучившаяся сумма будет умножена на 10.

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

a = ((b + c) * 10 — d) / 2 + 9;

Здесь операторы будут выполнены в такой последовательности:

1. Сложение b и c.

2. Умножение полученной суммы на 10.

3. Вычитание d из произведения.

4. Деление разности на 2.

5. Прибавление 9 к частному.

Если удалить скобки:

a = b + c * 10 — d / 2 + 9;

то порядок выполнения операторов будет таким:

1. Умножение c и 10.

2. Деление d на 2.

3. Сложение b и произведения c и 10.

4. Вычитание из полученной суммы частного от деления d на 2.

5. Прибавление 9 к полученной разности.

Получается совсем другой результат, не так ли?

 

Сложные выражения JavaScript

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

 

Блоки

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

{

b = "12";

c = a — b;

}

Как правило, блоки не существуют сами по себе. Чаще всего они входят в состав других сложных выражений.

 

Условные выражения

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

Листинг 14.7 иллюстрирует формат условного выражения.

Листинг 14.7

if ( <условие> )

<блок "то">

else

<блок "иначе">

Существует  также  другая,  "вырожденная"  разновидность  условного  выражения,

содержащая только одно выражение, которое выполняется при выполнении условия и пропускается, если условие не выполнено:

if (<условие>)

<блок "то">

Для написания условных выражений предусмотрены особые ключевые слова if и

else. Отметим, что условие всегда записывают в круглых скобках.

Если условие имеет значение true, то выполняется блок "то". Если же условие имеет значение false, то выполняется блок "иначе" (если он присутствует в условном выражении). А если блок "иначе" отсутствует, выполняется следующее выражение Web-сценария.

ВНИМАНИЕ !

Значения null или undefined преобразуются в false. Не забываем об этом.

Рассмотрим несколько примеров.

В листинге 14.8 мы сравниваем значение переменной x с единицей и в зависимости от результатов сравнения присваиваем переменным f и h разные значения.

Листинг 14.8

if (x == 1) {

a = "Единица";

b = 1;

}

else {

a = "Не единица";

b = 22222;

}

Условие может быть довольно сложным (листинг 14.9).

Листинг 14.9

if ((x == 1) && (y > 10))

f = 3;

else

f = 33;

Здесь мы использовали сложное условие, возвращающее значение true в случае,

если значение переменной x равно 1 и значение переменной y больше 10. Заметим также, что мы подставили одиночные выражения, т. к. фрагменты кода слишком просты, чтобы оформлять их в виде блоков.

 

Условный оператор ?

Если условное выражение совсем простое, мы можем записать его немного по-другому. А именно, воспользоваться условным оператором ?:

<условие> ? <выражение "то"> : <выражение "иначе">;

Достоинство этого оператора в том, что он может быть частью выражения. Например:

f = (x == 1 && y > 10) ? 3 : 33;

Фактически мы записали условное выражение из предыдущего примера, но в виде простого выражения. Компактность кода налицо. Недостаток же оператора ? в том, что с его помощью можно записывать только самые простые условные выражения.

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

 

Выражения выбора

Выражение выбора — это фактически несколько условных выражений, объединенных в одном. Его формат иллюстрирует листинг 14.10.

Листинг 14.10

switch ( <исходное выражение> ) {

case <значение 1> :

<блок 1>

[break;]

[case <значение 2> :

<блок 2>

[break;]]

<... другие секции case>

[default :

<блок, исполняемый для остальных значений> ]

}

В выражениях выбора присутствуют ключевые слова switch, case и default.

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

Листинг 14.11 иллюстрирует пример выражения выбора.

Листинг 14.11

switch (a) {

case 1 :

out = "Единица";

break;

case 2 :

out = "Двойка";

break;

case 3 :

out = "Тройка";

break;

default :

out = "Другое число";

}

Здесь, если переменная a содержит значение 1, переменная out получит значение "Единица", если 2 — значение "Двойка", а если 3 — значение "Тройка". Если же переменная a содержит какое-то другое значение, переменная out получит значение "Другое число".

 

Циклы

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

 

Цикл со счетчиком

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

Цикл со счетчиком записывается так:

for (<выражение инициализации>; <условие>; <приращение>)

<тело цикла>

Здесь  используется  ключевое  слово  for.  Поэтому  такие  циклы  часто  называют "циклами for".

Выражение инициализации выполняется самым первым и всего один раз. Оно присваивает особой переменной, называемой счетчиком цикла, некое начальное значение (обычно 1). Счетчик цикла подсчитывает, сколько раз было выполнено тело цикла — собственно код, который нужно выполнить определенное количество раз.

Следующий шаг — проверка условия. Оно определяет момент, когда выполнение цикла прервется и начнет выполняться следующий за ним код. Как правило, условие сравнивает значение счетчика цикла с его граничным значением. Если условие возвращает true, выполняется тело цикла, в противном случае цикл завершается и начинается выполнение кода, следующего за циклом.

После прохода тела цикла выполняется выражение приращения, изменяющее значение счетчика. Это выражение обычно инкрементирует счетчик (увеличивает его значение на единицу). Далее снова проверяется условие, выполняется тело цикла, приращение и т. д., пока условие не станет равно false.

Пример цикла со счетчиком:

for (i = 1; i < 11; i++) {

a += 3;

b = i * 2 + 1;

}

Этот цикл будет выполнен 10 раз. Мы присваиваем счетчику i начальное значение 1 и после каждого выполнения тела цикла увеличиваем его на единицу. Цикл перестанет выполняться, когда значение счетчика увеличится до 11, и условие цикла станет ложным.

Счетчик цикла можно записать в одном из выражений тела цикла, как это сделали

мы. В нашем случае счетчик i будет содержать последовательно возрастающие значения от 1 до 10, которые используются в вычислениях.

Приведем еще два примера цикла со счетчиком:

for (i = 10; i > 0; i--) {

a += 3;

b = i * 2 + 1;

}

Здесь значение счетчика декрементируется. Начальное его значение равно 10. Цикл выполнится 10 раз и завершится, когда счетчик i будет содержать 0; при этом значения последнего будут последовательно уменьшаться от 10 до 1.

for (i = 2; i < 21; i += 2) b = i * 2 + 1;

А в этом примере начальное значение счетчика равно 2, а конечное — 21, но цикл выполнится, опять же, 10 раз. А все потому, что значение счетчика увеличивается на 2 и последовательно принимает значения 2, 4, 6... 20.

 

Цикл с постусловием

Цикл с постусловием во многом похож на цикл со счетчиком: он выполняется до тех пор, пока остается истинным условие цикла. Причем условие проверяется не до, а после выполнения тела цикла, отчего цикл с постусловием и получил свое название. Такой цикл выполнится хотя бы один раз, даже если его условие с самого начала ложно.

Формат цикла с постусловием:

do

 <тело цикла>

while (<условие>);

Для задания цикла с постусловием предусмотрены ключевые слова do и while, поэтому такие циклы часто называют "циклами do-while".

Вот пример цикла с постусловием:

do {

a = a * i + 2;

++i;

} while (a < 100);

А вот еще один пример:

var a = 0, i = 1;

do {

   a = a * i + 2;

++i;

} while (i < 20);

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

 

Цикл с предусловием

Цикл с предусловием отличается от цикла с постусловием тем, что условие проверяется перед выполнением тела цикла. Так что, если оно (условие) изначально ложно, цикл не выполнится ни разу:

while (<условие>)

<тело цикла>

Для создания цикла с постусловием предусмотрено ключевое слово while. Поэтому такие циклы называют еще "циклами while" (не путать с "циклами do-while"!).

Пример цикла с предусловием:

while (a < 100) {

a = a * i + 2;

++i;

}

 

Прерывание и перезапуск цикла

Иногда бывает нужно прервать выполнение цикла. Для этого JavaScript предоставляет Web-программистам операторы break и continue.

Оператор  прерывания  break позволяет  прервать  выполнение  цикла  и  перейти к следующему за ним выражению:

while (a < 100) {

a = a * i + 2;

if (a > 50) break;

++i;

}

В этом примере мы прерываем выполнение цикла, если значение переменной a превысит 50.

Оператор перезапуска continue позволяет перезапустить цикл, т. е. оставить не- выполненными все последующие выражения, входящие в тело цикла, и запустить выполнение цикла с самого его начала: проверка условия, выполнение приращения и тела и т. д.

Пример:

while (a < 100) {

i = ++i;

if (i > 9 && i < 11) continue;

a = a * i + 2;

}

Здесь мы пропускаем выражение, вычисляющее a, для всех значений i от 10 до 20.

 

Функции

Функция —  это  особым  образом  написанный  и  оформленный  фрагмент  кода JavaScript, который можно вызвать из любого Web-сценария на данной Web- странице (повторно используемый код, как его часто называют). Так что, если ка- кой-то фрагмент кода встречается в нескольких местах нашего Web-сценария, лучше всего оформить его в виде функции.

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

 

Объявление функций

Прежде чем функция будет использована где-то в Web-сценарии, ее нужно объявить. Функцию объявляют с помощью ключевого слова function:

function <имя функции>([<список параметров, разделенных запятыми>])

<тело функции>

Имя функции, как уже говорилось, должно быть уникально в пределах Web- страницы. Для имен функций действуют те же правила, что и для имен переменных.

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

Список параметров функции помещают в круглые скобки и ставят после ее имени; сами параметры отделяют друг от друга запятыми. Если функция не требует пара- метров, следует указать пустые скобки — это обязательно.

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

return <переменная или выражение>;

Здесь переменная должна содержать возвращаемое значение, а выражение должно его вычислять.

Пример объявления функции:

function divide(a, b) {

var c;

c = a / b;

return c;

}

Данная функция принимает два параметра — a и b, — после чего делит a на b и возвращает частное от этого деления.

Эту функцию можно записать компактнее:

function divide(a, b) {

return a / b;

}

Или даже так, в одну строку:

function divide(a, b) { return a / b; }

JavaScript позволяет нам создавать так называемые необязательные параметры функций — параметры, которые при вызове можно не указывать, после чего они примут некоторое значение по умолчанию. Вот пример функции с необязательным параметром b, имеющим значение по умолчанию 2:

function divide(a, b) {

if (typeof(b) == "undefined") b = 2;

return a / b;

}

Понятно, что мы должны как-то выяснить, был ли при вызове функции указан параметр b. Для этого мы используем оператор получения типа typeof (он был описан ранее). Если параметр b не был указан, данный оператор вернет строку "undefined"; тогда мы создадим переменную с именем b, как и у необязательного параметра, и присвоим ей число 2 — значение этого параметра по умолчанию, — которое и будет использовано в теле функции. Если возвращенное оператором значение иное, значит, параметр b был указан при вызове, и мы используем значение, которое было передано с ним.

 

Функции и переменные. Локальные переменные

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

Разумеется, любая функция может обращаться к любой переменной, объявленной вне ее тела (переменной уровня Web-страницы). При этом нужно помнить об одной вещи. Если существуют две переменные с одинаковыми именами, одна — уровня Web-страницы, другая — локальная, то при обращении по этому имени будет по- лучен  доступ   к   локальной   переменной.  Одноименная  переменная  уровня  страницы будет "замаскирована" своей локальной "тезкой".

 

Вызов функций

После объявления функции ее можно вызвать из любого Web-сценария, присутствующего на этой же Web-странице. Формат вызова функции:

<имя функции>([<список фактических параметров, разделенных запятыми>])

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

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

ВНИМАНИЕ !

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

Вот пример вызова объявленной нами ранее функции divide:

d = divide(3, 2);

Здесь мы подставили в выражение вызова функции фактические параметры — константы 3 и 2.

А здесь мы выполняем вызов функции с переменными в качестве фактических параметров:

s = 4 * divide(x, r) + y;

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

s = divide(4);

Тогда в переменной s окажется число 2 — результат деления 4 (значение первого параметра) на 2 (значение второго, необязательного, параметра по умолчанию).

Если функция не возвращает результат, то ее вызывают так:

initVars(1, 2, 3, 6);

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

Если функция не принимает параметров, при ее вызове все равно нужно указать пустые скобки, иначе возникнет ошибка выполнения Web-сценария:

s = computeValue();

Функции могут вызывать друг друга. Вот пример:

function cmp(c, d, e) {

var f;

f = divide(c, d) + e;

return f;

}

Здесь мы использовали в функции cmp вызов объявленной ранее функции divide.

 

Присваивание функций. Функциональный тип данных

JavaScript позволяет выполнять над функциями один фокус — присваивать функции переменным.

Пример:

var someFunc;

someFunc = cmp;

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

Впоследствии мы можем вызывать данную функцию, обратившись к переменной, которой она была присвоена:

c = someFunc(1, 2, 3);

Здесь мы вызвали функцию cmp через переменную someFunc.

Переменная,  которой  была  присвоена  функция,  хранит  данные,  относящиеся  к функциональному типу. Это еще один тип данных, поддерживаемый JavaScript.

А можно сделать и так:

var someFunc = function(c, d, e) {

var f;

f = divide(c, d) + e;

return f;

}

Здесь мы объявили функцию и сразу же присвоили ее переменной. Как видим, имени объявляемой функции мы не указали — в данном случае оно не нужно.

А еще JavaScript позволяет нам указать функцию в качестве параметра другой функции. Для примера давайте рассмотрим фрагмент JavaScript-кода нашего второго Web-сценария:

ceLinks.on("mouseover", function(e, t) { Ext.get(t).addClass("hovered");

});

Здесь второй параметр функции on (вообще-то, это не функция, а метод объекта, но об этом потом) — другая функция, объявленная там же. Эта функция принимает два параметра и содержит одно выражение.

В следующих главах, изучая библиотеку Ext Core, мы будем часто сталкиваться с функциями, которые присваиваются переменным и передаются в качестве пара- метра другим функциям. Настолько часто, что скоро привыкнем к этому.

 

Массивы

Массив — это пронумерованный набор переменных (элементов), фактически хранящийся в одной переменной. Доступ к отдельному элементу массива выполняется по его порядковому номеру, называемому индексом. А общее число элементов массива называется его размером. 

ВНИМАНИЕ !

Нумерация элементов массива начинается с нуля.

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

var someArray = [1, 2, 3, 4];

Здесь мы создали массив, содержащий четыре элемента, и присвоили его перемен- ной someArray. После этого мы можем получить доступ к любому из элементов по его индексу, указав его после имени переменной массива в квадратных скобках:

a = massive[2];

В данном примере мы получили доступ к третьему элементу массива. (Нумерация элементов массива начинается с нуля — помните об этом!)

Определять сразу все элементы массива необязательно:

someArray2 = [1, 2, , 4];

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

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

someArray[4] = 9;

При этом будет создан новый, пятый по счету, элемент массива с индексом 4 и значением 9.

Можно даже сделать так:

someArray[7] = 9;

В этом случае будут созданы четыре новых элемента, и восьмой элемент получит значение 9. Пятый, шестой и седьмой останутся неопределенными (undefined).

Мы можем присвоить любому элементу массива другой массив (или, как говорят опытные программисты, создать вложенный массив):

someArray[2] = ["n1", "n2", "n3"];

После этого можно получить доступ к любому элементу вложенного массива, ука- зав последовательно оба индекса: сначала — индекс во "внешнем" массиве, по- том — индекс во вложенном:

str = someArray[2][1];

Переменная str получит в качестве значения строку, содержащуюся во втором элементе вложенного массива, — n2.

Ранее говорилось, что доступ к элементам массива выполняется по числовому индексу. Но JavaScript позволяет создавать и массивы, элементы которых имеют строковые индексы (ассоциативные массивы, или хэши).

Пример:

var hash;

hash["AUDIO"] = "t_audio.htm"; hash["IMG"] = "t_img.htm"; hash["TITLE"] = "t_title.htm";

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

var someArray;

someArray = [];

Разумеется, впоследствии мы можем и даже должны наполнить этот массив эле- ментами:

someArray[0] = 1; someArray[1] = 2; someArray[2] = 3;

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

 

Ссылки

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

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

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

Рассмотрим такой пример:

var myArray = ["AUDIO", "IMG", "TITLE"];

var newArray = myArray;

Здесь создается массив myArray с тремя элементами и далее он присваивается переменной newArray (при этом данная переменная получает ссылку на массив). Если потом мы присвоим новое значение первому элементу массива myArray:

myArray[0] = "VIDEO";

и обратимся к нему через переменную newArray:

s = newArray[0];

то в переменной s окажется строка "VIDEO" — новое значение первого элемента этого массива. Фактически переменные myArray и newArray указывают на один и тот же массив.

ВНИМАНИЕ !

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

Переменная, хранящая ссылку на массив (и экземпляр объекта), содержит данные объектного типа. Это последний тип данных, поддерживаемый JavaScript, который мы здесь рассмотрим.

НА ЗАМЕТКУ

Ранее мы узнали, что можем присвоить функцию переменной. Так вот, фактически пе- ременная, которой была присвоена функция, также хранит ссылку на нее.

 

Объекты

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

 

Понятия объекта и экземпляра объекта

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

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

Однако JavaScript предоставляет нам и сложные типы данных. Сущность такого типа может хранить сразу несколько значений. Один из примеров сложного типа данных — уже знакомые нам массивы.

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

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

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

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

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

Объекты — невероятно мощное средство объединить данные (свойства) и средства их   обработки   (методы)   воедино.   Так,   объект,   представляющий   абзац   Web- страницы, объединяет параметры этого абзаца, хранящиеся в различных свойствах, и инструменты для манипуляции абзацем, предоставляемые соответствующими методами. Нам не придется "раскидывать" параметры абзаца по десяткам переменных и пользоваться для работы с ним массой отдельных функций — все это сведено в один объект.

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

Все объекты, доступные в Web-сценариях, можно разделить на три разновидности:

-  предоставляемые самим языком JavaScript (встроенные объекты);

-  предоставляемые Web-обозревателем (объекты Web-обозревателя);

- созданные нами или сторонним разработчиком на самом JavaScript (пользовательские объекты). В частности, популярные JavaScript-библиотеки, такие как Ext Core, создают множество пользовательских объектов.

С точки зрения Web-программиста все эти объекты одинаковы. И работа ними и их экземплярами выполняется сходным образом.

 

Получение экземпляра объекта

Но как нам получить экземпляр нужного объекта?

-  Экземпляры   многих   объектов   создаются   самим   языком   JavaScript,   Web-обозревателем или библиотеками сторонних разработчиков.

-  Экземпляры некоторых объектов возвращаются функциями или методами других объектов.

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

Давайте рассмотрим все эти случаи подробно.

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

ВНИМАНИЕ !

Вообще-то, в переменных хранятся ссылки на экземпляры объектов. Просто раньше мы договорились, что будем считать, будто в переменных хранятся сами экземпляры объектов, — так проще.

Так, переменная Math хранит экземпляр одноименного объекта, поддерживающего множество методов для выполнения математических и тригонометрических вычислений над числами:

var a = Math.sqrt(2);

Это выражение поместит в переменную a квадратный корень из 2. Метод sqrt объекта Math как раз вычисляет квадратный корень из числа, переданного ему в качестве единственного параметра.

А метод sin объекта Math вычисляет синус угла, заданного в радианах и переданного данному методу единственным параметром:

var b = Math.sin(0.1);

Как видим, мы просто используем переменную Math, созданную языком JavaScript, чтобы получить доступ к экземпляру объекта Math и его методам.

Web-обозреватель также предоставляет множество экземпляров объектов, пред- ставляющих текущую Web-страницу, различные ее элементы и сам Web- обозреватель. Так, Web-страницу представляет экземпляр объекта HTMLDocument, который хранится в переменной document, также созданной Web-обозревателем.

В частности, объект HTMLDocument поддерживает метод write, выводящий переданную ему в качестве единственного параметра строку в то место Web-страницы, где встретился вызов этого метода:

document.write("Привет, посетитель!");

И в этом случае мы просто используем переменную document, созданную Web- обозревателем, чтобы получить доступ к экземпляру объекта HTMLDocument и его методу write.

Сторонние библиотеки также создают множество экземпляров объектов сами. Так, библиотека Ext Core создает экземпляр объекта Ext, хранящийся в одноименной переменной:

var ceLinks = Ext.select("UL[id=navbar] > LI");

Здесь мы обратились к методу select объекта Ext. Данный метод возвращает массив экземпляров объектов Element, каждый из которых представляет элемент Web-страницы, удовлетворяющий переданному ему в качестве единственного параметра специальному селектору CSS. (Кстати, объект Element также определен библиотекой Ext Core.)

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

var elNavbar = Ext.get("navbar");

Метод get объекта Ext, созданного библиотекой Ext Core, возвращает экземпляр объекта Element, представляющий определенный элемент Web-страницы. В данном случае это будет элемент, значение атрибута тега ID которого равно "navbar" — именно такую строку мы передали методу get в качестве параметра.

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

И, наконец, с помощью оператора создания экземпляра new мы можем создать экземпляр нужного нам объекта сами, явно:

new <имя объекта>([<список параметров, разделенных запятыми>])

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

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

Язык JavaScript предоставляет нам объект Date, хранящий значение даты и времени. Его экземпляры можно создать только явно, оператором new:

var dNow = new Date();

После выполнения этого выражения в переменной dNow окажется экземпляр объекта Date, хранящий сегодняшнюю дату и текущее время.

А вот  выражение, которое  поместит в переменную  dNewYear экземпляр объекта

Date, хранящий дату 31 декабря 2009 года и текущее время:

var dNewYear = new Date(2009, 12, 31);

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

-  Экземпляры объектов Web-обозревателя, представляющие Web-страницу и сам Web-обозреватель, доступны через соответствующие переменные.

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

-  Экземпляры всех объектов языка JavaScript, кроме Math, получаются явным созданием с помощью оператора new.

-  Экземпляр объекта  Math языка JavaScript  доступен  через одноименную переменную.

Это самые общие правила. А конкретные приемы получения нужного экземпляра объекта мы рассмотрим потом, когда начнем заниматься Web-программированием вплотную.

 

Работа с экземпляром объекта

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

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

Пример:

var elNavbar = Ext.get("navbar");

Здесь мы вызвали метод get объекта Ext, экземпляр которого хранится в перемен- ной Ext, передав данному методу параметр — строку "navbar". Как мы уже знаем, метод get ищет на Web-странице элемент, имеющий значение атрибута тега ID, которое совпадает с переданной ему в качестве параметра строкой. Возвращенный этим методом результат — экземпляр объекта Element, представляющего список navbar, — будет присвоен переменной elNavbar.

Пример:

var elParent = elNavbar.parent();

А здесь мы вызвали метод parent у экземпляра объекта Element, хранящегося в переменной elNavbar и полученного в предыдущем выражении. Возвращенный этим методом результат — экземпляр объекта Element, представляющего элемент Web- страницы, который является родителем списка navbar, — будет присвоен перемен- ной elParent.

Если результат, возвращаемый каким-либо методом (назовем его методом 1), представляет собой объект, мы можем сразу вызвать метод у него (это будет метод 2). Для этого мы добавим справа от вызова метода 1 точку, после которой поставим вызов метода 2.

Пример:

elNavbar.parent().addClass("hovered");

Здесь мы сначала вызвали метод parent у экземпляра объекта Element, хранящегося в переменной elNavbar (метод 1). У полученного в результате вызова метода 1 результата — экземпляра объекта Element, представляющего элемент-родитель, — мы вызвали метод addClass (метод 2).

Такие цепочки последовательных вызовов методов, когда стоящий справа метод

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

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

Пример:

var sID = elParent.id;

В данном примере мы обратились к свойству id объекта Element, экземпляр которого хранится в переменной elParent. Это свойство хранит значение атрибута тега ID у соответствующего элемента Web-страницы.

Пример:

Ext.enableFx = false;

Здесь мы присвоили новое значение свойству enableFx объекта Ext, экземпляр которого хранится в переменной Ext.

Пример:

var sID = elNavbar.parent().id;

А здесь мы сначала получили родитель элемента Web-страницы, хранящегося в переменной elNavbar, после чего извлекли значение атрибута тега ID уже у полученного родителя.

Что ж, с объектами и их экземплярами, свойствами и методами мы познакомились. Теперь давайте кратко "пробежимся" по объектам, которые будем использовать при написании Web-сценариев. Рассмотрим только встроенные объекты JavaScript и объекты Web-обозревателя; пользовательским объектам, создаваемым библиотекой Ext Core, будет полностью посвящена глава 15.

 

Встроенные объекты языка JavaScript

Ранее мы познакомились со встроенным объектом Date, который предоставляется самим языком JavaScript и служит для хранения значений даты и времени:

var dNow = new Date();

Объект Date поддерживает ряд методов, позволяющих получать отдельные составляющие даты и времени и манипулировать ими. Так, метод getDate возвращает число, getMonth — номер месяца, а getFullYear — год. Все эти методы не принимают параметров, а возвращаемые ими результаты представляют собой числа.

Пример:

var sNow = dNow.getDate() + "." + dNow.getMonth() + "." +

dNow.getFullYear();

Здесь мы объединяем в одну строку число, номер месяца и год, разделяя их точками. Таким образом мы получим значение даты в формате <число>.<месяц>.<год>.

При  этом  JavaScript  сам  выполняет  неявное  преобразование  числовых  величин в строки.

Объект String служит для хранения строк.

var s = "JavaScript";

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

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

Свойство length объекта String хранит длину строки в символах:

var l = s.length;

var l = "JavaScript".length;

Эти выражения помещают в переменную l длину строки JavaScript.

Метод substr возвращает фрагмент строки заданной длины, начинающийся с указанного символа:

substr(<номер первого символа>[, <длина фрагмента>]);

Первым параметром передается номер первого символа, включаемого в возвращаемый фрагмент строки.

ВНИМАНИЕ !

В JavaScript символы в строках нумеруются, начиная с нуля.

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

После выполнения Web-сценария

var s1 = s.substr(4);

var s2 = s.substr(4, 2);

в переменной s1 окажется строка "Script", а в переменной s2 — строка "Sc".

Объект Number служит для хранения чисел, а объект Boolean — логических величин:

var n = 123;

var b = false;

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

Объект Array служит для хранения массивов:

var a = [1, 2, 3, 4];

Он поддерживает единственное свойство length, возвращающее размер массива, т. е. число элементов в нем:

var l = a.length;

var l = [1, 2, 3, 4].length;

Уже знакомый нам объект Math, единственный экземпляр которого создается самим JavaScript и хранится в переменной Math, представляет набор методов для выполнения математических и тригонометрических вычислений.

Мы не будем рассматривать здесь все встроенные объекты JavaScript и поддерживаемые ими свойства и методы. Это можно найти в любой книге по JavaScript- программированию.

 

Объект

Object

и использование его экземпляров

Но об одном встроенном объекте следует поговорить особо. Это объект Object, весьма специфический.

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

Экземпляры объекта Object создают с помощью особых выражений, называемых инициализаторами.  Инициализатор  чем-то  похож  на определение стиля (листинг 14.12).

Листинг 14.12

{

<имя свойства 1> : <значение свойства 1> ,

<имя свойства 2> : <значение свойства 2> ,

. . .

<имя свойства n-1> : <значение свойства n-1> ;

<имя свойства n> : <значение свойства n>

<имя метода 1> : <функция, реализующая метод 1> ,

<имя метода 2> : <функция, реализующая метод 2> ,

. . .

<имя метода n-1> : <функция, реализующая метод n-1> ,

<имя метода n> : <функция, реализующая метод n>

}

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

Пример:

   var oConfig = { tag: "DIV", id: "cother", html: "Это прочие сведения." };

Здесь мы получили экземпляр объекта Object со свойствами tag, id и html, задали для этих свойств значения и сохранили получившийся экземпляр в переменной oConfig.

Пример:

var oConfig2 = { url: "pages/t_img.htm",

success: function (response, opts){

var obj = Ext.decode(response.responseText);

}

};

А здесь мы создали экземпляр объекта Object со свойством url и методом success и сохранили получившийся экземпляр в переменной oConfig2. (Код последнего примера взят из документации по библиотеке Ext Core.)

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

Экземпляры объекта Object в библиотеке Ext Core обычно служат для задания различных необязательных параметров и создаются как раз с помощью инициализаторов. Так что мы часто будем иметь с ними дело.

 

Объекты Web-обозревателя. Объектная модель документа DOM

Объекты, предоставляемые Web-обозревателем, делятся на две группы:

-  объекты,  представляющие  Web-страницу  и  элементы,  созданные  с  помощью разных тегов (абзац, заголовок, таблица, изображение и др.);

-  объекты, представляющие сам Web-обозреватель. Начнем с объектов первой группы.

Как мы уже знаем, саму Web-страницу представляет объект HTMLDocument. Единственный экземпляр данного объекта хранится в переменной document и представляет Web-страницу, открытую в текущем окне Web-обозревателя.

Отдельный элемент Web-страницы, независимо от тега, с помощью которого он создан, представляется объектом HTMLElement. На этом объекте основаны другие объекты, представляющие элементы Web-страницы, которые созданы на основе определенных  тегов.  Так,  абзац  представляется  объектом  HTMLParagraphElement, изображение — объектом HTMLImageElement, гиперссылка — объектом HTMLLinkElement, а таблица — объектом HTMLTableElement.

Для каждого элемента загруженной Web-страницы Web-обозреватель создает экземпляр соответствующего объекта. Например, для каждого абзаца создается экземпляр объекта HTMLParagraphElement, для каждого изображения — экземпляр объекта    HTMLImageElement,    для    каждой    гиперссылки —    экземпляр    объекта HTMLLinkElement, а для каждой таблицы — экземпляр объекта HTMLTableElement.

В результате в памяти компьютера создается структура взаимосвязанных экземпляров объектов, соответствующая структуре элементов Web-страницы. Она называется  объектной  моделью  документа,  или  DOM  (сокращение  от  Document  Object Model — объектная модель документа).

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

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

DOM является одним из основополагающих стандартов Интернета, разрабатывается и утверждается организацией W3C. Все Web-обозреватели обязаны ее поддерживать.

Что касается объектов второй группы, то их немного. Это, прежде всего, объект Window, представляющий окно Web-обозревателя и поддерживающий ряд свойств и методов, с помощью которых мы можем им управлять. Экземпляр этого объекта, представляющий текущее окно Web-обозревателя, хранится в переменной window. Кроме того, существует еще несколько объектов, представляющих Web-обозреватель, но они встречаются значительно реже.

К рассмотрению объектов Web-обозревателя мы вернемся в главе 15. А пока что закончим с ними.

 

Свойства и методы экземпляра объекта

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

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

Пример:

var dNow = new Date();

dNow.someProperty = 3;

Здесь мы создали у экземпляра объекта Date, хранящегося в переменной dNow, свойство someProperty и присвоили ему значение 3. Данное свойство будет принадлежать только этому экземпляру объекта Date.

Пример:

elNavbar.someMethod = function() { . . . };

Здесь мы добавили к экземпляру объекта Element библиотеки Ext Core, хранящемуся в переменной elNavbar, метод someMethod. Опять же, данный метод будет принадлежать только этому экземпляру объекта.

А в следующем примере мы добавили к созданному ранее конфигуратору oConfig свойство style и присвоили этому свойству строку с определением встроенного стиля:

oConfig.style = "color: red;";

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

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

 

Правила написания выражений

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

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

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

-  Признаком конца выражения служит символ точки с запятой (;).

В качестве примера давайте возьмем одно из выражений из начала главы:

y = y1 * y2 + x1 * x2;

Мы можем записать его так, разнеся на две строки:

y = y1 * y2 +

x1 * x2;

Или даже так:

y =

y1*

y2+

x1*

x2;

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

Но если мы нарушим их, выполнив перенос строк внутри имени переменной y2:

y = y1 * y

2 + x1 * x2;

получим сообщение об ошибке.

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

 

Комментарии JavaScript

Из глав 2 и 7 мы знаем о существовании комментариев — особых фрагментов кода HTML и CSS, которые не обрабатываются Web-обозревателем и служат для того, чтобы Web-дизайнер смог оставить какие-либо заметки для себя или своих коллег. Было бы странно, если бы JavaScript не предоставлял аналогичной возможности.

Комментарии JavaScript бывают двух видов.

Комментарий, состоящий из одной строки, создают с помощью символа / (слэш), который помещают в самом начале строки комментария:

/ Это комментарий

var dNow = new Date();

Однострочный комментарий начинается с символа / и заканчивается концом строки. Комментарий, состоящий из произвольного числа строк, создают с помощью последовательностей символов /* и */. Между ними помещают строки, которые станут комментарием:

/*

Это комментарий, состоящий из нескольких строк.

*/

var dNow = new Date();

Многострочный комментарий начинается с последовательности символов /* и заканчивается последовательностью */.

Вот и все о языке JavaScript.

Что дальше?

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

В следующей главе мы изучим библиотеку Ext Core, которой будем пользоваться в дальнейшем. Также мы познакомимся с некоторыми объектами Web-обозревателя, с которыми будем работать напрямую. А еще узнаем о событиях и их обработке.

 

ГЛАВА 15. Библиотека Ext Core и объекты Web-обозревателя

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

Также мы получили в свое распоряжение библиотеку Ext Core, призванную облегчить труд Web-программистов — нас с вами. О, это замечательная библиотека!..

Замечательная настолько, что ей будет посвящена почти вся эта глава. Еще мы рассмотрим пару объектов Web-обозревателя, с которыми мы будем работать напрямую, но разговор о них будет совсем коротким.

Итак, просим любить и жаловать — библиотека Ext Core!

 

Библиотека Ext Core

В этом разделе мы будем изучать самые полезные для нас на данный момент возможности библиотеки Ext Core. Полностью она описана в справочнике, доступном на ее "домашнем" Web-сайте.

 

Зачем нужна библиотека Ext Core

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

В том-то и дело, что библиотека Ext Core призвана устранять сложности, а не создавать их.

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

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

Организация W3C требует, чтобы DOM соответствовала разработанному стандарту, тесно связанному со стандартами HTML и CSS. Разумеется, все Web- обозреватели его поддерживают.

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

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

Поэтому опытные Web-программисты и создают дополнительные библиотеки, добавляющие к инструментам, предусмотренным стандартом DOM, свои собственные, как правило, более мощные. К таким библиотекам относится и Ext Core.

Далее. Стандарт — стандартом, но разные Web-обозреватели зачастую по-разному его поддерживают. Сплошь и рядом возникают ситуации, когда один и тот же Web- сценарий прекрасно работает, скажем, в Firefox, но никак не хочет правильно исполняться в Internet Explorer. Ладно, Internet Explorer, как говорится, — отрезанный ломоть, до сих пор не получивший поддержку HTML 5 и CSS 3, но ведь и Opera, и Chrome,  и  Safari  тоже  поддерживают  некоторые  аспекты  стандарта  DOM  по-своему. Налицо несовместимость Web-обозревателей, которую приходится как-то обходить.

К тому же, некоторые объекты Web-обозревателя до сих пор не стандартизированы, и, насколько известно автору, даже не было предпринято попытки их стандартизировать. К таким объектам относятся те, что представляют сам Web- обозреватель, в частности, объект Window.

Поэтому сторонние библиотеки, расширяющие возможности DOM, еще и устраняют несовместимость Web-обозревателей. И Ext Core — не исключение.

Сейчас такие библиотеки — Prototype, jQuery и "героиня" этой главы Ext Core — становятся все более и более популярными. Они применяются при программировании поведения для всех сложных Web-сайтов. В России даже выходят посвященные им книги; по крайней мере, автору встречалась книга, посвященная jQuery.

Ну а мы вернемся к Ext Core.

 

Использование библиотеки Ext Core

Библиотека Ext Core распространяется с Web-страницы своего "домашнего" Web-сайта. На ней мы найдем гиперссылки на Web-страницы загрузки библиотеки, краткое руководство программиста с примерами и полный справочник по ней.

Библиотека Ext Core распространяется в виде архива ZIP, хранящегося в файле с именем вида ext-core-<номер версии>.zip и содержащего саму библиотеку, ее исходные коды в "читабельном" виде и несколько примеров. На момент написания книги была доступна версия 3.1.0, ее-то и использовал автор.

Сама библиотека Ext Core хранится в файле Web-сценария ext-core.js, который находится в описанном архиве. Она написана на языке JavaScript и представляет со- бой объявления многочисленных объектов, их свойств и методов и переменных, хранящих  экземпляры  объектов, —  всем  этим  Web-программист  будет  активно пользоваться.

Код Ext Core нужно выполнить еще до загрузки Web-страницы, тогда библиотека сможет успешно создать все свои объекты, экземпляры объектов и переменные. Для этого в секцию тела Web-страницы (тег ) помещают тег

Он указывает Web-обозревателю загрузить Web-сценарии, хранящиеся в файле ext- core.js и составляющие эту библиотеку, и выполнить их еще до того, как будет за- гружена секция тела Web-страницы. Таким образом, когда Web-страница будет выведена на экран, библиотека Ext Core окажется "во всеоружии".

Что касается Web-сценариев, составляющих поведение Web-страницы, то они хранятся в отдельном файле — так требует концепция Web 2.0. Тег

Любые Web-сценарии, выполняющие манипуляции с элементами Web-страницы, должны быть выполнены только после того, как Web-страница будет полностью загружена, а соответствующая ей DOM — сформирована. Но как этого добиться? Даже если мы поместим тег

А вот HTML-код, формирующий гиперссылки на Web-страницы index.htm и t_img.htm:

  • HTML

  • IMG
  • Интернет-адреса остальных гиперссылок формируются аналогично.

    5. Сохраняем готовую Web-страницу t_audio.htm.

    Остается проделать все это с остальными Web-страницами, описывающими теги, что мы к этому времени создали.

    Создадим также Web-страницы с описанием технологии CSS, примеров и сведениями о разработчиках. Они будут храниться в файлах css_index.htm, samples_index.htm и about.htm.

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

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

    Конечно, наш Web-сайт все еще неполон, но для его отладки созданных Web- страниц вполне хватит. Потом, закончив с программированием, мы доделаем его до конца.

     

    Скрытие и открытие вложенных списков

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

    Легко!

    - Изначально все вложенные списки у нас будут скрыты.

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

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

    - В данный момент может быть открыт только один вложенный список — остальные будут скрыты.

    - Для скрытия и раскрытия вложенного списка мы будем менять у него значение атрибута стиля display (см. главу 9) с помощью методов объекта Element, управляющих видимостью элемента Web-страницы.

    Откроем файл Web-сценария main.js и запишем где-либо в его начале, еще до вызова метода onReady объекта Ext, объявление функции, приведенное в листинге 16.6.

    Листинг 16.6

    function showInnerList(iIndex) {

    var elNavbar = Ext.get("navbar");

    var ceInnerLists = elNavbar.select("UL");

    ceInnerLists.setDisplayed(false);

    if (iIndex) {

    var sSelector = "UL:nth(" + iIndex + ")";

    elNavbar.child(sSelector).setDisplayed(true);

    }

    }

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

    Рассмотрим код этой функции построчно.

    Сначала получаем "внешний" список, формирующий полосу навигации:

    var elNavbar = Ext.get("navbar");

    Затем получаем все вложенные в него списки:

    var ceInnerLists = elNavbar.select("UL");

    Далее скрываем все вложенные списки, для чего используем метод setDisplayed — так проще:

    ceInnerLists.setDisplayed(false);

    Проверяем, был ли функции showInnerList передан параметр:

    if (iIndex) {

    Если он был передан, переменная iIndex будет содержать число, которое преобразуется в значение true, и условие выполнится. В противном случае переменная iIndex получит значение null, которое будет преобразовано в false, и условие не выполнится.

    Если параметр функции showInnerList был передан, выполняется следующий код. Формируем строку с селектором CSS, который будет выбирать вложенный список, чей порядковый номер был передан с параметром:

    var sSelector = "UL:nth(" + iIndex + ")";

    Выбираем вложенный список с заданным номером и открываем его:

    elNavbar.child(sSelector).setDisplayed(true);

    }

    На этом выполнение функции showInnerList завершится.

    Теперь вставим в конец тела функции, которая передается в качестве параметра методу onReady объекта Ext, такое выражение:

    showInnerList(outerIndex);

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

    Теперь откроем Web-страницу index.htm и в секцию ее заголовка (в теге ) вставим такой код:

    Мы присваиваем переменной outerIndex число 1 — номер вложенного списка, который должен быть открыт при открытии Web-страницы index.htm (это список раздела  "HTML").  Когда  будут  выполняться  Web-сценарии,  хранящиеся  в  файле main.js, в том числе и вызов функции showInnerList, значение этой переменной будет передано данной функции в качестве параметра.

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

    Такой же Web-сценарий мы вставим в секцию заголовка Web-страниц, описывающих теги HTML. И не забываем сохранять исправленные Web-страницы.

    В секцию заголовка Web-страницы css_index.htm и Web-страниц, описывающих атрибуты стиля CSS, мы вставим аналогичный код:

    Он укажет, что при открытии данных Web-страниц должен быть открыт второй по счету вложенный список — раздела "CSS".

    В секцию заголовка Web-страницы samples_index.htm и Web-страниц, содержащих примеры, мы вставим код... сами догадайтесь, какой. (Подсказка: он должен раскрыть третий вложенный список.)

    А вот в секцию заголовка Web-страницы about.htm вставим такой код:

    Поскольку четвертый пункт "внешнего" списка не содержит вложенного списка, открывать там ничего не нужно — следует только скрыть уже открытые вложенные списки.

    Сохраним все исправленные Web-страницы и опробуем их в деле. Неплохо получилось, правда?

     

    Выделение пункта полосы навигации, соответствующего открытой в данный момент Web-странице

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

    Хорошим тоном сейчас считается как-то выделять пункт полосы навигации, соответствующий открытой в данный момент Web-страницы. Выделяют его обычно рамкой или "инверсными" цветами (в качестве цвета текста используется цвет фона, а в качестве цвета фона — цвет текста). Давайте и мы сделаем нечто подобное.

    -  Пункт вложенного списка мы будем выделять "инверсными" цветами.

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

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

    Заодно давайте объединим установку выделения на пункт полосы навигации со скрытием и открытием вложенных списков.

    Откроем таблицу стилей main.css и добавим в нее стили из листинга 16.7.

    Листинг 16.7

    .selected,

    #navbar .selected A:link,

    #navbar .selected A:focus,

    #navbar .selected A:hover,

    #navbar .selected A:active,

    #navbar .selected A:visited { color: #F8F8F8; background-color: #576C8C }

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

    Сохраним таблицу стилей. И откроем файл Web-сценария main.js. Удалим из него объявление функции showInnerList, созданное ранее, и вставим на его место объявление функции, приведенной в листинге 16.8.

    Листинг 16.8

    function selectItem(iIndex, sText) { var elNavbar = Ext.get("navbar"); elNavbar.select("LI").removeClass("selected"); var ceInnerLists = elNavbar.select("UL"); ceInnerLists.setDisplayed(false);

    var sSelector = "> LI:nth(" + iIndex + ")"; var elOuterItem = elNavbar.child(sSelector); var elInnerList = elOuterItem.child("UL");

    if (elInnerList) {

    elInnerList.setDisplayed(true);

    if (sText) {

    sSelector = "LI:has(:nodeValue(" + sText + "))";

    elOuterItem.child(sSelector).addClass("selected");

    }

    } else elOuterItem.addClass("selected");

    }

    Эта функция с именем selectItem будет выполнять несколько действий:

    -  Если первым параметром передан порядковый номер пункта "внешнего" списка, который содержит вложенный список, она откроет данный список и скроет все остальные вложенные списки.

    -  Если первым параметром передан порядковый номер пункта "внешнего" списка, который не содержит вложенный список, она выделит данный пункт "внешнего" списка и, опять же, скроет все остальные вложенные списки.

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

    Рассмотрим ее код построчно.

    Получаем "внешний" список, формирующий полосу навигации:

    var elNavbar = Ext.get("navbar");

    Убираем из привязки ко всем пунктам всех списков, формирующих полосу навигации, — и "внешнего", и вложенных — стилевой класс selected:

    elNavbar.select("LI").removeClass("selected");

    Так мы снимем выделение со всех пунктов всех списков. Сворачиваем все вложенные списки:

    var ceInnerLists = elNavbar.select("UL");

    ceInnerLists.setDisplayed(false);

    Эти три выражения перекочевали из функции showInnerList без изменений. Формируем строку, содержащую селектор CSS, который выбирает пункт "внешнего" списка с переданным функции selectItem в качестве первого параметра порядковым номером, и получаем этот пункт:

    var sSelector = "> LI:nth(" + iIndex + ")";

    var elOuterItem = elNavbar.child(sSelector);

    Получаем вложенный список, присутствующий в этом пункте, разумеется, если он там есть:

    var elInnerList = elOuterItem.child("UL");

    Если вложенный список есть, метод child вернет экземпляр объекта Element, в противном случае — значение null.

    Проверяем, что мы получили в результате вызова метода child в предыдущем выражении:

    if (elInnerList) {

    Если этот метод вернул экземпляр объекта Element, последний будет преобразован в значение true, и условие выполнится. Если же он вернул значение null, оно будет преобразовано в false, и условие не выполнится.

    Если вложенный список существует, открываем его:

    elInnerList.setDisplayed(true);

    Проверяем, был ли функции selectItem передан второй параметр — текст пункта вложенного списка, который следует выделить:

    if (sText) {

    Если он был передан, формируем строку с селектором CSS, выбирающим пункт вложенного списка, потомок которого содержит текст, переданный этим самым вторым параметром:

    sSelector = "LI:has(:nodeValue(" + sText + "))";

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

    elOuterItem.child(sSelector).addClass("selected");

    }

    Если полученный ранее пункт "внешнего" списка с указанным порядковым номером не содержит вложенного списка, привязываем к этому пункту стилевой класс selected:

    } else elOuterItem.addClass("selected");

    На этом выполнение функции завершается.

    В теле функции, передаваемой в качестве параметра методу onReady объекта Ext, ранее мы добавили вызов функции showInnerList. Удалим его и вместо него вставим такой код:

    selectItem(outerIndex, innerText);

    Здесь мы вызываем функцию selectItem, передавая ей в качестве параметров значения переменных outerIndex и innerText. Первая переменная будет хранить номер вложенного списка, который требуется открыть, а вторая — текст пункта вложенного списка, который нужно выделить.

    Откроем Web-страницу index.htm, найдем код, вставленный ранее в ее секцию заголовка, и дополним его таким образом:

    Мы  добавили  выражение,  присваивающее  переменной  innerText значение  null. При выполнении Web-сценариев, хранящихся в файле main.js, в том числе и вызове функции selectItem, значения обеих этих переменных будут переданы данной функции в качестве параметров. В результате откроется первый вложенный список, и ни один его пункт не будет выделен.

    Откроем Web-страницу t_audio.htm и дополним вставленный ранее код в ее секцию заголовка так:

    В  результате  будет  открыт  первый  вложенный  список  и  в  нем  выделен  пункт "AUDIO".

    Аналогично заменим вставленный ранее код во всех остальных Web-страницах нашего Web-сайта и проверим их в действии.

     

    Скрытие и открытие текста примеров

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

    Давайте и мы реализуем нечто подобное на своих Web-страничках. Хотя бы в самом простом варианте.

    - Текст примера мы поместим в блочный контейнер, к которому привяжем стилевой класс sample. Этот стилевой класс будет служить двум целям: во-первых, он обозначит данный контейнер как "вместилище" для примера, во-вторых, он задаст для контейнера особое представление, чтобы его выделить среди остального содержимого Web-страницы.

    - Особое представление для контейнера с текстом примера будет включать внутренние отступы и рамку.

    - Все содержимое контейнера изначально будет скрыто, за исключением первого его потомка (у нас — заголовок "Пример:" шестого уровня).

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

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

    - На Web-странице могут быть несколько контейнеров с текстом примеров. Это следует учесть при написании Web-сценария.

    Откроем таблицу стилей main.css и добавим в нее такие стили:

    .sample   { padding: 5px; border: thin dotted #B1BEC6 }

    sample > :first-child  { margin: 0px 0px; cursor: pointer }

    Первый стиль — это стилевой класс sample, помечающий контейнер как "вместилище" для примера. Он задает для контейнера параметры внутренних отступов и рамки.

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

    Теперь откроем файл Web-сценария main.js и поместим где-либо в его начале, перед вызовом метода onReady объекта Ext, код листинга 16.9.

    Листинг 16.9

    function showHideSample(e, t) {

    var elDiv = Ext.fly(t).parent(".sample");

    var ceSampleText = elDiv.select("*:not(:first-child)"); ceSampleText.setVisibilityMode(Ext.Element.DISPLAY); ceSampleText.toggle();

    }

    function prepareSamples() {

    var ceSamples = Ext.select(".sample");

    ceSamples.each(function(el, cl, ind){ var elH6 = el.child(":first"); elH6.on("click", showHideSample); elH6.on("mouseover", function(e, t) {

    Ext.get(this).parent("DIV").addClass("hovered");

    });

    elH6.on("mouseout", function(e, t) { Ext.get(this).parent("DIV").removeClass("hovered");

    });

    var ceSampleText = el.select("*:not(:first-child)");

    ceSampleText.setDisplayed(false);

    });

    }

    Мы объявили функции showHideSample и prepareSamples. Первая будет обрабатывать событие click в первом потомке контейнера с текстом примера и в ответ на это событие скрывать или открывать данный контейнер. Вторая будет выполнять подготовительные  действия:  при  открытии  Web-страницы  скрывать  контейнеры с текстом примеров и привязывать к ним обработчики событий.

    Рассмотрим код этих функций построчно. И начнем с функции prepareSamples.

    Сначала получаем все контейнеры с привязанным стилевым классом sample, т. е. содержащие текст примеров:

    var ceSamples = Ext.select(".sample");

    Затем для каждого полученного контейнера выполняем описанные далее манипуляции:

    ceSamples.each(function(el, cl, ind){

    Получаем первый потомок контейнера:

    var elH6 = el.child(":first");

    Привязываем    к    нему    в    качестве    обработчика    события    click  функцию

    showHideSample:

    elH6.on("click", showHideSample);

    Привязываем к нему функцию — обработчик события mouseover, которую там же и объявляем:

    elH6.on("mouseover", function(e, t) { Ext.fly(this).parent("DIV").addClass("hovered");

    });

    Эта функция получит родитель элемента Web-страницы, в котором возникло данное событие, т. е. контейнер с текстом примера, и привяжет к нему стилевой класс hovered.

    Не забываем привязать функцию-обработчик события mouseout, которая будет убирать из привязки к контейнеру стилевой класс hovered:

    elH6.on("mouseout", function(e, t) { Ext.fly(this).parent("DIV").removeClass("hovered");

    });

    Получаем все остальные элементы-потомки контейнера (не являющиеся первым потомком):

    var ceSampleText = el.select("*:not(:first-child)");

    И скрываем их:

    ceSampleText.setDisplayed(false);

    });

    На очереди — функция showHideSample.

    Получаем родитель элемента Web-страницы, в котором возникло событие:

    var elDiv = Ext.fly(t).parent(".sample");

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

    Получаем все элементы-потомки контейнера, не являющиеся первым потомком:

    var ceSampleText = elDiv.select("*:not(:first-child)");

    Указываем, что для управления их видимостью будет использован атрибут стиля display, и изменяем их видимость (открываем, если они скрыты, и скрываем, если они открыты):

    ceSampleText.setVisibilityMode(Ext.Element.DISPLAY);

    ceSampleText.toggle();

    Использовать здесь метод toggle проще, чем setDisplayed — нам не придется проверять, открыты данные элементы Web-страницы или нет. Правда, перед этим потребуется  указать,  что  видимостью  элемента  будет  управлять  атрибут  стиля display, но это мелочи.

    Еще нам нужно поставить вызов функции prepareSamples в самый конец функции, передаваемой методу onReady объекта Ext:

    prepareSamples();

    Теперь откроем любую Web-страницу, содержащую описание тега HTML или атрибута стиля CSS, и поместим текст примера в блочный контейнер с привязанным стилевым классом sample. Листинг 16.10 иллюстрирует HTML-код Web-страницы t_audio.htm.

    Листинг 16.10

    <DIV CLASS="sample">

    <H6>Пример:</H6>

    <PRE>&lt;AUDIO SRC=&quot;sound.wav&quot; CONTROLS&gt;&lt;/AUDIO&gt;</PRE>

    <H6>Результат:</H6>

    <AUDIO SRC="sound.wav" CONTROLS></AUDIO>

    </DIV>

    Внесем исправления во все аналогичные Web-страницы и проверим их в действии.

    Что дальше?

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

    Часть IV будет посвящена самым современным подходам в Web-дизайне и Web- программировании: подгружаемому и генерируемому содержимому и семантической разметке. Уже в следующей главе мы научимся подгружать часть содержимого Web-страницы из сторонних файлов программно.

    А библиотека Ext Core нам в этом поможет.