• Содержание
  • Разработка приложений с помощью Mozilla / автор: Н.Макфарлейн
    5. Глава: Скрипты

    JavaScript, несложный язык программирования с синтаксисом, напоминающим C-синтаксис, является существенной частью платформы Mozilla. Программы на JavaScript или их фрагменты называются сценариями, или скриптами. При добавлении скриптов XUL документы из документов, доступных только для чтения, становятся динамическими интерфейсами, которые могут выполнять какие-нибудь действия в ответ на команды пользователя. Написание скриптов - задача программиста, а не автора web-страницы или человека, предоставляющего для нее информацию. Приложения Mozilla могут разрабатываться только программистами.

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

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

    Легкие приложения Mozilla содержат скрипты, похожие на скрипты web- страниц. В web-страницах такие скрипты часто добавляют к HTML- содержимому уже в самом конце разработки. Когда эти скрипты разрастаются, их иногда называют динамическим HTML. Но даже тогда они по сути дела просто переставляют части HTML-содержимого так, чтобы в целом все выглядело приятнее. Макросы, использующиеся в продуктах вроде Microsoft Word, такие же легкие.

    В приложениях Mozilla средней сложности скрипты используются уже систематически. Другие похожие среды разработки этого класса - инструменты четвертого поколения, например PowerBuilder от Sybase или SQL*Forms от Oracle. В таких случаях скрипты несут ответственность за большую часть основной функциональности программного обеспечения. Использование языка Python в сервере приложений Zope - пример такого ПО с открытыми исходными текстами.

    Очень сложные приложения Mozilla вроде среды разработки Komodo от ActiveState содержат столько скриптов, что они перевешивают базовую функциональность браузера платформы. В таких случаях JavaScript можно сравнить с другими самостоятельными средами языков скриптов вроде Visual Basic, Perl и Tcl/Tk или, возможно, elisp в Emacs. При таком тщательно продуманном программировании скрипты управляют платформой Mozillla от начала и до конца.

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

    На рисунке в начале этой главы показаны те части Mozilla, которые наиболее важны для поддержки скриптов. Из этого рисунка видно, что JavaScript равноудален и от пользователя, и от операционной системы. Это так потому, что JavaScript и его скрипты - встроенные технологии, скрытые внутри другого программного обеспечения. Скрипты редко управляют Mozilla извне. Две самые важные части, XPConnect и DOM, находятся глубоко внутри платформы Mozilla. Они предоставляют большое число программных интерфейсов (API, Application Programming Interfaces), доступных всем JavaScript-скриптам. Разумное использование этих интерфейсов из скриптов - основная задача проектирования при разработке приложений на основе Mozilla. Имея возможность манипулировать этими интерфейсами, программист получает возможность писать более простой скрипт и не использовать сложные языки вроде C и C++.

    Простейший вариант применения JavaScript требует XML и тега <script>. И для XUL-, и для HTML-документов следующая строчка изменит слова в строке заголовка окна документа:

    <script> window.title = "Scripted Title"; </script>
    

    Скрипты, подобные этому, могут менять любую часть отображаемого окна Mozilla, в том числе и любую часть его содержимого. Такие скрипты также неявно взаимодействуют и с внешним миром, в том числе с Internet. Само по себе содержимое тега <script> очень важно и требует специальной обработки, как и CSS-код внутри тега <style>.

    Платформа Mozilla сама частично написана с использованием скриптов javaScript. Такие скрипты часто можно найти в chrome, хотя есть и несколько исключений, например файлы настроек не находятся в chrome. Так как chrome спроектирован для хранения? приложений Mozilla, неудивительно, что там находится так много скриптов.

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

    5.1. Роль JavaScript как языка

    JavaScript - член семьи языков с C-подобным синтаксисом. Среди самых известных его "родственников" C и ANSI C, Objective-C, C++, Java, JavaScript, PHP, C# и awk.

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

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

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

    Так как JavaScript - довольно ограниченный? язык, он во многом зависит от другого программного обеспечения (которое часто называют базовым). В этом проявляется сходство этого языка с C, который тоже мало что может без помощи своих библиотек stdio или их эквивалентов. Платформа Mozilla в виде набора библиотек и исполняемых файлов предоставляет базу для интерпретатора JavaScript.

    Так как базовое ПО обычно достаточно велико, большая часть времени, затраченная на написание JavaScript-скриптов, уходит на то, чтобы выяснить, что может предложить базовое ПО. В Mozilla JavaScript играет примерно ту же роль, что и Visual Basic for Applications (VBA) в Microsoft Word и Microsoft Excel. С таким способом работы с языком знакомы и web-разработчики.

    5.2. Стандарты, браузеры и <script>

    Mozilla поддерживает стандарт ECMA-262, а точнее ECMAScript версии 1, редакция 3. Этот стандарт также называется ISO-16262. ISO - Международная организация по стандартизации (хотя по-английски это и пишется как "international standards organization", ISO - не аббревиатура). ECMA - Европейская ассоциация производителей ЭВМ. Их адреса в Internet - www.iso.org и www.ecma.ch соответственно. Стандарты ECMAScript в формате PDF можно получить бесплатно. С некоторыми оговорками этот стандарт можно использовать как настольный справочник по языку. Существует и два других стандарта ECMAScript, но Mozilla не поддерживает ни один из них.

    ECMA-327 "ECMAScript Compact Profile" - почти идентичная версия ECMAScript, предназначенная для использования во встроенных системах. В ней отсутствуют некоторые функции, которые считаются достаточно сложными для реализации в таких устройствах. Этот стандарт можно рассматривать как попытку создать конкурента языку WAPScript, а также всем остальным языкам, предназначенным для использования во встроенных системах.

    ECMAScript - это официальное название языка JavaScript, так как товарным знаком Java владеет Sun Microsystems. Защита товарного знака включает в себя запрет на использование другими компаниями слов, производных от текущего знака или расширяющих его. Давным-давно, еще до официального появления JavaScript, кодовым названием этого языка было LiveScrpt. Реализация третьей редакции ECMA-262 в Mozilla называется JS 1.5, имя соответствующего проекта - SpiderMonkey. Это реализация интерпретатора в виде C-библиотеки.

    SpiderMonkey также поддерживает работу с более ранними версиями JavaScript, в том числе несколько необычную версию 1.2. Эта версия содержала множество новых функций, из которых многие так и не приобрели популярность. При необходимости можно включить поддержку более ранних версий, следуя подходу первых браузеров Netscape. В XUL- приложениях всегда следует использовать последнюю доступную версию языка. Для других языков разметки, например, HTML, можно выбирать любую версию. Тег <script> поддерживается и HTML, и XUL и позволяет выбирать версию языка.

    Вот правильный способ включать JavaScript-код в XML-документ:

    <script type="application/x-javascript" src="code.js/>
    

    Использование следующего варианта не приветствуется, поэтому лучше избегать его, хотя это тоже будет работать:

    <script type="text/javascript" src="code.js/>
    

    Для выбора конкретной версии JavaScript можно делать так:

    <script type="JavaScript1.2" src="code.js/>
    

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

    <script src="code.js/>
    

    Для нового приложения Mozilla идеален синтаксис первого примера. Последний вариант - полезная альтернатива, но ради аккуратности и безупречности следует всегда добавлять атрибут type. У всех этих примеров кодировка по умолчанию - encoding="UTF-8".

    В главе 2, "Проектирование с XUL", в разделе "Хороший стиль кодирования на XUL" объясняется, почему JavaScript-код всегда должен храниться вне XML-документа в отдельном файле. В разделе "Использование XPCOM-компонентов" этой главы будет показано, как включать один JavaScript-файл в другой.

    Организация Mozilla предоставляет и второй интерпретатор JavaScript, написанный не на C, а на Java. Эта версия интерпретатора называется Rhino и не используется и не собирается вместе с платформой Mozilla; тем не менее, она доступна для загрузки. Она также совместима со стандартом ECMA-262 версии 1, редакция 3.

    5.3. ECMAScript, редакция 3

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

    5.3.1. Синтаксис

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

    JavaScript-скрипты Mozilla хранятся как обычные текстовые файлы. Содержимое этих файлов должно быть в кодировке UTF-8. Это значит, что символы из таблицы ASCII хранятся каждый в одном байте. Этого достаточно, если предпочтительный язык - английский, так как никакая особая подготовка не нужна. Можно просто пользоваться обычным текстовым редактором.

    В некоторых системах задействуются и неиспользуемые значения ASCII от 128 до 255, например, для разных символов языков Западной Европы é в слове résumé. Раньше эта традиция была удобным способом создавать текст на других языках. Но это не соответствует правилам использования UTF-8, которые требуют двух или более байтов для всех символов не из таблицы ASCII. Вставка таких 8-битных европейских символов в скрипты будет безуспешной. Символ é правильно кодируется в UTF-8 как "Ãc", по крайней мере, это то, что показывает простенький текстовый редактор, не знающий о UTF-8.

    Но даже у корректно закодированных многобайтовых символов UTF-8 ограниченное применение. Они могут появляться только внутри строковых литералов JavaScript, в комментариях и иногда в именах переменных. Но в общем рекомендуется придерживаться в коде программы ASCII-символов; другие символы следует использовать только для данных. Для европейцев использование для имен переменных символов таблицы Latin1 в целом безопасно, если эти символы корректно закодированы в UTF-8. Чтобы узнать подробнее о поддержке Unicode, см. раздел 7.6 стандарта ECMA-262, редакция 3, или раздел 5.16 стандарта Unicode 3.0.

    В строках по-прежнему можно работать с любыми Unicode-символами. Подробнее об этом рассказано в разделе "Типы данных".

    5.3.1.1. Визуальное форматирование

    JavaScript - язык, в котором визуальное форматирование большой роли не играет, как и XML, и C, например. Одна инструкция не обязана находиться на одной строке. В число распознаваемых пробельных символов входят собственно пробел и табуляция; в число распознаваемых символов конца строки входят символ перевода строки (0A в шестнадцатеричном представлении) и символ возврата каретки (0D в шестнадцатеричном представлении). Напомним, что в Windows, Mac OS 9 и UNIX разные представления о том, каким символом должна завершаться строка. Это не влияет на интерпретацию скриптов, но на некоторых системах может заметно усложнить правку кода.

    Комментарии в JavaScript пишутся в C-стиле. Поддерживаются как однострочные комментарии:

    // Однострочный комментарий
    

    так и многострочные:

    /* комментарий на
       несколько строк */
    

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

    5.3.1.2. Инструкции

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

    x = 5;
    x = 5
    x = 5 // несмотря на комментарий, эта инструкция эквивалентна предыдущей
    

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

    Скриптам не нужна процедура main() или что-то подобное ей. Как и в Perl, инструкции могут появляться вне функций и объектов начиная с первой строки. В JavaScriptтакже допустима инструкция, не делающая ничего:

    ;
    

    JavaScript поддерживает составные инструкции, оформляющиеся с помощью фигурных скобок { и }, но они отличаются от своих аналогов в C (см. раздел "Определение области видимости"). Составные инструкции сами по себе в JavaScript не так уж полезны, хотя и поддерживаются:

    { x = 5; y= 5; }
    

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

    5.3.1.3. Типы данных

    В JavaScript поддерживаются следующие собственные типы данных:

    Undefined Null Boolean Number String Object
    

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

    В JavaScript типы ассоциируются с элементами данных, а не со структурами, хранящими эти данные (например, переменными). Можно привести очень старую аналогию между переменными в программе и коробками для обуви. По этой аналогии элемент данных соответствует паре ботинок, а структура, хранящая ее (переменная) - обувной коробке. В JavaScript информация о типе связывается с ботинками, вы не найдете ее в коробке. Переменные, хранящие данные типа Boolean, Number или String, подразумевают обувную коробку, в которой находится единственная пара ботинок определенного типа. Тип Object подразумевает коробку, в которой может быть много пар, связанных вместе. Тип Null подразумевает пустую коробку, а тип Undefined используется для коробки с неопределенным содержимым.

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

    "undefined" "boolean" "number" "string" "object" "function"
    

    При этом если тип данных - Null, возвращается "object". Давайте по очереди рассмотрим все эти типы.

    Тип данных Undefined допускает только одно значение (неопределенное), но литеральная константа для него отсутствует. У глобального объекта (см. ниже) есть единственное свойство с именем undefined, содержащим это значение. При необходимости это значение можно генерировать с помощью оператора void или с помощью undefined:

    x = void 0;
    X = undefined;
    

    Тип Null допускает только одно значение: пустое. Существует литерал null, который может использоваться для опустошения переменной или в сравнениях:

    x = null;
    

    Тип Boolean допускает два значения: истина и ложь. Литералами для этих значений будут true и false соответственно. false не эквивалентен 0, как в C, но преобразование между этими двумя значениями настолько незаметно, что мелкие различия обычно можно не учитывать.

    x = true;
    

    Тип Number позволяет хранить 64-битные двойной точности числа с плавающей запятой в соответствии со стандартом IEEE 754. Этот стандарт не распространяется свободно, но почти идентичный ему черновик можно найти по адресу http://www.validlab.com/754R/.

    Числа с плавающей запятой - попытка представить действительные числа, но это представление неточное. Если математические операции не вводят дальнейших погрешностей, такое представление точно для чисел до 15 разрядов. Стандарт IEEE 754 допускает значения "не число" (Not-a- Number, возможный результат от деления на нуль или взятия арксинуса от 2) и бесконечные значения (появляющиеся, возможно, при переполнении). Чтобы проверить, не является ли ваше число таким особым значением, можно воспользоваться методами isNaN() и isFinite() соответственно. В JavaScript для этих значений литералов нет, но у глобального объекта есть свойства NaN и Infinity, кроме того, у объекта Math имеется несколько других полезных свойств:

    POSITIVE_INFINITY NEGATIVE_INFINITY NaN MAX_VALUE MIN_VALUE
    

    Эти свойства можно использовать в сравнениях. Для литералов чисел с плавающей запятой поддерживается экспоненциальное представление до примерно +/-10AAAAAAAA:

    x = -3.141592654; y = 1.0e+23; z = 234.555555E-100;
    

    Числа также можно записывать в шестнадцатеричном виде, указывая префикс 0x или 0X, за которым должны следовать цифры от 0 до 9 и буквы от A до F в любом регистре.

    x = 0xFEFF;
    

    Метод сравнивания нечисловых значений (NaN) в JavaScript соответствует рекомендациям IEEE 754, но уникальные идентификаторы для различных нечисловых значений IEEE 754 в языке ECMAScript не предусмотрены.

    Так как числа с плавающей запятой неточны и часто вызывают ошибки, это не лучший выбор для счетчиков и индексов. Для таких общих задач оптимально использовать целые числа. В интерпретаторе JavaScript Mozilla данные типа Number на самом деле хранятся как 31-битные знаковые целые, пока не появится действительная необходимость в точности нецелых чисел. Итог таков: если избегать деления и пользоваться числами меньше 46000 (квадратный корень из 2AAAA), почти все простые вычисления в JavaScript будут выполняться с использованием точной целочисленной арифметики, и можно будет избежать погрешностей нецелых чисел. Эти целые числа также достаточно велики, чтобы хранить любое значение Unicode и любое значение цвета CSS 2 в формате RGB (Red-Green-Blue, красный-зеленый-синий).

    Но в некоторых ситуациях значение может на самом деле храниться как нецелое число. Вот несколько примеров: если это число с десятичной точкой; если в результате деления получается остаток; если вызывается функция, результат которой - действительное число (например, sin()); или если в результате вычислений получается число, большее 2AAAA). Во всех остальных случаях данные типа Number хранятся как целые.

    Если число преобразовывается из целого в нецелое, погрешности, связанные с новым представлением, могут проявиться не сразу. Стандарт IEEE 754 предусматривает 51-битную точность представления нецелых чисел, чего обычно достаточно, если только не требуются интенсивные и многократные вычисления.

    Тип String представляет последовательность символов Unicode, хранящихся в кодировке UTF-16 (то есть по два байта на каждый символ). Каждая строка неизменяема, как и в Java; сходу выполнять операции над строками, как в C, нельзя. Строковые литералы помещаются между парой одиночных или двойных кавычек. Для распространенных непечатаемых символов может использоваться специальное представление. Оно появилась под влиянием нотации для специальных символов в C-строках. Вот ее версия для JavaScript:

    \b \t \n \v \f \r \" \' \\ and \x и \u
    

    Выше представлены соответственно символы забоя, табуляции, перевода строки, вертикальной табуляции, перевода страницы, возврата каретки, двойной кавычки, одиночной кавычки, обратной наклонной черты и начальные комбинации для шестнадцатеричного и Unicode-представления символов. При использовании шестнадцатеричного представления числа за указанной последовательностью должны идти две шестнадцатеричные цифры, которые соответствуют позиционному коду (индексу) символа Unicode, находящемуся в диапазоне от 0 до 255. В этот диапазон включаются символы ASCII и символ неразрывного пробела (0xA0). Сюда также входят символы ISO 8859 (Latin 1 - европейские). При использовании Unicode- представления символа за указанной последовательностью должны идти четыре шестнадцатеричные цифры, которые соответствуют любому символу Unicode, который только можно вообразить. Вот простой пример:

    str = "hello, world\n";
    

    Тип Object будет рассматриваться в отдельном разделе, посвященном только ему.

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

    5.3.1.4. Переменные

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

    my_variable x counter5 interCapName not$common _secret
    

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

    Переменные не определены, если они не объявлены с ключевым словом var. Если они используются, не будучи объявленными, возникает синтаксическая ошибка. Если они объявлены, но им ничего не присваивается, они остаются неопределенными. Таким образом, переменные определяются при объявлении или при первом присваивании - это зависит от того, что случится раньше. В JavaScript переменные необязательно инициализировать константными выражениями, как в C. Для первого присваивания может использоваться любое выражение, а переменная может быть объявлена в любом месте скрипта.

    var x;
    var y = 2, z = 3.45;
    var product = x * y * z;
    

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

    5.3.1.5. Массивы

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

    var arr1 = new Array();             // массив нулевой длины
    var arr2 = new Array(5);            // массив из 5 элементов
    var arr3 = new Array(11,12,13);     // массив из 3 элементов
    var arr4 = new Array(2,"red",true); // массив из 3 элементов
    

    Все элементы массива не определены, если только их содержимое не задано при создании массива. Каждый элемент массива может хранить данные любого типа. Массив также можно создать с помощью литерала, заключенного в квадратные скобки, [ ]. Следующие примеры совпадают с предыдущими и часто предпочтительны, так как метод Array() выглядит не очень красиво и часто вносит путаницу:

    var arr1 = [];              // массив нулевой длины
    var arr2 = [, , , , ,];     // массив из 5 элементов
    var arr3 = [11,12,13];      // массив из 3 элементов
    var arr4 = [2,"red",true];  // массив из 3 элементов
    

    Литералы массивов могут быть вложенными, так что элементы массивов могут сами быть массивами:

    var arr5 = [ 6, ["red","blue"], 8, [], 10];
    

    На элементы массивов можно ссылаться по их индексам, которые начинаются с 0. Свойство массива length - целое число, большее индекса самого последнего элемента в массиве на один. Оно не равно числу элементов массива и обновляется автоматически:

    a[0];           // первый элемент массива a
    b[2];           // третий элемент массива b
    c.length;       // на один больше самого большого индекса в c
    c[c.length-1];  // последний элемент массива c
    d[1][4];        // см. ниже
    

    В последней строке предыдущего примера используется массив d, чей второй элемент, d[1] сам является массивом. Следовательно, d[1][4] - пятый элемент массива d[1].

    Размер массивов не фиксирован. Свойство length можно менять, или присваивая ему какое-то значение, или определяя элемент с большим индексом.

    arr1 = new Array(3);    // length - 3
    arr1.length = 5;        // теперь length - 5;
    arr1[8] = 42;           // теперь length - 9;
    

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

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

    obj.prop_name == obj["prop_name"]   // корректно и всегда истина
    obj[1] != obj.1                     // некорректный синтаксис
    

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

    obj["A % overhead"] = 20;
    

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

    obj[12.35] == obj["12.35"];
    

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

    5.3.1.6. Выражения

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

    Таблица 5.1. Операторы JavaScript

    ИмяБинарныйПриоритетСимвол
    Принудительно задать высший приоритетУнарный0()
    Литерал массиваУнарный0[]
    Литерал объектаУнарный0{}
    Вызов функцииУнарный0()
    Свойство объекта1.
    Элемент массиваБинарный1[]
    Литерал объектаУнарный1{}
    Создание объектаУнарный2new
    Удалить ссылку на свойствоУнарный3delete
    Преобразовать в неопределенноеУнарный3void
    Получить строку, соответствующую типуУнарный3typeof
    Пре- и постинкрементУнарный3++
    Пре- и постдекрементУнарный3--
    Тот же знакУнарный3+
    Противоположный знакУнарный3-
    32-битное побитовое отрицаниеУнарный3~
    Логическое отрицаниеУнарный3!
    Умножение4*
    Деление4/
    Остаток от деления4%
    Сложение, конкатенация5+
    Вычитание5-
    32-битный сдвиг влево6<<
    32-битный знаковый сдвиг вправо6>>
    32-битный беззнаковый сдвиг вправо6>>>
    Совпадает с данным типом7instanceof
    Совпадает со свойством объекта7in
    Порядковые сравнения7< > <= >=
    Равенство7== !=
    Строгое равенство7=== !==
    32-битное побитовое И8&
    32-битное побитовое исключающее ИЛИ9^
    32-битное побитовое ИЛИ10|
    Логическое И11&&
    Логическое ИЛИ12||
    Условное выражениеТернарный13?:
    Простое присваивание14=
    Составное присваивание14*= /= %= += -= <<= >>= >>>= &= ^= |=
    Разделитель элементов списка15,

    Приоритет 0 - самый высокий. Для операторов с одинаковым приоритетом JavaScript грубо следует принятым в C соглашениям о вычислениях слева направо и справа налево. Он также поддерживает упрощенное вычисление булевых выражений, то есть если выражение состоит из нескольких операций && и ||, они вычисляются слева направо только до тех пор, пока не будет понятно, что окончательный результат уже не изменится, а не до тех пор, пока не будет вычислено последнее условие.

    Одна область, в которой логика работы с булевыми выражениями JavaScript ближе к Perl, чем к C - многозначная семантика. По этому соглашению выражения с && и || играют роль управляющих конструкций наподобие ?:, а не обычных булевых выражений. Поэтому в инструкции

    var x = flag && y;
    

    переменная x принимает значение y, если значение flag - true, иначе она принимает значение false. В C переменная x как значение принимала бы результат вычисления булевого выражения "flag и y".

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

    5.3.1.7. Управление

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

    if (выражение) инструкция
    if (выражение) инструкция else инструкция
    while (выражение) инструкция
    do инструкция while (выражение)
    for (выражение; выражение; выражение) инструкция
    switch (выражение) {
    case выражение: инструкция; break;
    case выражение: инструкция; break; // столько раз, сколько нужно
    default: инструкция; break;
    }
    

    Аргументом switch() может быть что угодно, не только переменная. Селекторами case также могут быть не только литералы. Следующие две if-конструкции эквивалентны:

    if (a) инструкция else if (b) инструкция else инструкция
    if (a) инструкция else {if (b) инструкция else инструкция}
    

    Как и во многих языках с C-подобным синтаксисом, стоит опасаться ловушки с if-конструкциями, когда else-часть относится к последнему if независимо от расстановки отступов; эта ловушка во втором случае исчезает благодаря использованию явного синтаксиса.

    Для инструкции for есть вариация, которая позволяет проходить по свойствам объекта JavaScript. При этом просматриваются все свойства, не являющиеся DontEnum (см. раздел 8.6.1 стандарта ECMA-262):

    for ( имя-переменной in объект ) инструкция
    

    В JavaScript нет инструкции goto. В нем есть метки, чьи имена соответствуют именам переменных, но они находятся в отдельном пространстве имен. continue завершает текущую итерацию цикла; break прерывает выполнение цикла или выходит из инструкции switch. Метки можно использовать для прерывания нескольких вложенных друг в друга циклов:

    mylabel: инструкция;
    break;
    break label;
    continue;
    continue label;
    

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

    try { инструкция; }
    catch (переменная) { инструкция; }
    finally { инструкция; }
    

    Блоков catch может быть несколько. Блок finally необязателен. Для генерации исключения используется throw - или внутри блока try или где угодно:

    throw выражение;
    

    Созданное исключение может сравниваться с данными любого типа, начиная от обычного числа и заканчивая сложным специально созданным объектом. Чтобы имитировать исключения, генерируемые XPConnect-частью платформы, нужно всегда генерировать 32-битное целое, желательно одно из значений объекта Components.results.

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

    Инструкция with рассматривается в разделе "Область видимости".

    5.3.1.8. Функции

    JavaScript поддерживает функции. Функции нетипизированы и могут работать с переменным числом аргументов, как, например, printf() в C. Функции могут не иметь имени, тогда они анонимны, см. ниже. В листинге 5.1 показана типичная функция.

    function sum(x, y) {
      if (arguments.length != 2) {
        return void 0;
      }
      return x + y;
    }
    var a = sum(2,3);		// a = 5
    var b = sum(1,2,3); 		// b = undefined
    var c = sum("red","blue"); 	// c = "redblue"
    var d = sum(5, d); 		// d = 5 + undefined = NaN
    var e = sum; 			// e теперь функция
    var f = e(3,4); 		// f = 7
    
    Листинг 5.1. Синтаксис обычной функции JavaScript

    Объект arguments аналогичен объекту Array за исключением того, что он статичен: при добавлении к нему элементов свойство length не изменится. Этот объект содержит все аргументы, переданные функции. Функции также могут быть анонимными:

    var plus = function (x,y) { return x + y; }
    var a = plus(2,3);
    

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

    var five = (function sum(a,b){return a+b;})(2,3);
    

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

    5.3.1.9. Регулярные выражения

    JavaScript поддерживает регулярные выражения Perl5 с некоторыми мало заметными отличиями. Эти неявные отличия существуют, так как синтаксис регулярных выражений очень чувствителен к малейшим неточностям, находится в постоянном развитии и еще до конца не устоялся. В UNIX-системах есть такие варианты регулярных выражений: для работы с файлами, обычный и расширенный. Perl и JavaScript поддерживают расширенные регулярные выражения, которые очень грубо соответствуют egrep(1) или параметру "Wildcards" окна поиска в Microsoft Word. Написанное в справочной странице (man(1)) Perl, perlre, понять легче, чем текст определения ECMAScript, но ненамного. Поищите какое-нибудь руководство в Internet.

    Все операции над регулярными выражениями в JavaScript - методы объекта String или RegExp; они не существуют самостоятельно, как оператор m// в Perl:

    match(re)               // "red".match(/e/) == ["e"];
    replace(re,string)      // "red".replace(/e/,"o") == "rod";
    replace(re,function)    // "red".replace(/e/,myfn);
    search(re)              // "red".search(/e/) == 1;
    split(re)               // "red".split(/e/) == ["r","d"];
    

    replace() возвращает строку; search() - число; split() и match() возвращают массивы строк.

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

    /выражение/флаги
    

    Здесь выражение - любое запутанное регулярное выражение; флаги - ноль или более число g (заменять везде), i (не обращать внимания на регистр) и m (в многострочных целях рассматривать каждую строку отдельно).

    5.3.2. Объекты

    В языке JavaScript есть объекты, но в версии 1.5 его еще нельзя назвать полностью объектно-ориентированным. Описание реализации многих концепций объектно-ориентированных систем в JavaScript приведено в таблице 5.2.

    Таблица 5.2. Поддержка объектов в JavaScript

    Концепция объектных системПоддержка в синтаксисеЛегкость в использовании
    АгрегацияХорошаяЛегко
    ВключениеХорошаяЛегко
    ДелегацияХорошаяНесложно
    ИнкапсуляцияНедостаточнаяНесложно
    НаследованиеДостаточнаяСложно
    Скрытие данныхНедостаточнаяНесложно
    ИнтерфейсыНетСложно
    Позднее связываниеХорошаяЛегко
    Основанность на объектахХорошаяЛегко
    Ориентация на объектыНетСложно
    Множественное наследованиеНетСложно
    Определение типов во время исполненияХорошаяЛегко
    ШаблоныНетСложно

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

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

    5.3.2.1. Простые объекты

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

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

    Чтобы создать собственный объект, можно воспользоваться или оператором new, или литералом объекта, как показано в листинге 5.2.

    // явное создание
    var obj = new Object;
    obj.foreground = "red";
    obj.background = "blue";
    
    // создание с помощью литерала
    var obj = { foreground:"red", background:"blue" }
    
    Листинг 5.2. Примеры создания объектов в JavaScript

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

    function start_it() { this.run = true; }
    // явное добавление
    var obj = new Object;
    obj.start = start_it;
    obj.stop = function (){ this.run = false; }
    
    // добавление в литерале
    var obj = { start: function (){ this.run = true; },
        stop: function (){ this.run = false; }
    }
    
    // выполнение методов
    obj.start();
    obj.stop();
    
    Листинг 5.3. Примеры создания методов в JavaScript

    this относится к объекту, свойством которого является вызываемая функция.

    Объекты могут содержать и другие объекты:

    var obj = {
      mother:{name:"Jane", age:34},
      father:{name:"John", age:35}
    };
    var my_ma_name = obj.mother.name;
    

    Включение и объединение в JavaScript - одно и то же, так как реального скрытия данных нет. Некоторое скрытие осуществить можно, повозившись со свойствами объектов Function в цепи прототипов. Таким образом можно создать постоянные свойства, которые будут находиться в области видимости только во время запуска функции - на деле получится локальная переменная. Этот неочевидный технический фокус обычно не нужен. К нему можно прибегнуть, только если вы предоставляете библиотеку готовых объектов для их использования кем-нибудь другим и хотите, чтобы ваша библиотека была надежной как скала. См. также раздел "Расширения языка", где рассказано о функциях чтения и задании значений свойств.

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

    function Parents(ma, pa) {
      this.mother = { mother:ma; };
      this.father = { father:pa; };
      this.dog = "Spot";
    }
    var family1 = new Parents("Jane","John");
    var family2 = new Parents("Joanne","Joe");
    
    Листинг 5.4. Примеры создания объектов с помощью функции конструктора

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

    Parents.prototype.lastname = "Smith";
    Parents.prototype.ring = function (){ dial(123456789); };
    

    Тогда при создании family1 и family2 получат помимо трех свойств из тела функции еще и дополнительные свойства из прототипа, то есть всего пять свойств. Так что у них не только будет собака (dog) по кличке Спот (Spot), но и фамилия у них обоих будет Смит (Smith), и номер телефона у них тоже будет один и тот же. В действительности свойства lastname и ring - общие для обоих объектов. Если у одного из них изменится свойство lastname, это новое значение заменит свойство lastname прототипа, и тогда значения данного свойства у объекта и прототипа более не будут общими. Если же изменится свойство lastname прототипа, тогда оно изменится и у всех объектов, для которых это свойство общее. Такие свойства отличаются от свойств, заданных как dog в нашем примере - это свойство уникально для каждого создаваемого объекта.

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

    См. также раздел "Цепочки прототипов" в этой главе.

    5.3.3.2. Объекты базового ПО

    Объекты базового программного обеспечения существуют вне JavaScript в самом базовом ПО, в которое встроен интерпретатор. В Mozilla такие объекты обычно написаны на C++. Некоторый код на C, связанный с интерпретатором JavaScript, находит их по мере надобности, создает простой внутренний интерфейс, напоминающий объект JavaScript, связывает с этим интерфейсом объект базового ПО и, таким образом, делает этот объект доступным для скрипта. Сложности возникают, прежде всего, при поиске объекта, и JavaScript требуется помощь базового ПО (платформы Mozilla). Объект document - пример описанного выше класса объектов.

    Объекты базового ПО для программиста выглядят так же, как и обычные объекты, если только не пытаться превратить их обратно в код с помощью toString(). Так как функции и методы в JavaScript - тоже объекты, эта разница действительна и для них. Например, следующий фрагмент кода пытается получить тело функции alert() (в XUL это метод объекта базового ПО типа ChromeWindow):

    var str = "" + alert;
    

    Результирующая строка, однако, показывает, что у alert() нет исходного текста на JavaScript:

    "\nfunction alert() {\n [native code]\n}"
    
    5.3.2.3. Собственные объекты

    Интерпретатор JavaScript предоставляет набор собственных объектов. Вот их имена (и типы):

    Object Array Boolean Number String Math Date RegExp Function Error
    

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

    var flag = new Boolean();
    

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

    Типы Object и Array уже обсуждались. Для объектов Array предусмотрено свойство length (длина массива), тогда как у объектов Object оно отсутствует.

    Объекты Boolean, Number и String соответствуют базовым типам данных с соответствующими именами. JavaScript свободно и автоматически преобразует данные между базовыми типами и соответствующими объектами, даже в случае литералов. Следующий пример демонстрирует автоматические преобразование литералов в объекты, вызывающие некоторый метод.

    "Test remark".charAt(3); // результат: "t"
    1.2345.toFixed(2); // результат: 1.23
    true.toString(); // результат: "true"
    

    Объект Math предоставляет разные математические функции, например, Math.sin().

    Объект Date хранит даты и использует несколько методов чтения значений свойств. Объекты этого типа поддерживают только западный григорианский календарь, расширенный во времени вперед и назад. Поддерживаются и даты до наступления "эпохи UNIX" (1 января 1970), и значения, не являющиеся 32-битными time_t. Они хранятся с двойной точностью IEEE и достигают 280000 лет вперед и назад. Даты точны до миллисекунд, если часы компьютера достаточно точны. Нулевое значение даты совпадает с началом "эпохи UNIX", так что у всех значений типа time_t - корректные значения Date. Не следует пользоваться методом getYear(), он устарел, лучше вызывать метод getFullYear().

    Объект RegExp хранит шаблон регулярного выражения и флаги. Некоторые методы, относящиеся к регулярным выражениям, доступны и в объекте String.

    Объект Function представляет функции и методы. Синтаксис его конструктора несколько странен. Почти всегда вместо new Function рекомендуется использовать анонимные функции или синтаксис с применением ключевого слова function.

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

    При изучении этих объектов имеет смысл пользоваться стандартом ECMAScript-262 как справочником. Свойства и методы объекта типа X описываются в разделе 15 под заголовком "Properties of the X Prototype Object" и "Properties of X Instances". Это правило работает для всех типов, кроме объектов Math (см. следующий раздел).

    5.3.2.4. Встроенные объекты и похожие эффекты

    При запуске интерпретатора JavaScript некоторые объекты доступны и без написания какого-либо кода. Если это собственные объекты интерпретатора, то они называются встроенными объектами. Объекты базового ПО также могут быть доступными до написания кода. Такая автоматическая инициализация для удобства выполняется всегда. Яркими примерами могут служить объекты Global, Math и document.

    Глобальный объект находится наверху иерархии включения объектов. Это корневой объект в JavaScript. Он не является свойством никакого другого объекта и не может быть создан без отдельной и независимой среды исполнения. В Mozilla объект Window (в HTML) и объекты ChromeWindow (в XUL) - глобальные объекты. Они реализованы так, что обладают свойством window. Это свойство ссылается на глобальный объект (получается цикл). Программисты в своих скриптах пользуются этим объектом window как корневым.

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

    var one = Math.sin(Math.PI/2);
    

    Если документ загружается в окно Mozilla, процесс загрузки автоматически сделает доступными множество дополнительных объектов. Эти объекты знакомы web-программистам по объектной модели документа DOM 0. В HTML они формируют обширную иерархию включения, которая часто используется следующим образом:

    window.document.form3.username.value = "John";
    

    Явное использование префикса window. необязательно. Эквивалентные префиксы - this и self.

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

    5.3.3. Принципы обработки

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

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

    Приоритет операторов указан в таблице 5.1. Правила вычислений слева направо и справа налево примерно такие же, как и в C, C++ и Java.

    5.3.3.2. Передача аргументов

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

    5.3.3.3. Преобразование типов

    JavaScript автоматически преобразовывает данные между всеми простыми типами и объектами Number, String и Boolean. Преобразование типов выполняется так, чтобы выражение могло быть вычислено в любом случае. Каждый объект в JavaScript обладает методами toNumber() и toString(), которые можно задействовать по мере надобности. Приведение типов не требуется; оно выполняется в соответствии с расширенным набором правил стандарта ECMA-262. Суть этих правил можно выразить в следующих двух:

    • Правило 1: Не следует предполагать, что преобразование сработает при превращении строки в число.
    • Правило 2: Не следует пользоваться бинарными операторами над объектами, чьи типы точно не известны.

    Правило 1 существует потому, что содержимое строки может быть некорректным числовым литералом. Тогда JavaScript вернет значение NaN и, в худшем случае, может аварийно завершить работу:

    var str = "123stop45";
    var x = str * 3; // str не число.
    

    Аварийное завершение работы происходит только в случае синтаксических ошибок или ошибок во время исполнения. Чтобы уберечься от таких вещей, необходимо явно использовать функции parseInt() и parseFloat():

    var x = parseInt("123stop45") * 3;
    

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

    5.3.3.4. Область видимости

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

    Первая сторона - традиционное определение области видимости переменных. Оно не отличается от своих аналогов в C и C++, где переменные могут быть локальными для функции или глобальными. В JavaScript в функции у локальных переменных могут быть такие же имена, как и у переменных вне функции. При интерпретации функции используется локальная переменная. При интерпретации инструкций вне функции используется глобальная переменная.

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

    В C и C++ переменные, объявленные в середине области видимости, действительны далее, начиная с этого места. В JavaScript переменные, объявленные в середине функции (или где-нибудь в области глобальной видимости), действительны для всей области видимости. Следующий фрагмент кода, некорректный для C/C++, корректен в JavaScript:

    function f() {
      alert(x); // выдаст "undefined"
      var x = 42;
      alert(x); // выдаст "42";
    }
    f();
    

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

    Инструкция with в JavaScript временно добавляет объекты в цепочку областей видимости. В листинге 5.5 показано, что функция toString() используется несколько раз и каждый раз - для нового объекта. В то же время переменная myflag всегда находится в объекте window, так как ни у одного из этих объектов нет свойства myflag.

    // цепочка областей видимости = window
    var myflag = "Test String";
    var x = toString(); // "[object ChromeWindow]"
    with (document) {
      // цепочка областей видимости = document, window
      x = toString(); // "[object HTMLDocument]"
      x = new Object;
      with (x) 	{
        // цепочка областей видимости = x, document, window
        var y = toString(); // "[object Object]"
        var x = myflag; // window.myflag
      }
    }
    
    Листинг 5.5. Пример работы цепочки областей видимости в JavaScript

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

    5.3.3.5. Стеки и сборка мусора

    В C, C++ и Java для данных используются два варианта размещения в памяти: в стеке или в свободной памяти (или куче). Стек используется автоматически по необходимости, а свободная память используется при любом вызове new или malloc(). В JavaScript нет стека, по крайней мере, нет стека, с которым программист мог бы работать. Память всегда выделяется из кучи. Ни для элементов массивов, ни для аргументов функций не гарантируется последовательное размещение. Стеки используются для реализации передачи аргументов функциям в Mozilla SpiderMonkey, но этот механизм не виден программисту на JavaScript.

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

    5.3.3.6. Вычисления во время выполнения

    JavaScript обладает возможностью интерпретировать новый код во время выполнения. Самый удобный способ сделать это - применить метод eval() глобального объекта. eval() может использоваться для выполнения любого корректного кода JavaScript:

    var x = 5;
    var code = "x = x + 1;";
    eval(code);                 // x = 6
    x = x + 1;                  // x = 7
    

    Другие аналогичные методы не связаны с интерпретацией напрямую. В Mozilla это методы setTimeout() и setInterval(), очень ограниченные parseInt(), parseFloat() и URL javascript:. Более подробно о механизме интерпретации нового кода во время выполнения рассказано в разделе "Использование XPCOM-компонентов".

    5.3.3.7. Замкнутые выражения

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

    function accumulate(x) {
      return function (y) { return x + y };
    }
    var subtotal = accumulate(2);
    var total = subtotal(3); // total == 5
    

    Каким будет возвращаемое значение при вызове анонимной функции? Если бы при завершении области действия функции accumulate() значение аргумента x очищалось, она бы не участвовала в вычислениях при вызове subtotal().

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

    5.3.3.8. Цепочки прототипов

    Возможно, самая сложная особенность JavaScript - система прототипов. Она довольно компактно описана в разделе 4.2.1 третьей редакции стандарта ECMAScript. Ранее упоминалось, что у каждого конструктора есть объект-прототип, который можно использовать для моделирования общих частей создаваемых конструктором объектов.

    На самом деле у каждого объекта в JavaScript, включая те, которые сами выступают как прототипы, есть объект-прототип с именем __proto__. Поэтому следующий код совершенно корректен:

    MyConstructor.prototype.__proto__.value = 5;
    

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

    С помощью прототипов можно реализовывать объектно-ориентированное наследование. Самый просто способ это сделать - заменить прототип новым "базовым классом", то есть новым объектом-прототипом. Как видно из листинга 5.4, это можно сделать так:

    function Family() { };          // Какой-то конструктор.
    Family.prototype = new Parents; // Новый базовый "класс".
    

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

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

    5.4. Расширения языка

    Реализация языка JavaScript в Mozilla обладает несколькими особенностями, дополняющими стандарт ECMA-262.

    5.4.1. Расширения Mozilla SpiderMonkey

    Интерпретатор Mozilla SpiderMonkey толерантно относится к тому, что составляет корректную инструкцию:

    x++ % y++;
    

    Это поведение соответствует стандарту, но другие реализации JavaScript не поддерживают его.

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

    var obj = {
      get foo () { effect1(); return this._real_foo; },
      set foo (val) { effect2(); return this._real_foo = val;},
      _real_foo: "bar"
    }
    var x = obj.foo; // вызывается effect1() и x = "bar"
    obj.foo = "zip"; // вызывается effect2() runs и _real_foo = "zip"
    
    Листинг 5.6. Создание активного свойства в объекте JavaScript

    В этом примере свойство _real_foo хранит действительное значение свойства foo, которое существует только как интерфейс. В терминах стандарта ECMAScript эти особые функции позволяют реализовывать для свойств операции [[Get]] и [[Put]], и это будут такие функции, которые вам нужны.

    Mozilla также предоставляет некоторые функции для управления перечисленными выше методами. Это

    _defineGetter__("имя-свойства", объект-функция);
    _defineSetter__("имя-свойства", объект-функция);
    _lookupGetter__("имя-свойства");
    _lookupSetter__("имя-свойства");
    

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

    Mozilla также поддерживает свойство __parent__, которое влияет на внутреннее свойство [[Scope]] объектов-функций. В JavaScript в Mozilla это свойство также доступно и всем собственным объектам и объектам базового ПО.

    Наконец, Mozilla предоставляет свойство __proto__, с помощью которого можно явно задавать или читать внутреннее свойство [[Prototype]], описанное в стандарте.

    5.4.2. Расширения из ECMA-262, четвертая редакция

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

    Некоторые из предложенных в этом черновике улучшений присутствуют в SpiderMonkey 1.5, но поддержка нового стандарта скоро будет закончена. Начать изучение особенностей четвертой редакции можно на сайте http://www.mozilla.org/js/language/.

    Простое расширение из четвертой редакции - поддержка констант:

    const varname = 5;
    

    В Mozilla есть настройка javascript.options.strict, которой можно задать значение true. Ее также можно найти в настройках, связанных с отладкой, если используется тестовая версия платформы. Это не то же самое, что режим строго соответствия из черновика стандарта; это просто дополнительная проверка корректности кода в соответствии с третьей редакцией стандарта. Разработчикам рекомендуется ею пользоваться.

    Планируется, что SpiderMonkey 2.0 будет соответствовать четвертой редакции стандарта.

    5.4.3. Расширения из соображений защиты

    Платформа Mozilla запускает интерпретируемые скрипты на компьютере пользователя, что в принципе может быть небезопасно. Платформа предоставляет разные функции обеспечения защиты, и некоторые из них относятся к JavaScript. Более подробно о защите рассказано в главе 16, "Объекты XPCOM".

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

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

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

    От ограничений из соображений защиты освобождаются скрипты, хранящиеся в chrome. Самое большое ограничение - свойство Components объекта Window или ChromeWindow полностью доступно. Это свойство - дорожка к XPCOM-компонентам Mozilla. Ни один из этих компонентов не доступен извне chrome. Некоторые из этих объектов лежат в основе какой-либо функциональности, доступ к которой осуществляется другим способом. Пример: объект, реализующий объект Form, доступный в HTML- документах.

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

    5.5. Интерфейсы Mozilla, доступные в скриптах

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

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

    Если основные функции ввода/вывода предоставляются, скрипт может включать другие скрипты откуда-нибудь еще. Теоретически такие скрипты могут использоваться для создания очень больших программ. Это похоже на модульную среду Perl, но в JavaScript такая практика не очень распространена. В JavaScript ожидается, что основная часть функциональности программы будет реализована в объектах базового ПО. Поэтому изучать синтаксис и семантику этого языка очень просто, по сравнению с изучением обширных библиотек объектов.

    Объекты базового ПО - концепция JavaScript. Хотя такие вещи появляются внутри скрипта как объекты, службам базового ПО, скрывающимся за ними, совсем необязательно быть объектами. В случае Mozilla функциональность, доступная в скриптах, обеспечивается платформой, которая состоит из набора интерфейсов. Многие из них неотличимы от объектов, и многие из них на самом деле реализованы как объекты C++. Они также могут быть реализованы в чистом JavaScript. Такие вещи называются скриптами, просто чтобы обратить внимание на то, что они, формально, делают функции платформы доступными. Концепция интерфейса в Mozilla следует концепциям интерфейсов в Java и Microsoft COM - некоторый набор (необязательно полный) функций и свойств, предоставляемых объектом.

    5.5.1. Происхождение интерфейсов

    Все объекты базового ПО с точки зрения скрипта одинаковы, но они приходят из разных мест. Вот некоторые из технологий, который вносят свой вклад в создание таких объектов:

    • Стандарты консорциума World Wide Web. Стандарты W3C DOM описывают обширные программные интерфейсы к XML-документам. Эти интерфейсы дают скриптам почти полный контроль над содержимым документа.
    • Объектные модели приложения и браузера (AOM и BOM). Кроме объектов DOM, платформа Mozilla предоставляет и другие объекты, доступные, когда HTML- или XUL-документ отображается в окне. Некоторые из них поддерживаются другими браузерами; некоторые нет.
    • XPConnect и XPCOM. Эта система уникальна для платформы Mozilla и предоставляет доступ ко всем компонентам, ее составляющим. Эти компоненты выполняют большую часть работы приложения. Компоненты также представляют собой библиотеку или набор инструментов, полезных при решении стандартных задач программирования, например, доступа к сокетам.
    • XBL-связки. Язык разметки XBL, описываемый в главе 15, "XBL-связки", комбинирует XML-документ, JavaScript и правила стилей, чтобы создать для XML-документа доступные из скриптов интерфейсы. Хотя эти интерфейсы и определяются в файлах вне платформы Mozilla, именно платформа несет ответственность за их разбор и связывание с объектами из скриптов.

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

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

    5.5.2. Стандарты W3C DOM

    Версии стандартов, описывающих объектную модель документа, начинаются с 0 (нуля) и созданы так, чтобы не зависеть ни от какого конкретного программного обеспечения. Mozilla поддерживает полностью DOM 0, DOM 1, большую часть DOM 2, кое-что из DOM 3 и множество нестандартных расширений. Другие браузеры, например, Internet Explorer 6 и Opera, поддерживают, по крайней мере, DOM 0 и DOM 1. Существует множество программных библиотек, с помощью которых в приложение, которое должно обрабатывать XML, можно добавить поддержку DOM 1.

    Если в Mozilla реализуется какая-то часть DOM, реализация этой функциональности точно такая, как предписано стандартом. Это очень просто.

    Версии этих стандартов в формате PDF (Portable Document Format) во время программирования можно использовать как справочники. Все версии, начиная с первой, доступны на сайте www.w3.org. Чтобы сохранить еще не окончательные черновики, которые обычно доступны только в формате XHTML, в Mozilla, следует сохранить их в файл как "Web-страница, полностью". Отображение таких XHTML-файлов, сохраненных локально, при отсутствии подключения к Internet может быть медленным. В таком случае следует сразу же выбрать File | Work Offline.

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

    Если вы понимаете синтаксис IDL в таких стандартах, то это идеально для чтения XPIDL-файлов, описывающих XPCOM-компоненты Mozilla. Чуть позже мы рассмотрим эти компоненты более подробно.

    5.5.2.1. DOM 0

    Mozilla поддерживает DOM 0 только в HTML-документах. Многие доступные в XUL объекты были созданы как аналоги каких-то объектов DOM 0, так что этот стандарт можно использовать как руководство, поясняющее, чего можно ожидать в XUL.

    У DOM версии 0 нет соответствующего стандарта W3C, и он представляет собой раннюю попытку поддержки JavaScript только для HTML. Содержание DOM 0 примерно эквивалентно доступным для скриптов функциям в третьих версиях браузеров. Лучшая документация по этим функциям находится на сайте Netscape's DevEdge: http://devedge.netscape.com, где по-прежнему хранятся довольно старые документы. Руководства к ранним версиям Navigator можно найти в разделе "Archived information".

    DOM 0 относится к HTML, а не к XML или XUL. Как упоминалось раньше, он предоставляет стандарт готовых объектов, которые доступны автору скрипта после загрузки HTML-документа. Самый известный пример - объект Image, который часто использовался в DHTML, чтобы менять изображение при наведении на него указателя мыши. Это описывается в любой книге о web-дизайне для читателей со средним уровнем подготовки.

    Объекты DOM 0 часто точно соответствуют именам HTML-тегов; например, у тега <input> есть объект InputElement или FormElement. В XUL при поиске объекта DOM 0 для какого-то тега пользуйтесь как отправной точкой ближайшим к вашему тегу HTML-тегом.

    5.5.2.2. DOM 1

    Mozilla полностью поддерживает DOM 1.

    Среди множества предоставляемых стандартом DOM 1 функций в 90% случаев для написания скрипта нужны только интерфейсы Node, NodeList, Element и Document. Важные свойства и методы этих интерфейсов перечислены в таблице 5.3.

    Таблица 5.3. Самые полезные функции стандарта DOM 1

    Самые полезные функции стандарта DOM 1Интерфейс DOMОписание
    parentNodeNodeРодительский тег по отношению к текущему
    childNodesNodeВсе теги-потомки текущего тега в виде NodeList
    firstChildNodeПервый тег-потомок текущего тега
    nextSiblingNodeСледующий тег-потомок родительского тега по отношению к текущему
    Node insertBefore(aNode, existingNode)NodeДобавить тег или текст перед указанным тегом-потомком или текстом
    Node removeChild(existingNode)NodeУдалить тег или текст из непосредственного потомка текущего тега
    Node appendChild(aNode)NodeДобавить тег или текст в конец списка потомков этого тега
    String getAttribute(attString)ElementВернуть значение существующего атрибута или "" для текущего тега
    void setAttribute(attString, value)ElementДобавить текущему тегу атрибут с определенным значением
    void removeAttribute(attString)ElementУдалить из тега данный атрибут, если он существует
    Boolean hasAttribute(attString)ElementОпределить, существует ли указанный атрибут
    Element createElement(tagString)DocumentСоздать тег; этот тег будет существовать отдельно от текущего документа
    Node createTextNode(value)DocumentСоздать текст; этот текст будет существовать отдельно от текущего документа
    Element getElementById(idString)DocumentВернуть тег с указанным идентификатором
    NodeList getElementsByTagName(tagString)Element, DocumentВернуть все теги с указанным именем тега
    Node item(i)NodeListВернуть i-й элемент списка
    lengthNodeListВернуть число элементов в списке

    Так как интерфейсы Element и Document также являются Nodes, все свойства и методы Node относятся и к этим интерфейсам.

    Стандарт DOM 1 игнорирует опыт DOM 0 и начинает с чистого листа. Этот стандарт состоит из двух частей.

    Первая часть очень общая и относится ко всем XML-документам, в том числе MathML, SVG и XUL. Она также относится к XHTML и HTML. Она создает из текста документа древоподобную структуру, очень похожую на то, что мы видим в HTML-редакторе со включенной функцией автоматического выравнивания тегов. Mozilla отображает такую структуру, если загрузить XML-документ, чей тип приложения она не может обработать. Эта структура - иерархия включений.

    Все в первой части DOM 1 объекты Node. Документы, теги, атрибуты и содержимое тегов - объекты, являющиеся подтипами Node. Дерево документов - дерево таких объектов, в корне которого находится объект Document. Это дерево не имеет сведений о конкретных тегах (например, о XUL-тегах). Поэтому здесь нет объекта <button>. Есть только обобщенный объект, использующийся для всех тегов. И для XML, и для HTML, и для XUL обработка одна и та же.

    Существует два способа перемещаться по этому дереву. Первый задействует систему запросов. Можно пользоваться эквивалентом document.all() от Microsoft из DOM 1 или расширением Mozilla:

    document.getElementsByTagName("button"); // любой XML
    document.getElementsByAttribute("class", "*"); // только XUL
    

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

    document.firstChild().firstChild().getAttribute("type");
    

    В первой части стандарта DOM 1 также предоставляется полный набор функций для вставки, изменения и удаления XML-содержимого, но это должно выполняться путем создания объектов, а не вставки нужных кусков в XML-данные. Если изменение содержимого важно, создание объектов может оказаться длительным и сложным процессом. В Mozilla есть единственное расширение, позволяющее решать эти задачи быстрее - свойство innerHTML. Это улучшение создано как аналог соответствующему свойству в Internet Explorer и позволяет добавлять XML-содержимое прямо к содержимому тега. Несмотря на имя, это свойство можно использовать в XML и XUL наравне с HTML:

    tag_object.innerHTML = '<box><button value="On"/></box>';
    old_content = tag_object.innerHTML;
    

    Mozilla не поддерживает расширения Internet Explorer innerText, outerHTML и outerText.

    Вторая часть стандарта DOM 1 относится только к HTML. В ней перечислены удобные методы и атрибуты для работы с HTML-документами. У элемента DOM-дерева, соответствующего HTML-тегу, свойства будут соответствовать HTML-атрибутам данного тега. Поэтому в дереве у тега <FORM> будет узел со свойствами action, enctype и target, помимо прочих.

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

    document.getElementsByName('myform1');
    document.getElementById('testid');
    

    getElementById() также поддерживается для XUL и используется везде. Пользоваться getElementsByName() не рекомендуется даже для HTML, так как атрибут имени в HTML понемногу убирается из этих стандартов.

    Подведем итог. DOM 1 позволяет преобразовать целый XML- или HTML- документ в одну большую полностью изменяемую структуру данных. Если это HTML-документ, атрибуты тегов отображаются как свойства объектов, соответствующих известным тегам. В любом случае можно легко выделить из этой структуры массивы похожих тегов. Интерфейсы DOM 1 можно назвать "каноническими" (общепризнанными).

    5.5.2.3. DOM 2

    Mozilla почти полностью поддерживает DOM 2.

    DOM 2 заполняет многие пробелы в стандарте DOM 1. Это надмножество DOM 1. DOM 1 написан как один документ, а DOM 2 разбит на шесть. Это:

    1. DOM 2 Core
    2. DOM 2 HTML
    3. DOM 2 Events (события)
    4. DOM 2 Style (стили)
    5. DOM 2 Traversal and Range
    6. DOM 2 Views

    Первые два - то же, что и стандарт DOM 1. В них есть небольшие исправления, но эти же исправления есть и в последних версиях DOM 1. Кроме того, есть некоторые мелкие дополнения. Все, что есть в DOM 1, в DOM 2 называется "базовым интерфейсом". "Расширенный интерфейс" DOM 2 предоставляет несколько удобных функций, которыми можно пользоваться только в XML, но не в HTML. Это интерфейсы, дающие доступ к:

    • ссылкам на сущности, например &amp;;
    • инструкциям обработки, реализуемым языками вроде XSLT;
    • объявлениям <!ENTITY> в DTD документа.

    Стандарт DOM 2 также описывает известные события браузеров. Кроме событий мыши (click) и конкретных событий HTML (submit) описываются и общие события XML (DOMFocusIn). Есть и события, которые появляются во время изменения дерева документа. В этом стандарте отсутствуют события клавиатуры (keydown). Стандарт также определяет перемещения событий по дереву документа. Более подробно мы обсудим события в главе 6, "События".

    Стандарт DOM 2 Style дополняет стандарт CSS 2, предоставляя доступ к стилям из JavaScript. В DOM 1 лучшее, что можно сделать - это изменить атрибут style у тега (встроенный стиль). Стандарт DOM 2 позволяет работать с правилами стилей так, как если бы они были объектами, а не просто текстом. Он также позволяет проверять и менять полностью каскадный стиль конкретного тега. Следующий код

    document.getElementById("greeting").style.color ="red";
    

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

    Стандарт DOM 2 Traversal and Range делает возможными операции вырезания и вставки данных, выполняемые пользователем. Чтобы понять, какие теги пользователь выбрал, требуется тщательный осмотр дерева документа. Для такого осмотра нужны удобные способы прохода по дереву. Именно они описываются в части Traversal, где вводятся новые объекты, Iterator и TreeWalker. В части Range создается набор объектов для выделенных мышью тегов. В Mozilla технология выделения и вставки мышью, там, где она существует, реализована с помощью этого стандарта. Также в этой части описывается немного сложный процесс обработки тегов. Теги можно разбивать на два, вставлять содержимое между тегами и делать другие полезные вещи. Более простой способ вставлять содержимое в XML-документ, который при этом гарантированно останется корректным, открывает широкие возможности.

    Стандарт DOM 2 Traversal and Range в основном направлен на создание нового программного обеспечения. Это же утверждение верно и для стандарта DOM 2 Views. Его цель - предоставить интерфейсы, позволяющие дереву документа отображаться сразу же в нескольких окнах. Самое очевидное применение этой возможности - в HTML-редакторах вроде HomeSite, где одновременно доступны предварительный просмотр, обзор исходного кода и дерево документа. Изменение лежащей в основе модели документа через любое из этих представлений приведет к изменению и остальных. Стандарт DOM 2 Views описывает необходимые для создания такого редактора интерфейсы.

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

    5.5.2.4. DOM 3

    Mozilla поддерживает только небольшую часть стандартов DOM 3.

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

    1. DOM 3 Core
    2. DOM 3 Events
    3. DOM 3 Load and Save
    4. DOM 3 Validation
    5. DOM 3 XPath

    В DOM 3 Core внесены небольшие по сравнению с DOM 2 Core изменения. Важны две вещи. Следующий пример использования DOM 3

    document.getInterface("SVG");
    

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

    Второе важное изменение показано в следующем примере:

    document.setNormalizationFeature("format-pretty-print");
    

    Оно дает возможность дополнительного контроля над процессором XML. Механизм обработки XML во время загрузки XUL- или HTML-файла принимает множество решений, например, что делать с пробельными вставками и когда сообщать об ошибках. После реализации DOM 3 Core можно будет контролировать процесс принятия решений.

    Стандарт DOM 3 Events дополнительно описывает события клавиатуры (также называемые текстовыми событиями), отсутствующие в DOM 2 Events. Описывается большая часть клавиш, например, Alt, Caps Lock и F12. Также вводятся перехватчики для обработчиков событий, что позволяет запускать два обработчика событий для одного тега при возникновении только одного события. Это единственная часть DOM 3, которую Mozilla почти поддерживает.

    Стандарт с длинным именем DOM 3 Abstract Schemas and Load and Save - еще одно двухчастное произведение. Часть Abstract Schemas предоставляет полный доступ на чтение и запись к DTD XML-документа. Это, вероятно, более полезно в XML-базе данных, чем в браузере. Часть Load and Save описывает, как переходить от XML-текста к дереву документа и обратно. Это стандарт, определяющий сериализацию или кодирование. Это стандартное решение задачи, которую сейчас решает свойство innerHTML.

    DOM 3 Validation добавляет к операциям над документами систему ограничений. С помощью других стандартов DOM можно изменить документ так, что он больше не будет соответствовать своему типу документов. Например, с помощью интерфейсов DOM 1 можно добавить к содержимому HTML-документа тег <circle>, хотя это и не тег HTML. Стандарт DOM 3 Validation проверяет определение типа документа (в виде DTD или XSchema) при каждом изменении документа с помощью DOM-интерфейсов. Если какое-то изменение неявно нарушит правила типа документа, оно не будет допущено. Наконец, DOM 3 XPath описывает еще один метод прохода через дерево документа с помощью JavaScript, основываясь на стандарте W3C XPath. DOM 3 XPath делает доступной в JavaScript систему XPath.

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

    Сомнительно, чтобы в ближайшее время появился набор стандартов DOM 4, если он вообще будет когда-либо существовать.

    5.5.2.5. Фокусы с DOM-совместимостью

    Стандарты DOM предоставляют интерфейс DOMImplementation с методом hasFeature(). Этот метод может быть вызван из JavaScript, чтобы выяснить, какая функциональность в Mozilla заявлена как поддерживаемая. Вот подходящая для этого строка кода:

    f = document.implementation.hasFeature("XML","1.0");
    

    В базе данных ошибок Mozilla хранится старое обсуждение метода hasFeature(). В таблице 5.4 приведены результаты для Mozilla 1.0 и 1.4. Слово "FALSE" ("ложь") написано большими буквами лишь для облегчения чтения.

    Таблица 5.4. Поддержка DOM-стандартов в Mozilla 1.0 и 1.4

    ФункциональностьВерсияРезультат hasFeature()
    HTML1.0true
    XML1.0true
    Core2.0true
    HTML2.0true
    XML2.0true
    Views2.0true
    StyleSheets2.0true
    CSS2.0true
    CSS22.0true
    Events2.0true
    UIEvents2.0FALSE
    MouseEvents2.0true
    MouseScrollEventsНе описывается стандартом W3Ctrue
    MutationEvents2.0FALSE
    HTMLEvents2.0true
    Range2.0true
    Traversal2.0FALSE
    Xpath3.0true
    All other DOM 3 features3.0FALSE

    Другие функции DOM 3

    Результаты одинаковы для скриптов и в XUL, и в HTML, что делает некорректным результат запроса со строкой "HTML" в XUL-документе. В XUL есть также собственная поддержка так называемых KeyEvents, но hasFeature() еще не сообщает о них.

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

    Если поддерживается какая-то часть DOM, то в скриптах доступны соответствующие интерфейсы. Интерфейсы используются в языках программирования. Однако есть и другие способы взаимодействия со службами браузера. Два примера тому - XML и таблицы стилей. Браузер может реализовать поддержку таблиц стилей, но не поддержку DOM, и наоборот. Метод hasFeature() возвращает результаты только о поддержке DOM.

    Некоторая функциональность браузера может когда-нибудь сама попасть в стандарты DOM. Функции Mozilla 1.0, которые вероятнее всего привлекут внимание стандартов - поддержка перетаскивания мышью, события клавиатуры, события прокрутки и клавиатурные сокращения.

    Отдельно от DOM 0 для HTML существует поддержка DOM 0 для XUL. У DOM 0 для XUL нет стандарта или основного документа, описывающего его структуру. Он не документирован, за исключением замечаний о некоторых тегах в этой книге.

    Интерфейсы объектов в Mozilla определяются на собственном языке: XPIDL. Файлы XPIDL, необходимые программисту при работе с XPCOM- объектами, используются и для DOM-объектов. Так как синтаксис XPIDL очень похож на синтаксис IDL, перевод из одного в другой очень прост. Однако XPIDL-определения Mozilla, расположенные в каталоге исходных текстов dom/public/idl, - официальное заявление поддержки интерфейсов DOM и схожих с ними. Они включают в себя переопределения и IDL-определений DOM, и доступных интерфейсов для XUL. В них также указаны специфичные для Mozilla расширения этих стандартов. Но помните, что некоторые реализованные обработчики событий не появляются в этих файлах.

    5.5.3. AOM и BOM в Mozilla

    DOM-объекты относятся к XML-документам. Некоторые из них доступны при загрузке XML-документа, потому что это удобно. Очевидный пример - объект Document и переменные документа, указывающие на него.

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

    Эти объекты, не относящиеся к документам - часть объектной модели браузера (Browser Object Model, BOM), существующей в большинстве браузеров. Эта модель по сравнению с DOM очень маленькая и состоит из иерархии всего лишь нескольких объектов. В самом ее верху находится так называемый объект Window, представляющий окно на рабочем столе, в котором отображается документ. Переменная окна указывает на этот объект. Небольшая иерархия BOM будет знакома web-разработчикам и содержит такие объекты:

    navigator screen history location frames
    

    В этой книге мы не будем надолго задерживаться на подобных объектах (в любой книге по JavaScript для web-страниц они подробно рассматриваются). Лишь в главе 10, "Окна и панели", мы продолжим обсуждение объекта окна.

    Платформа Mozilla - это не только браузер, но и основа для приложений. При отображении XUL-документов более логично говорить об объектной модели приложения (Application Object Model, AOM), чем о BOM. Окна XUL также начинаются с переменной окна, самого верхнего объекта в иерархии приложения, но соответствующий ему тип - окно chrome, а не просто окно. На рисунке 5.1 показана иерархия приложения для окна chrome.

    Объектная модель XUL-приложения Mozilla

    Рис. 5.1.  Объектная модель XUL-приложения Mozilla

    Каждое слово на рисунке 5.1 - свойство объекта, само содержащее объект, хотя иногда там может находиться null или строка. Иерархия, начинающаяся с окна (window), присутствует всегда. Иерархия, начинающаяся с Element, существует для каждого объекта Element DOM 1. Темные ребра на рисунке показывают существование и для XUL, и для HTML; светлые - только для XUL. Прерывистая линия означает, что объект доступен, только если присутствуют защитные фильтры. Термины, совпадающие с терминами BOM, имеют то же значение, что и в BOM. Остальные термины обсуждаются в этом курсе.

    Краткая характеристика AOM-объектов, специфичных для XUL, звучит так: аргументы (arguments) передаются при открытии окна; содержимое (content) указывает на документ, содержащийся в теге <browser> XUL-страницы; набор управляющих команд (controllers) существует или для окна, или для одного тега; диспетчер команд (commandDispatcher) - также часть системы команд; фокус (focus) отвечает за точку фокуса; объект блока (boxObject) содержит всю информацию о фрейме конкретного тега и для обычных XUL-тегов, и для особых тегов вроде <scrollbox>; builder предоставляет механизм постройки дерева для тега <tree>; база данных (database) и источник данных (datasource) - части системы шаблонов XUL.

    Кроме этих объектов в главе 10, "Окна и панели", рассказывается и о некоторых менее явных. Многие из этих объектов связаны с общими механизмами отображения XUL- или HTML-документов.

    Объект окна XUL очень похож на объект окна HTML. Например, у него тоже есть свойство history. Такое положение дел несколько сбивает с толку, так как подразумевается, что эти свойства что-то делают. А они не делают ничего. Они существуют потому, что многие скрипты и XUL-документы, составляющие приложение браузера, ожидают, что эти свойства будут работать. В нормальных условиях (классический браузер), чтобы эти свойства функционировали, необходима дополнительная поддержка в скриптах. В обычных окнах XUL этой поддержки нет, если только XUL-приложение не собирается копировать функциональность браузера. Как и в случае HTML, все реализованные интерфейсы DOM напрямую доступны из XUL. Свойства и методы объекта window.document - входные точки для этих интерфейсов.

    И HTML-, и XUL-окна обладают одним особым свойством - свойством Components. Оно является шлюзом для доступа к большинству уникальных интерфейсов Mozilla. Эти интерфейсы мы и рассмотрим далее.

    5.5.4. XPCOM и XPConnect

    Стандарты DOM предоставляют интерфейсы, которые делают содержимое доступным из скриптов. Похожим образом XPConnect и XPCOM предоставляют интерфейсы, которые делают доступными из скриптов составные части самой платформы Mozilla.

    XPCOM - полностью внутренняя часть платформы Mozilla. XPConnect - соединительное звено, превращающее XPCOM-объекты и их интерфейсы в объекты базового ПО для JavaScript. В главе 16, "Объекты XPCOM", подробно описывается работа с этой системой с точки зрения приложения. В текущей главе дается только обзор этих двух технологий.

    С точки зрения программиста приложения, система XPCOM в основном представляется объектом window.Components (см. рисунок 5.1). Свойства этого объекта classes и interfaces - списки всех доступных идентификаторов контрактов XPCOM-объектов и имена их интерфейсов. Свойство manager - менеджер XPCOM-компонентов. Свойство results - список всех исключений, которые могут быть выброшены, и значений результатов, которые могут возвращаться. Результаты - 32-битные числа.

    Теоретически эти списки могут динамически меняться, но в стандартной платформе этого не происходит. Стандартная платформа регистрирует все доступные компоненты во время запуска, и большая часть этой избыточной регистрации проводится во время первого запуска платформы. Набор зарегистрированных компонентов может быть пересмотрен при запуске инструмента regxpcom (в Microsoft Windows - regxpcom.exe). Эта маленькая программа устанавливается вместе со стандартной платформой и необходима только при создании или установке новых XPCOM- объектов (например, целых модулей, а не просто объектов; см. лекцию 16, "Объекты XPCOM").

    С точки зрения программиста приложений, система XPConnect, делающая написанные на C/C++ компоненты доступными из JavaScript, почти невидима. Чтобы выполнить свою задачу, система XPConnect опирается на набор библиотек типов (с расширениями .xpt), которые хранятся в подкаталоге components каталога установки платформы. XPConnect также предоставляет доступ из скриптов к модулям браузера, интерфейсам DOM, Java и Java-аплетам. Хотя эти вещи и кажутся не связанными с XPCOM, в конце концов они все оказываются XPCOM-компонентами.

    Все XPCOM-компоненты, предоставляющие более одного XPCOM- интерфейса, должны реализовывать интерфейс nsISupports. Этот интерфейс предоставляет метод QueryInterface(), с помощью которого можно находить другие поддерживаемые объектом интерфейсы.

    5.5.4.1 Поиск XPCOM-компонентов

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

    Существует, по крайней мере, тысяча XPCOM-компонентов, каждый из которых предоставляет один или несколько интерфейсов. Это огромное число компонентов и этот набор эквивалентны очень большой библиотеке классов. Такое количество компонентов создает трудности для новичков. На первый взгляд, доступные функции Mozilla кажутся никак не связанными между собой, однако это не так. Просто все необходимые знания за один присест получить не удастся, а структура предмета неочевидна. Поиск документации по всем компонентам Mozilla также непрост. По две страницы на компонент - итого две тысячи страниц. Когда-нибудь такая книга, может, и будет написана, но пока ее еще нет.

    Чтобы найти подходящий компонент, придется приложить усилия. То, что нам в конце концов нужно - объект, который можно использовать для решения конкретной задачи. Не ко всем объектам необходимо обращаться напрямую через XPCOM-интерфейсы. Многие объекты существуют в других формах. Перед погружением в XPCOM задайте себе вопрос: может ли нужный вам объект появиться в одной из этих форм?

    • Стандартный объект JavaScript (например, Date или Math). Если да, загляните в стандарт ECMAScript.
    • Как часть DOM. Если объект связан с любой частью XML-документа, он описывается стандартами W3C DOM.
    • Как часть BOM. Из книги о JavaScript для web-страниц можно узнать, является ли какой-то объект стандартной частью скриптов для HTML. В введении в AOM в этой главе также сообщается, что есть и стандартные объекты XUL, доступные из скриптов.
    • XBL-связь. Почти у всех объектов, которым соответствует какой-то XUL-тег, есть и XBL-связка. Определения связок можно найти в архиве toolkit.jar в chrome.
    • Как служба нестатического объекта. Некоторые объекты существуют отдельно от DOM, но, тем не менее, доступны для непосредственной работы с ними (например, Image и Option в HTML, XMLHttpRequest и SOAPService в HTML и XUL).

    Если же не остается ничего, кроме как искать XPCOM-объект, то вот что следует делать. Объекты создаются из XPCOM-интерфейсов и XPCOM-компонентов, реализующих эти интерфейсы. У интерфейсов есть имена; у компонентов есть идентификаторы контракта (они же и их имена). Для поиска этой информации подойдет любая из следующих стратегий:

    • в индексе в конце этой книги найти ключевое слово, соответствующее области задачи. На протяжении книги рекомендуются многие пары "компонент-интерфейс";
    • загрузить краткое описание лекций этого курса с сайта (www.nigelmcfarlane.com) и поискать в них;
    • загрузить наборы XPIDL-определений с сайта этой книги (www.nigelmcfarlane.com) или из исходных текстов на mozilla.org и поискать в них;
    • исследовать chrome-приложения, которые поставляются вместе с классической Mozilla. Этот код содержит множество оттестированных и проверенных примеров;
    • искать в ресурсах организации mozilla.org, например, на сайтах, в конференциях, задавать вопросы в IRC. На четко сформулированные вопросы в этих сообществах часто дают подробные ответы.

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

    Наконец, имена почти всех (но не всех) полезных интерфейсов начинается с префикса:

    nsI
    

    а идентификаторы контракта всех компонентов начинаются с префикса:

    @mozilla.org/
    

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

    5.5.4.2. Работа с XPCOM-компонентами

    Вот простой пример использования XPCOM-компонента. В языке Perl есть ключевое слово require, позволяющее одному скрипту на Perl загрузить содержимое другого скрипта. В JavaScript нет аналога этой инструкции, но в Mozilla для этого можно использовать компоненты. Посмотрим на пример в листинге 5.7.

    var comp_name = "@mozilla.org/moz/jssubscript-loader;1";
    var comp_obj = Components.classes[comp_name];
    var if_obj = Components.interfaces.mozIJSSubScriptLoader;
    var final_obj = comp_obj.createInstance(if_obj);
    final_obj.loadSubScript("file:///tmp/extras.js");
    
    Листинг 5.7. Включение скриптов JavaScript с помощью XPCOM-компонента Mozilla

    Этот скрипт эквивалентен использованию eval() с содержимым файла /tmp/extras.js как аргумента.

    Для создания объекта необходимы две вещи: имя компонента (строка, comp_name) и интерфейс, который можно использовать (имя свойства, if_obj). После получения объекта (final_obj) можно задействовать его методы. В нашем случае метод loadSubScript() импортирует содержимое файла extras.js, как если бы оно было строкой, обрабатываемой eval().

    Характерный для многих компонентов Mozilla комментарий в XPIDL-определении mozIJSSubScriptLoader объясняет, что загружаемый файл JavaScript должен быть расположен на локальном компьютере. Краткие и рассредоточенные пояснения обычны для компонентов Mozilla.

    5.5.4.3. Другие варианты создания объектов

    В листинге 5.7 для создания объекта использовался метод createInstance(). Это одна из основных альтернатив. Другая - метод getService(). Он используется, когда данный XPCOM-компонент реализован так, чтобы предоставлять только единственный экземпляр объекта (одиночный объект). Такие объекты могут содержать статическую или глобальную информацию, которую нельзя синхронизировать между различными экземплярами объекта. В листинге 5.7 строку с createInstance() можно заменить на следующую:

    var final_obj = comp_obj.getService(if_obj);
    

    Как определить, какой из методов следует использовать? Если идентификатор контракта или имя интерфейса содержат слово "service" (в любом регистре), пользуйтесь getService(), иначе - createInstance().

    Эти методы можно вызывать и без аргументов. В этом случае у создаваемого или возвращаемого объекта есть интерфейс nsISupports. Затем с помощью данного интерфейса можно получить любой другой нужный интерфейс. Чтобы проиллюстрировать эту ситуацию, мы можем изменить листинг 5.7:

    var anon_obj = comp_obj.createInstance();
    var final_obj = anon_obj.QueryInterface(if_obj);
    

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

    var Includer = Components.Constructor(
      "@mozilla.org/moz/jssubscript-loader;1",
      "mozIJSSubScriptLoader",
      null
    );
    var final_obj = new Includer();
    // var another_obj = new Includer()
    

    Обратите внимание, что в этом случае имя интерфейса представляет собой строку. Третий аргумент метода Constructor() - необязательная строка. Если она есть, она означает имя метода, который будет вызван как метод инициализации при создании экземпляра объекта. Все аргументы, переданные конструктору объекта Includer(), будут передаваться этому методу инициализации.

    Наконец, можно создавать объекты с XPCOM-интерфейсами прямо в JavaScript. Это можно делать только для простых интерфейсов, для которых не предусмотрена какая-либо особая обработка. Продолжая вариации на тему листинга 5.7, мы можем создать объект, используя код листинга 5.8.

    var final_obj = {
      // интерфейс nsISupports
      QueryInterface : function(iid) {
      var Ci = Components.interfaces;
      if ( !iid.equals(Ci.nsISupports) &&
      !iid.equals(Ci.mozIJsSubSCriptLoader )
      throw Components.results.NS_ERROR_NO_INTERFACE;
      return this;
    },
    // интерфейс mozIJSSubScriptLoader
    loadSubScript : function (url) {
      // здесь идет код для запуска скрипта
      return;
    }
    };
    
    Листинг 5.8. Создание JavaScript-объекта с XPCOM-интерфейсом

    Этот объект поддерживает и интерфейс nsISupports, и mozIJsSub-ScriptLoader. Если бы мы могли быть уверены, что для этого объекта никогда не будет вызван метод QueryInterface(), мы могли бы не использовать часть объекта с nsISupports. Конечно, с этим объектом связана одна большая проблема: как правильно реализовать второй интерфейс? Эта реализация будет содержать длинный код, открывающий файл в файловой системе, читающий его содержимое, передавая его eval(). Для этого можно использовать уже существующую реализацию объекта. Такое создание объектов вручную иногда очень полезно. Чаще всего оно бывает необходимо при создании возвращаемых объектов для разных отслеживающих функций, см. лекцию 6, "События".

    Метод createInstance() сам реализуется интерфейсом nsIComponentManager; getService() реализуется интерфейсом nsIServiceManager.

    5.5.4.4. Модули и DOM

    К модулям браузера можно обращаться из скриптов, начиная с третьих версий браузеров. В то время Netscape расширила версию 1.1 стандарта модулей NPAPI, чтобы сделать его доступным из скриптов, и для работы с ним были написаны сотни модулей. Mozilla до сих пор поддерживает этот стандарт, но чтобы все работало без заминок, нужны последние версии и платформы, и модулей (особенно это касается Macromedia's Flash).

    Для модулей существуют наборы XPIDL-интерфейсов. С их помощью отдельными модулями можно управлять прямо из JavaScript. По-прежнему самый простой способ работать с модулями - пользоваться HTML-тегами <embed> (не рекомендуется) или <object> (рекомендуется) и массивом plugins DOM 0. Обсуждение комбинирования HTML и XUL можно найти в главе 10, "Окна и панели".

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

    5.5.4.5. Java

    Браузеры поддерживают доступ к Java-апплетам из скриптов, начиная с третьих версий; Mozilla также поддерживает эту функциональность. Архитектура, используемая для этого в платформе 1.x, отличается от архитектуры в Netscape Communicator 4.x, но совместима с ней, так что проблем у авторов скриптов быть не должно.

    В Netscape Communicator 4.x браузер полностью зависел от модели защиты Java 1.1. Чтобы браузер мог принять какое-то решение относительно защиты, нужно было послать запрос Java с целью проверить специальную библиотеку классов, предоставляемую Netscape. Java и JavaScript были связаны с помощью технологии LiveConnect.

    В Mozilla этой зависимости от Java больше нет. Netscape отвечает за свою защиту сам. Java не требуется и она уже не интегрирована в такой степени в платформу. LiveConnect в его первоначальном смысле не существует; есть только XPConnect. Интеграция, предоставляемая LiveConnect, все еще существует, но она находится ниже XPCOM под слоями кода. Явная поддержка Java в Mozilla теперь состоит из набора XPCOM-интерфейсов, как и многое другое, а поддержка Java по статусу считается близкой к модулям. Функциональность LiveConnect по-прежнему проявляется, когда создаются интерфейсы объектов Java и JavaScript для доступа к ним из "неродного" языка.

    Mozilla предоставляет две технологии для адаптации Java. Первая - OJI (Open Java Inteface, открытый интерфейс к Java). Это программный интерфейс, напоминающий интерфейс NPAPI (для модулей), который предназначен для работы с платформой Java любого производителя. XPCOM-интерфейсы предоставляют доступ к OJI. Вторая технология - эмулятор LiveConnect для обратной совместимости. Это тоже XPCOM-интерфейс, определенный в интерфейсе nsIJRILLiveConnectPlugin. Этот эмулятор обрабатывает C/C++-запросы Mozilla, которые раньше отправлялись Netscape 4.x виртуальной машиной Java.

    Как и в случае с модулями, по-прежнему простейший способ использовать Java внутри Mozilla - теги <applet> или <object> в HTML-файле. Опять же, о том, как смешивать HTML и XUL, рассказано в главе 10, "Окна и панели".

    Некоторые программисты, пришедшие в web-программирование, уже обладают знанием Java. Если у вас есть сложная Java-среда, которую предстоит интегрировать с Mozilla, будьте осторожны. С одной стороны, самые простые и очевидные взаимодействия между JavaScript и Java будут работать прекрасно. С другой стороны, JavaScript, Java и Mozilla - очень сложные системы, и идеальное их взаимодействие на любом уровне - глобальная цель. В случае более сложных взаимодействий по-прежнему возникают проблемы, и рекомендуется внимательно изучить ошибки, связанные с Java в базе данных ошибок, Bugzilla.

    Лучше всего остановиться на реализации Java от Sun Microsystems и затем пользоваться только версией 1.4 или более поздней. Чтобы свести к минимуму вероятность нетривиальных ошибок, рекомендуется пользоваться последними версиями Mozilla.

    За исключением некоторых замечаний в главе 10, "Окна и панели", в этом курсе разговор о Java завершен.

    5.5.5. Библиотеки скриптов

    Чтобы использовать чью-либо еще функциональность, Mozilla опирается на систему XPCOM, но существуют и небольшие библиотеки, написанные полностью на JavaScript. Эти библиотеки пытаются упростить взаимодействие с отдельными частями XPCOM-системы.

    Самые полезные из этих библиотек - JSLib и RDFLib. На самом деле RDFLib - лишь подмножество JSLib. JSLib упрощает работу с файлами и папками. Ее часть RDFLib предназначена для облегчения работы с источниками данных RDF. Эта библиотека предоставляет JavaScript-объекты, упрощающие XPCOM-объекты, на основе которых они созданы.

    Эту библиотеку можно загрузить с сайта http://jslib.mozdev.org. Ее файлы написаны в кодировке ASCII с переводом строки, принятым в UNIX.

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

    Чтобы пользоваться библиотекой, необходимо включить файл верхнего уровня jslib.js, предоставляющий полезные процедуры и константы. Самая важная из этих процедур - функция include(), которая выполняет те же функции, что и require в Perl (или #include в препроцессоре C). Затем можно включать из JSLib любую нужную функциональность. Библиотека содержит около дюжины подкаталогов, но самые важные из них - io (для работы с файлами) и rdf (для работы с RDF). На рисунке 5.2 показаны зависимости между файлами.

    Зависимости между файлами в JSLib и RDFLib

    Рис. 5.2.  Зависимости между файлами в JSLib и RDFLib

    Скрипты, из которых выходят стрелки, требуют, чтобы скрипты, к которым эти стрелки направлены, были загружены первыми. Файлы без зависимостей не показаны. Очевидно, что библиотека разделена на две основные части, которые в этой книге будут называться RDFLib и JSLib. Существует две независимые реализации доступа к RDF-файлам: rdf/rdfFile.js и io/RDF.js. Они представляют собой различные интерфейсы для решения примерно одной задачи; интерфейс rdf/rdfFile.js - более общий и использует подобъекты.

    Библиотека задействует метод window.Components.Constructor(), чтобы создавать конструкторы объектов для XPCOM-компонентов (описание того, как это делается, дано в разделе "Другие варианты создания объектов"). Кроме того, она реализует конструкторы объектов JavaScript с помощью системы прототипов JavaScript. Конструктор на основе прототипов используется для создания нового объекта JavaScript. Во время процесса создания конструкторы XPCOM-объектов используются для задания свойств нового объекта JavaScript. Каждое такое свойство хранит один XPCOM- объект. Поэтому окончательный объект JavaScript содержит (или использует) один или несколько XPCOM-объектов и является точкой доступа к этим объектам или их "фасадом". Это очень простая и широко распространенная методика.

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

    Таблица 5.5. Объекты, создаваемые JSLIb и RDFLib

    КонструкторФайл с исходным кодомИспользует объектыЦель
    include() methodjslib.jsВключать другие файлы .js
    Dir(filePath)io/dir.jsFileSystem()Работать с папкой на локальном диске
    DirUtils()io/dirUtilsНаходить каталоги, специфичные для установки
    File(filePath)io/file.jsFileSystem()Работать с файлом на локальном диске
    FileSystem(filePath)io/filesystem.jsРаботать с любым элементом файловой системы на локальном диске
    RDF(url, urn, xmlns, sync_flag)io/rdf.jsЗаписать или прочитать один RDF-контейнер конкретного формата в существующий файл или из него
    RDFFile(file_url, urn, xmnls, sync_flag)io/rdf.jsRDF()Записать или прочитать один RDF-контейнер конкретного формата в локальный файл или из него
    Socket()network/socket.jsСоздать и читать из сокета или писать в него
    SocketListener()network/socket.jsОбеспечить асинхронное чтение из сокета
    RDF(url, sync_flag)rdf/rdf.jsRDFBase()Читать записи из RDF-документа или писать их в него по очереди
    RDFBase(datasource)rdf/rdfBase.jsЧитать записи из RDF-документа или писать их в него по очереди, используя существующий источник данных
    RDFContainer(type ownerURI, subjectURI, datasource)rdf/rdfContainer.jsRDFResource()Упорядочивать или создавать RDF-контейнеры <Seq>, <bag> или <alt>, используя записи
    RDFResource(type, ownerURI, subjectURI, datasource)rdf/rdfContainer.jsRDFBase()Изменить или создать URI для работы с ним как с субъектом, объектом или предикатом RDF
    Sound(url)sound/sound.jsПроиграть звук, находящийся по этому адресу

    В таблице 5.5 аргумент datasource - объект nsIRDFDataSource, не URL rdf:. Библиотека JSLib содержит несколько других полезных объектов, но они не такие общие или полные, как объекты, перечисленные в таблице. Например, объект Zip, определенный в файле zip/zip.js, выполняет только часть работы, необходимой для извлечения файла из архива.

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

    5.5.6. XBL-связки

    В JavaScript также доступны интерфейсы, предоставляемые XBL-определениями. Эти определения включаются в XML-файл со свойством стиля CSS 2 -moz-binding. Такая связь добавляет определение тегу или тегам. Хотя XBL-определения могут включать какое-то содержимое, можно создавать и определения, содержащие только функциональность - интерфейс. Возможность добавлять любую функциональность к любому тегу - очень мощная функция.

    Если для получения объекта Element, соответствующего тегу с XBL-определением, используется стандарт DOM 1, результирующий объект JavaScript будет включать свойства и методы, подразумеваемые этим определением. Этими свойствами и методами затем можно пользоваться как обычно для объектов базового ПО. Платформа автоматически выполнит все необходимые действия.

    Таким образом, XBL-определения расширяют функциональность некоторых DOM-объектов. Самими XBL-определениями можно пользоваться и как XPCOM-компонентами, поэтому они доступны через XPConnect. Единственная причина делать это - если XBL-компонент должен быть доступен из кода на C/C++.

    В главе 15, "XBL-связки", XBL рассматривается более подробно.

    5.5.7. Изолированные интерфейсы

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

    Система настроек Mozilla - первый из этих интерфейсов. Ее AOM состоит из единственного объекта PrefConfig, который играет роль глобального объекта. У него есть всего два свойства, два метода: pref() и user_pref(). Файл настроек, prefs.js, состоит из многих вызовов этих методов, но на самом деле там можно использовать JavaScript полностью. Любая из возможностей языка в лучшем случае неявная. Система настроек также предоставляет два XPCOM-компонента. Они называются

    @mozilla.org/preferences-service;1
    @mozilla.org/preferences;1
    

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

    Второй такой интерфейс - система сетевой установки компонентов Mozilla. В главе 17, "Система распространения и установки - XPInstall", полностью описывается эта среда и ее AOM. Эта система также предоставляет XPCOM-компоненты для доступа из обычной среды, но эти интерфейсы низкоуровневые и мало полезны для решения обычных задач. Они позволяют уведомлять систему XPInstall о появлении содержимого откуда-нибудь еще (например, из Internet) и принимать это содержимое для обработки. Эти ограниченные интерфейсы свидетельствуют, что XPInstall стоит использовать только для процесса установки.

    Третий изолированный интерфейс Mozilla - xpcshell. Это отдельная от платформы Mozilla программа, недоступная в обычных стабильных сборках. Она доступна в нестабильных сборках или если вы собираете Mozilla самостоятельно (требует параметра --enable-debug). xpcshell - самостоятельный интерпретатор JavaScript, использующийся для тестирования скриптов, интерпретатора JavaScript и системы XPConnect/XPCOM. Его AOM состоит из единственного глобального объекта и объектов, связанных с XPCOM. Самые полезные свойства глобального объекта перечислены в таблице 5.6.

    Таблица 5.6. Свойства глобального объекта xpcshell

    СвойствоОписание
    ComponentsМассив компонентов XPConnect/XPCOM, предоставляет доступ к большей части компонентов платформы Mozilla
    dump(arg)Преобразует аргумент в строку Unicode и отправляет ее необработанной на стандартный вывод
    load(arg1,arg2,...)Пытается загрузить и интерпретировать указанные файлы (не URL)
    print(arg1,args2,...)Преобразует аргументы в строки и отправляет их, слегка отформатировав, на стандартный вывод с помощью printf("%s")
    quit()Завершает работу xpcshell

    Массив Components делает xpcshell отличным местом для тестирования скриптов, которые используют большое число компонентов Mozilla. Так как xpcshell пользуется обычными дескрипторами файлов stdin и stdout, эту программу легко запускать в пакетном режиме автоматическими тестовыми системами. xpcshell - это JavaScript-эквивалент интерпретатора Perl, только он немногим больше программы тестирования.

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

    5.6. Практика: динамическое содержимое NoteTaker

    В этом разделе мы добавим в окно редактирования (Edit) NoteTaker несколько скриптов. Эти скрипты будут менять способ работы XUL- документа после его отображения платформой. Во время работы мы будем манипулировать несколькими типами объектов, описанных в этой главе.

    5.6.1. Работа с <deck> с помощью DOM

    Первое изменение NoteTaker будет касаться содержимого окна с ключевыми словами. Это окно на самом деле поддерживает две отдельные панели. Каждая панель представляется одним из тегов <toolbarbutton>: "Edit" и "Keywords". Мы бы хотели отображать окно редактирования так, чтобы видимой была только одна из этих панелей. Так как содержимое панели с ключевыми словами еще не определено, мы используем "заполнитель".

    Чтобы добавить поддержку двухпанельной системы, воспользуемся тегом <deck>. Содержимое панели "Edit" будет появляться как одна карта колоды, а содержимое панели "Keyword" будет другой картой. Ранее содержимое, скрывающееся под двумя тегами <toolbarbutton>, было для кнопки "Edit". Теперь мы хотим поместить это содержимое на одну карту, а для другой используем что-нибудь вроде:

    <hbox flex="1">
      <description>Добавим потом.</description>
    </hbox>
    

    На рисунке 5.3. показана структура XUL-документа до и после изменения.

    Добавление <deck> в NoteTaker


    Рис. 5.3.  Добавление <deck> в NoteTaker

    Все, что мы сделали - добавили в нужные места <deck> и новое содержимое. Это можно было сделать и в главе 2, "Проектирование с XUL", однако переключаться между двумя картами <deck> без скрипта невозможно. Теперь у нас есть JavaScript, и мы можем реализовать переключение. Слегка забегая вперед (в лекцию 6, "События"), мы отметим, что теги <toolbarbutton> поддерживают обработчик событий onclick. Мы добавим скрипт так, чтобы с ним было интересно повозиться.

    Существует множество способов решить нашу задачу, но все они сводятся к добавлению тегам атрибутов id; написанию функции, которую мы назовем action(), и добавлению обработчика onclick. Обработчики onclick выглядят примерно так:

    <toolbarbutton label="Edit" onclick="action('edit')"/>
    <toolbarbutton label="Edit" onclick="action('keywords')"/>
    

    Новые идентификаторы будут такие:

    <deck flex="1" id="dialog.deck">
    <hbox flex="1" id="dialog.edit">
    <hbox flex="1" id="dialog.keywords">
    

    Функция action(); приведена в листинге 5.9.

    function action(task) {
      var card = document.getElementById("dialog." + task);
      if (!card || ( task != "edit" && task != "keywords") )
      throw("Unknown Edit Task");
      var deck = card.parentNode;
      var index = 0;
      if ( task == "edit" ) index = 0;
      if ( task == "keywords") index = 1;
      deck.setAttribute("selectedIndex",index);
    }
    
    Листинг 5.9. Простая функция NoteTaker, принимающая команды как аргументы

    Мы хотим научиться передавать функциям аргументы, похожие на команды, потому что это общая практика для приложения Mozilla. Если мы включим этот код в наш XUL-файл, с тегом <script>, мы получим непонятные ошибки, описанные в разделе "Отладка" этой главы, если только не будем осторожны и не воспользуемся содержимым <![CDATA[]]>. Лучше всего поместить наш скрипт в отдельный файл с самого начала и включить его примерно так:

    <script src="editDialog.js"/>
    

    Функция action() вызывает метод window.document.getElementById(). Этот метод - самая распространенная входная точка для доступа к DOM из скриптов. Ей передается значение HTML- или XUL-атрибута id, и она возвращает объект для тега с этим идентификатором. Далее мы можем работать с этим объектом через его свойства и методы.

    Оставшаяся часть функции проходит через тег <deck> с помощью свойства DOM parentNode и устанавливает там значение атрибута selectedIndex с помощью свойства DOM setAttribute(). Так как это свойство имеет смысл для тега <deck>, система отображения XUL отреагирует на изменения автоматически, и отобразится нужная карта.

    Если мы не будем пользоваться аргументами, похожими на команды, можно достигнуть того же эффекта быстрее, но не очень аккуратно. Эквивалентные обработчики onclick, которые по-прежнему используют интерфейсы DOM, выглядят так:

    document.getElementById("dialog.deck").setAttribute("selectedIndex",0);
    document.getElementById("dialog.deck").setAttribute("selectedIndex",1);
    

    Независимо от того, какой из двух подходов выбрать, для решения большей части задачи потребуются всего лишь три интерфейса стандарта DOM 1 Core. Эти три интерфейса - Document, Element и Node.

    5.6.2. Альтернатива: изменение стилей с использованием DOM

    Использование <deck> - лишь один из многих способов "оживить" XUL-документ скриптами. Другой, не менее правильный способ - менять CSS-стили после загрузки документа.

    Начнем с примера <deck>, удалив его открывающие и закрывающие теги. Теперь каждый набор содержимого, заключенный в <hbox>, можно интерпретировать как HTML-тег <div>. В листинге 5.10 показана другая версия метода action().

    function action(task) {
      var card = document.getElementById("dialog." + task);
      if (!card || ( task != "edit" && task != "keywords") )
      throw("Unknown Edit Task");
      var oldcard; // the content to remove
      if (task == "edit")
      oldcard = document.getElementById("dialog.keywords");
      else
      oldcard = document.getElementById("dialog.edit");
      oldcard.setAttribute("style","visibility:collapse");
      card.removeAttribute("style");
    }
    
    Листинг 5.10. Простая функция NoteTaker, принимающая команды как аргументы

    Здесь используются те же интерфейсы DOM, что и в листинге 5.9, но меняются атрибуты в DOM-иерархии окна. При изменении правил стиля содержимого система отображения внутри платформы автоматически обновляет отображаемый документ. Использовать атрибут style не очень удобно. В XUL есть более удобный атрибут collapsed, который можно менять, не трогая при этом никакие другие атрибуты или встроенные стили, которые может добавлять скрипт. Две последние инструкции скрипта нужно заменить следующими строчками:

    oldcard.setAttribute("collapsed","true");
    card.removeAttribute("collapsed");
    

    Атрибут XUL hidden не должен обновляться автоматически, поскольку он может нанести вред XBL-связям (см. лекцию 15, "XBL-связки").

    Кроме использования <deck> и стилей, можно использовать DOM-интерфейсы и для физического удаления/вставки частей в иерархию DOM документа. Это третий подход, использующийся для скрытия и отображения содержимого, но он слишком сложен для столь простой задачи.

    5.6.2. Альтернатива: изменение <deck> с использованием DOM

    При преобразовании XUL-документа в иерархию DOM предоставляется больше информации, чем предусмотрено стандартными интерфейсами DOM. Существует еще и набор интерфейсов XBL-системы Mozilla. Эти дополнительные интерфейсы добавляют свойства и методы специфичным для XUL DOM-объектам. Эти свойства и методы чрезвычайно удобны и упрощают написание скриптов. Иногда использование только стандартов DOM делает код менее простым и понятным.

    Давайте посмотрим, можно ли с помощью одного из этих интерфейсов улучшить наше решение с использованием тега <deck>. Вероятнее всего, тег, с которым мы свяжем наш скрипт, будет <deck>. Для начала заглянем в файл xul.css. Этот файл хранится в архиве toolkit.jar в chrome. Желательно хранить распакованную версию этого архива где-нибудь под рукой. Просматривая нужный файл в обычном текстовом редакторе, зададим поиск "deck", и вот что мы найдем:

    deck {
      display: -moz-deck;
      -moz-binding: url("chrome://global/content/bindings/
      general.xml#deck");
    }
    

    Строка -moz-binding сообщает нам о том, что для этого тега есть связь, так что нам есть на что посмотреть. Связь называется "deck" и описана в файле general.xml. Откроем этот файл и поищем такую строку:

    <binding id="deck">
    

    Этого достаточно, связь найдена. Вот одна из частей ее определения:

    <binding id="deck">
    <implementation>
    <property name="selectedIndex" ...
    

    Это все, что нам нужно. Имена свойства в XBL и имена атрибутов в XUL обычно совпадают, более того, они обычно совпадают с именами HTML- объектов. Свойство selectedIndex совпадает с атрибутом selectedIndex тега <deck>. Его мы и используем. В методе action() заменим строку

    deck.setAttribute("selectedIndex",index);
    

    на

    deck.selectedIndex = index;
    

    Это тривиальное изменение, но наш код становится достаточно коротким, чтобы мы могли выкинуть переменную index и избавиться еще от пары строк. Если бы в определении связки для <deck> был бы, например, метод setCard(), мы бы могли использовать его вместо написания action(). Последние строки нашей функции можно сократить так:

    var deck = card.parentNode;
    if ( task == "edit" ) deck.selectedIndex = 0;
    if ( task == "keywords") deck.selectedIndex = 1;
    

    5.6.4. Чтение наборов строк через XPCOM

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

    Чтобы получить доступ вовне, нам не нужны XPCOM-компоненты. Мы могли бы задействовать URL, указанный в главе 7, "Формы и меню". Но здесь мы воспользуемся наборами строк.

    Мы можем манипулировать наборами строк (файлами properties) из XUL или из JavaScript. Если мы выберем первый путь, будет то же, что и при написании скрипта для <deck>: мы добавим несколько тегов и поищем XBL-определение, которое предоставляет полезные интерфейсы. Но сейчас мы будем работать с наборами строк напрямую из JavaScript. Так как для этого нужен XPCOM, нам придется хранить файлы внутри chrome.

    Нам нужен объект, который умел бы работать с файлами properties. Другими словами, нам требуется некий полезный XPCOM-интерфейс и компонент, реализующий этот интерфейс. Если мы будем искать его в XPIDL-файлах (или по индексу этой книги), легко заметить интерфейсы в файле nsIStringBundle.idl. В нем есть два интерфейса: nsIStringBundleService и nsIStringBundle. Так как в имени первого встретилось слово "Service" (служба), это, вероятно, XPCOM-служба; для нас это отправная точка. Вспомним, что службы создают объект с помощью getService(); в остальных случаях используется createObject().

    Мы также обратим внимание на то, что метод createBundle() этого интерфейса выглядит так:

    nsIStringBundle createBundle(in string aURLSpec);
    

    Итак, этот метод создает объект с интерфейсом nsIStringBundle из URL. У интерфейса nsIStringBundle есть метод getStringFromName(), извлекающий строку из файла с набором строк. Нам не так важно, что в XPIDL используются собственные типы wstring и string; как нам известно, XPConnect преобразует их во что-то, что JavaScript может понять - обычное строковое значение, которое появится как объект String.

    В файле интерфейса также в самом верху заявляется, что связанный с ним идентификатор контракта таков (теперь у нас получается пара из интерфейса и XPCOM-компонента):

    @mozilla.org/intl/stringbundle;1 nsIStringBundleService
    

    Нам не нужен идентификатор контракта для интерфейса nsIStringBundle, так как служба создаст объекты с этим интерфейсом для нас, когда мы вызовем createBundle(). Но нам нужно сначала получить объект службы. Так как мы нашли пару целиком, это легко:

    var Cc = Components.classes;
    var Ci = Components.interfaces;
    var cls = Cc["@mozilla.org/intl/stringbundle;1"];
    var svc = cls.getService(Ci.nsIStringBundleService);
    

    Если в Консоли JavaScript никаких ошибок не появится (а их быть не должно), тогда теперь переменная svc содержит объект службы, который мы запросили. Теперь мы можем писать код. В листинге 5.11 показаны результаты.

    var Cc = Components.classes;
    var Ci = Components.interfaces;
    var cls = Cc["@mozilla.org/intl/stringbundle;1"];
    var svc = cls.getService(Ci.nsIStringBundleService);
    var URL = "chrome://notetaker/locale/dialog.properties";
    var sbundle = svc.createBundle(URL);
    function load_data() {
      var names = ["summary", "details", "chop-query", "home-page", "width",
      "height", "top", "left"];
      var box, desc, value;
      for (var i = names.length; i>0; i--) {
        value = sbundle.getStringFromName("dialog."+ names);
        desc = document.createElement("description");
        desc.setAttribute("value",value);
        box = document.getElementById("dialog." + names);
        box.appendChild(desc);
      }
    }
    
    Листинг 5.11. Код NoteTaker для чтения наборов строк

    Переменная sbundle содержит XPCOM-объект, указанный нашим URL. Файл по выбранному нами URL должен соответствовать правилам файла properties. Функция load_data() считывает строки из этого файла и задействует DOM так, что тег <description value="строка"> добавляется как содержимое в конец всякого блока-заглушки <box>. Обратите внимание, что объект <description> строится и добавляется к блоку в самом конце. Это более эффективно, чем пошаговое добавление новых данных в существующую структуру DOM.

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

    <box class="temporary"/>
    

    на что-то вроде

    <box class="temporary" id="dialog.summary"/>
    

    Наконец, мы вызовем функцию load_data() из другого обработчика событий, который подсмотрели в главе 6, "События": атрибут onload тега <window>. Таким образом, мы будем уверены, что документ уже будет существовать к тому времени, когда мы начнем манипулировать им:

    <window xmlns= "http://www.mozilla.org/keymaster/gatekeeper/
    there.is.only.xul" onload="load_data()>
    

    Так как файл хранится в части chrome, относящейся к локализации, нам нужно задать язык, как это описано в разделе "Практика" главе 3, "Статическое содержимое". Другими словами, необходимо создать файл contents.rdf и обновить файл installed-chrome.txt. Сам файл со строками должен выглядеть примерно так, как в листинге 5.12.

    dialog.summary=My Summary
    dialog.details=My Details
    dialog.chop-query=true
    dialog.home-page=false
    dialog.width=100
    dialog.height=90
    dialog.top=80
    dialog.left=70
    
    Листинг 5.12. Файл dialog.properties для NoteTaker

    Путь к этому файлу относительно корневого каталога chrome будет таким:

    notetaker/locale/en-US/dialog.properties
    

    Теперь диалог NoteTaker должен походить на снимок на риcунке 5.4.

    Диалог NoteTaker cо строками, загружаемыми скриптом

    Рис. 5.4.  Диалог NoteTaker cо строками, загружаемыми скриптом

    Нам удалось успешно использовать XPCOM для взаимодействия с ресурсами вне загруженного документа. Это важный шаг, даже если все, что мы сделали - чтение файла. Мы пока оставим процедуру записи в файл. Запись в файлы (и их чтение) - долгая история, ее мы расскажем в главе 16, "Объекты XPCOM".

    Подведем итог проделанной работы: мы работали с DOM-, AOM-, XBL- и XPCOM-объектами. JavaScript предоставляет полный доступ к загруженному содержимому и, если наше приложение установлено в chrome, позволяет делать с этим содержимым что угодно. Большая часть добавленной нами в этой главе функциональности - лишь маленькие, но любопытные фокусы. В следующих главах мы заменим эти процедуры более профессиональной работой.

    5.7. Отладка: отладка скриптов

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

    Лучшая защита от ошибок - хороший стиль написания кода. Держите свои скрипты отдельно от документов. Не забывайте о завершающих точках с запятой и отступах, пользуйтесь содержательными именами переменных. Всегда проверяйте аргументы, передаваемые функции, и возвращайте какое-нибудь осмысленное значение. Всегда пользуйтесь блоками try, если используемые интерфейсы Mozilla могут генерировать исключения. Обратите внимание на советы о настройках в первой главы, "Основные концепции".

    Метод dump() объекта окна - очень полезное средство. При его использовании можно запустить Mozilla с ключом -console, тогда вспомогательная информация будет выводиться в отдельное окно и не повлияет на собственные окна платформы. Этот вывод также можно сохранить в файл журнала. В UNIX запуск с этим ключом возможен только из командной строки. Если скрипты используют выполнение через промежутки времени, либо создаваемые либо получаемые, такой вывод - иногда единственный способ узнать, какой же на самом деле была последовательность событий во время обработки.

    Mozilla также поддерживает URL javascript:. Этот URL может пригодиться для быстрой проверки состояния документа. Если документ загружен в окно Навигатора (XUL-документы могут быть загружены так наравне с web-страницами), такие URL можно использовать для проверки содержимого документа. Это наиболее эффективно, когда пользователь вводит в документ большое количество информации. Этот ввод обычно хранится как информация о состоянии, которую можно запросить инструкциями, подобными следующим:

    javascript:var x=window.state.formProgress; alert(x);
    

    Здесь alert() - небольшое окно, отображающее одну или несколько строк текста. Эту инструкцию можно поместить куда угодно в скрипт, связанный с окном Mozilla, таким образом вы получите базовую информацию о состоянии среды скрипта и ее содержимого. alert() также приостанавливает интерпретатор JavaScript; это неплохо при простой обработке данных, но неудачно, если предполагается, что информация должна обрабатываться скриптом за очень быстрое время (например, при использовании потокового аудио/видео). alert() - простейший инструмент отладки.

    Контрольные точки - еще одно простое, но очень полезное средство отладки. У каждого созданного объекта JavaScript есть метод watch(). Этот метод используется для добавления к свойствам объекта одной скрытой функции. Синтаксис этой скрытой функции напоминает синтаксис "set function ()" в Mozilla. Пример приводится в листинге 5.13.

    function report(prop,oldval,newval) {
      dump(prop + "old: " + oldval + "; new: " + newval);
      return newval; // гарантирует, что отслеживаемый код еще выполняется
    };
    var obj = { test:"before" };
    obj.watch("test",report);
    obj.test = "after"; // вызов report()
    obj.unwatch("test");
    
    Листинг 5.13. Отслеживание изменения свойств объекта с помощью контрольных точек

    Аргументы функции report() предоставляются функцией watch(). При отслеживании свойства test каждое его изменение в качестве побочного эффекта приводит к вызову функции report().

    При снятии отслеживания этот побочный эффект исчезает. Это тоже тактика отслеживания изменений, когда они происходят.

    Наконец, в любую часть скрипта можно вставить ключевое слово debugger. При этом откроется Отладчик JavaScript, о командах которого можно узнать, набрав /help в командной строке в нижней части окна. Отладчик основывается на этой XPCOM-паре:

    @mozilla.org/js/jsd/debugger-service;1 jsdIDebuggerService
    

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

    При возникновении непонятных проблем следует полностью закрыть Mozilla, запустить ее заново и провести проверку снова. Нужно всегда держать открытой Консоль JavaScript (Tools | Web Development) и всегда очищать ее журнал перед загрузкой нового документа, чтобы ошибки были заметны сразу. Время от времени необходимо проверять, не появились ли "умершие" процессы Mozilla без окон, к которым их можно отнести.

    5.8. Итоги

    Язык JavaScript невелик и прост в освоении. Он во многом похож на многие другие языки из семьи языков с C-подобным синтаксисом, за исключением уникальной работы с объектами. Цепочки областей видимости и прототипов - любопытные и сложные особенности языка, и на искусное создание кода, работающего с ними, можно потратить часы. В JavaScript 2.0 цепочки прототипов будут заменены классами.

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

    Опытные web-разработчики заметят, что поддержка DOM-интерфейсов в Mozilla проста в использовании, знакома и функциональна. Без дополнительных усилий она применима и к XUL-приложениям. Хорошая поддержка стандартов - настоящий праздник после многих лет проверок на совместимость с различными браузерами. Начинающим XML-программистам рекомендуется внимательно изучить первую часть стандарта DOM 1, пока понятия узла, элемента, документа и фабричного метода не уложатся в голове.

    Инфраструктура, состоящая из XPConnect и XPCOM - новый мир Mozilla. Имея более тысячи компонентов (и интерфейсов), нельзя получить все сразу. Возможно, некоторые интерфейсы вам не придется задействовать никогда; использование других будет во многом зависеть от того, как сильно требуется изменить платформу и насколько амбициозно ваше приложение.

    XPCOM предоставляет в распоряжение пользователя целый мир объектов и позволяет сделать программирование на JavaScript таким же развитым, как и с применением языков Java, C или C++. У всех XPCOM-интерфейсов есть XPIDL-определение; отыскав нужное, вы прочитаете его без труда. Обзор этих интерфейсов может дать некоторое представление обо всех возможностях Mozilla.

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


    об авторе

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





    Сайт создан в системе uCoz