II Клиентский JavaScript

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

13. JavaScript в веб-браузерах

Первая часть этой книги была посвящена базовому языку JavaScript. Теперь мы перейдем к тому языку JavaScript, который используется в веб-браузерах и обычно называется клиентским JavaScript (client-side JavaScript). Большинство примеров, которые мы видели до сих пор, будучи корректным JavaScript-кодом, не зависело от специфического контекста; это были JavaScript-фрагменты, не ориентированные на запуск в какой-либо определенной среде. Данная глава предоставляет такой контекст.

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

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

содержание 13.1. Клиентский JavaScript содержание

Объект Window является средоточием всех особенностей и прикладных интерфейсов клиентского JavaScript. Он представляет окно веб-браузера или фрейм, а сослаться на него можно с помощью идентификатора window. Объект Window определяет свойства, такие как location, которое ссылается на объект location, определяющий URL текущего окна, и позволяет сценарию загружать в окно содержимое других адресов URL:

// Установить значение свойства для перехода на новую веб-страницу
window.location = "http://www.oreilly.com/";

Кроме того, объект Window определяет методы, такие как alert(), который отображает диалог с сообщением, и setTimeout(), который регистрирует функцию для вызова через указанный промежуток времени:

// Ждать 2 секунды и вывести диалог с приветствием
setTimeout(function() { alert("hello world"); }, 2000);

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

Объект Window определяет также множество других важных свойств, методов и конструкторов. Полный их перечень можно найти в главе 14.

Одним из наиболее важных свойств объекта Window является свойство document: оно ссылается на объект Document, который представляет содержимое документа, отображаемого в окне. Объект Document имеет важные методы, такие как getElementById(), который возвращает единственный элемент документа (представляющий пару из открывающего и закрывающего тегов HTML и всего, что содержится между ними), опираясь на значение атрибута id

// Отыскать элемент с атрибутом id="timestamp"
var timestamp = document.getElementById("timestamp");

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

// Если элемент пуст, вставить в него текущую дату и время
if (timestamp.firstChild == null)
timestamp.appendChild(document.createTextNode(new Date().toString()));

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

Каждый объект Element имеет свойства style и className, позволяющие определять стили CSS элемента документа или изменять имена классов CSS, применяемых к элементу. Установка этих свойств, имеющих отношение к CSS, изменяют визуальное представление элемента документа:

// Явно изменить представление элемента заголовка
timestamp.style.backgroundColor = "yellow";
// Или просто изменить класс и позволить определять особенности
// визуального представления с помощью каскадных таблиц стилей:
timestamp.className = "highlight";

Свойства style и className, а также другие приемы управления стилями CSS описываются в главе 16.

Другим важным множеством свойств объектов Window, Document и Element являются свойства, ссылающиеся на обработчики событий. Они позволяют сценариям определять функции, которые должны вызываться асинхронно при возникновении определенных событий. Обработчики событий позволяют программам на языке JavaScript изменять поведение окон, документов и элементов, составляющих документы. Свойства, ссылающиеся на обработчики событий, имеют имена, начинающиеся с «on», и могут использоваться, как показано ниже:

// Обновить содержимое элемента timestamp, когда пользователь щелкнет на нем
timestamp.onclick = function() {
    this.innerHTML = new Date().toString();
    timestamp.style.backgroundColor = "yellow";
    timestamp.className = "highlight";
}

Одним из наиболее важных обработчиков событий является обработчик onload объекта Window. Это событие возникает, когда содержимое документа, отображаемое в окне, будет загружено полностью и станет доступно для выполнения манипуляций. Программный код на языке JavaScript обычно заключается в обработчик события onload. События являются темой главы 17. Пример 13.1 использует обработчик onload и демонстрирует дополнительные приемы получения ссылок на элементы документа, изменения классов CSS и определения обработчиков других событий в клиентском JavaScript. В этом примере программный код на языке JavaScript заключен в HTML-тег <script>. Подробнее этот тег описывается в разделе 13.2. Обратите внимание, что в этом примере имеется определение функции, заключенное в определение другой функции. Вложенные функции часто используются в клиентских сценариях на языке JavaScript, особенно в виде обработчиков событий.

Пример 13.1. Простой клиентский сценарий на языке JavaScript, исследующий содержимое документа

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

содержание 13.1.1. Сценарии JavaScript в веб-документах

Программы на языке JavaScript могут манипулировать содержимым документа через объект Document и содержащиеся в нем объекты Element. Они могут изменять визуальное представление содержимого, управляя стилями и классами CSS, и определять поведение элементов документа, регистрируя соответствующие обработчики событий. Комбинация управляемого содержимого, представления и поведения называется динамическим HTML (Dynamic HTML, или DHTML), а приемы создания документов DHTML описываются в главах 15, 16 и 17.

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

содержание 13.1.2. Сценарии JavaScript в веб-приложениях

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

Чтобы понять суть веб-приложений, важно осознать, что веб-браузеры развивались не только как инструменты для отображения документов и давно уже трансформировались в некоторое подобие простых операционных систем. Сравните: традиционные операционные системы позволяют создавать ярлыки (представляющие файлы и приложения) на рабочем столе и в папках. Веб-браузеры позволяют создавать закладки (представляющие документы и веб-приложения) на панели инструментов и в папках. Операционные системы выполняют множестве приложений в отдельных окнах; веб-браузеры отображают множество документов (или приложений) в отдельных вкладках. Операционные системы определяют низкоуровневые API для организации сетевых взаимодействий, рисования графики и сохранения файлов. Веб-браузеры определяют низкоуровневые API для организации сетевых взаимодействий (глава 18), сохранения данных (глава 20) и рисования графики (глава 21).

Представляя веб-браузеры как упрощенные операционные системы, веб-приложения можно определить как веб-страницы, в которых используется программный код на языке JavaScript для доступа к расширенным механизмам (таким как сетевые взаимодействия, рисование графики и сохранение данных) браузеров. Самым известным из этих механизмов является объект XMLHttpRequest, который обеспечивает сетевые взаимодействия посредством управляемых HTTP-запросов. Веб-приложения используют этот механизм для получения новой информации с сервера без полной перезагрузки страницы. Веб-приложения, использующие этот прием, часто называют Ajax-приложениями, и они образуют фундамент того, что известно под названием «Web 2.0». Объект XMLHttpRequest во всех подробностях рассматривается в главе 18.

Спецификация HTML5 (которая на момент написания этих строк еще находилась в состоянии проекта) и ряд связанных с ней спецификаций определяют ряд других важных прикладных интерфейсов для веб-приложений. В их число входят прикладные интерфейсы для сохранения данных и рисования графики, которые описываются в главах 21 и 20, а также множество других возможностей, таких как геопозиционирование (geolocation), управление журналами посещений и фоновые потоки выполнения. Когда все эти прикладные интерфейсы будут реализованы, они обеспечат дальнейшее расширение возможностей веб-приложений. Подробнее о них рассказывается в главе 22.

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

Интерактивные веб-страницы, взаимодействующие с серверными CGI-сценариями посредством форм HTML также можно считать «веб-приложениями», и они могут быть написаны без применения JavaScript. Однако это не тот тип веб-приложений, которые мы будем обсуждать в этой книге.

содержание 13.2. Встраивание JavaScript-кода в разметку HTML содержание

Клиентский JavaScript-код может встраиваться в HTML-документы четырьмя способами:

В следующих далее подразделах описываются все четыре способа встраивания программного кода на языке JavaScript. Следует отметить, что HTML-атрибуты обработчиков событий и адреса URL с псевдопротоколом javascript: редко используются в современной практике программирования на языке JavaScript (они были более распространены на раннем этапе развития Всемирной паутины). Встроенные сценарии (в тегах <script> без атрибута src) также стали реже использоваться по сравнению с прошлым. Согласно философии программирования, известной как ненавязчивый JavaScript (unobtrusive JavaScript), содержимое (разметка HTML) и поведение (программный код на языке JavaScript) должны быть максимально отделены друг от друга. Следуя этой философии программирования, сценарии на языке JavaScript лучше встраивать в HTML-документы с помощью элементов <script>, имеющих атрибут src.

содержание 13.2.1. Элемент <script>

Клиентские JavaScript-сценарии могут встраиваться в HTML-файлы между тегами <script> и </script>:

<script>
// Здесь располагается JavaScript-код
</script>

В языке разметки XHTML содержимое тега <script> обрабатывается наравне с содержимым любого другого тега. Если JavaScript-код содержит символы < или &, они интерпретируются как элементы XML-разметки. Поэтому в случае применения языка XHTML лучше помещать весь JavaScript-код внутрь секции CDATA:

<script> <![CDATA[
// Здесь располагается JavaScript-код
]]> </script>

В примере 13.2 демонстрируется содержимое HTML-файла, включающего простую программу на языке JavaScript. Действия программы описываются в комментариях, тем не менее замечу, что главная цель этого примера в том, чтобы продемонстрировать, как JavaScript-код встраивается в файлы HTML наряду со всем остальным, в данном случае – со стилями CSS. Обратите внимание, что этот пример по своей структуре напоминает пример 13.1 и точно так же использует обработчик события onload.

Пример 13.2. Простые часы с цифровым табло на JavaScript

содержание 13.2.2. Сценарии во внешних файлах

Тег <script> поддерживает атрибут src, который определяет URL-адрес файла, содержащего JavaScript-код. Используется он следующим образом:

<script src="../•./scripts/util.js"></script>

Файл JavaScript-кода обычно имеет расширение .js и содержит JavaScript-код в «чистом виде» без тегов <script> или любого другого HTML-кода.

Тег <script> с атрибутом src ведет себя точно так, как если бы содержимое указанного файла JavaScript-кода находилось непосредственно между тегами <script> и </script>. Обратите внимание, что закрывающий тег </script> обязателен, даже когда указан атрибут src и между тегами отсутствует JavaScript-код. В разметке XHTML в подобных случаях можно использовать единственный тег </script>.

При использовании атрибута src любое содержимое между открывающим и закрывающим тегами <script> игнорируется. При желании в качестве содержимого в тег <script> можно вставлять описание включаемого программного кода или информацию об авторском праве. Однако следует заметить, что инструменты проверки соответствия разметки требованиям стандарта HTML5 будут выдавать предупреждения, если между тегами <script src=""> и </script> будет находиться какой-либо текст, не являющийся пробельными символами или комментариями на языке JavaScript.

Использование тега с атрибутом src дает ряд преимуществ:

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

содержание 13.2.3. Тип сценария

JavaScript изначально был языком сценариев для Всемирной паутины, и по умолчанию предполагалось, что элементы <script> содержат или ссылаются на программный код на языке JavaScript. Если у вас появится необходимость использовать нестандартный язык сценариев, такой как VBScript корпорации Microsoft (который поддерживается только в Internet Explorer), необходимо в атрибуте type указать MIME-тип сценария:

<script type="text/vbscript">
' Здесь располагается VBScript-код
</script>

По умолчанию атрибут type получает значение «text/JavaScript». При желании можно явно указать это значение, однако в этом нет необходимости.

В старых браузерах вместо атрибута type использовался атрибут language тега <script>, и вы по-прежнему можете встретить веб-страницы, включающие такие теги:

<script language="javascript">
// Здесь располагается JavaScript-код...
</script>

Атрибут language считается устаревшим и не должен более использоваться.

Когда веб-браузер встречает элемент <script> с атрибутом type, значение которого он не может распознать, он пытается проанализировать элемент, но не отображает и не выполняет его содержимое. Это означает, что элемент <script> можно использовать для встраивания в документ произвольных текстовых данных: достаточно просто указать значение атрибута type, указывающее, что данные не являются выполняемым программным кодом. Чтобы извлечь эти данные, можно воспользоваться свойством text объекта HTMLElement, представляющего элемент script (как получить эти элементы, описывается в главе 15). Однако важно отметить, что такой прием встраивания данных работает только при непосредственном встраивании их в разметку. Если указать атрибут src и неизвестное значение в атрибуте type, браузер проигнорирует этот тег и ничего не будет загружать с указанного адреса URL.

содержание 13.2.4. Обработчики событий в HTML. Тип сценария

JavaScript-код, расположенный в теге <script>, исполняется один раз, когда содержащий его HTML-файл считывается в веб-браузер. Для обеспечения интерактивности программы на языке JavaScript должны определять обработчики событий – JavaScript-функции, которые регистрируются в веб-браузере и автоматически вызываются веб-браузером в ответ на определенные события (такие как ввод данных пользователем). Как было показано в начале этой главы, JavaScript- код может регистрировать обработчики событий, присваивая функции свойствам объектов Element (таким как onclick или onmouseover)), представляющих HTML- элементы в документе. (Существует и другой способ регистрации обработчиков событий – подробности приводятся в главе 17.)

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

<input type="checkbox" name="options" value="giftwrap"
             onchange="order.options.giftwrap = this.checked;">

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

Атрибуты обработчиков событий, включенных в разметку HTML, могут содержать одну или несколько JavaScript-инструкций, отделяемых друг от друга точками с запятой. Эти инструкции будут преобразованы интерпретатором в тело функции, которая в свою очередь станет значением соответствующего свойства обработчика события. (Подробное описание, как выполняется преобразование тек; стового содержимого HTML-атрибутов в функции на языке JavaScript, приводится в разделе 17.2.2.) Однако обычно в HTML-атрибуты обработчиков событий включаются простые инструкции присваивания, как в примере выше, или простые вызовы функций, объявленных где-то в другом месте. Это позволяет держать большую часть JavaScript-кода внутри сценариев и ограничивает степень взаимопроникновения JavaScript- и HTML-кода. На практике многие веб-разработчики считают плохим стилем использование HTML-атрибутов обработчиков событий и предпочитают отделять содержимое от поведения.

содержание 13.2.5. JavaScript в URLs

Еще один способ выполнения JavaScript-кода на стороне клиента – включение этого кода в URL-адресе вслед за спецификатором псевдопротокола javascript:. Этот специальный тип протокола обозначает, что тело URL-адреса представляет собою произвольный JavaScript-код, который должен быть выполнен интерпретатором JavaScript. Он интерпретируется как единственная строка, и потому инструкции в ней должны быть отделены друг от друга точками с запятой, а для комментариев следует использовать комбинации символов /* */, а не //. «Ресурсом», который определяется URL-адресом javascript:, является значение, возвращаемое этим программным кодом, преобразованное в строку. Если программный код возвращает значение undefined, считается, что ресурс не имеет содержимого.

URL вида javascript: можно использовать везде, где допускается указывать обычные URL: в атрибуте href тега <a> , в атрибуте action тега <form> и даже как аргумент метода, такого как window.open(). Например, адрес URL с программным кодом на языке JavaScript в гиперссылке может иметь такой вид:

<a href="javascript:new Date().toLocaleTimeString();">
Который сейчас час?
</a>

Некоторые браузеры (такие как Firefox) выполняют программный код в URL и используют возвращаемое значение в качестве содержимого нового отображаемого документа. Точно так же, как при переходе по ссылке http:, браузер стирает текущий документ и отображает новое содержимое. Значение, возвращаемое последним примером, не содержит HTML-теги, но если бы они имелись, браузер мог бы отобразить их точно так же, как любой другой HTML-документ, загруженный в браузер.

Который сейчас час?

Другие браузеры (такие как Chrome и Safari) не позволяют URL-адресам, как в последнем примере, затирать содержимое документа – они просто игнорируют возвращаемое значение. Однако они поддерживают URL-адреса вида:

<a href="javascript:alert(new Date().toLocaleTimeString());">
Узнать время, не затирая документ
</a>

Check the time without overwriting the document

Когда загружается такой URL-адрес, браузер выполняет JavaScript-код, но, т. к. он не имеет возвращаемого значения (метод alert() возвращает значение undefined), такие браузеры, как Firefox, не затирают текущий отображаемый документ. (В данном случае URL-адрес javascript: служит той же цели, что и обработчик события onclick. Ссылку выше лучше было бы выразить как обработчик события onclick элемента <button> – элемент <a> в целом должен использоваться только для гиперссылок, которые загружают новые документы.) Если необходимо гарантировать, что URL-адрес javascript: не затрет документ, можно с помощью оператора void обеспечить принудительный возврат значения undefined:

<a href="javascript:void window.open('about:blank');"> Open Window<a>

Без оператора void в этом URL-адресе значение, возвращаемое методом Window.open(), было бы преобразовано в строку и (в некоторых браузерах) текущий документ был бы затерт новым документом с текстом:

[object Window]

Подобно HTML-атрибутам обработчиков событий, URL-адреса javascript: являются пережитком раннего периода развития Веб и не должны использоваться в современных HTML-страницах. URL-адреса javascript: могут сослужить полезную службу, если использовать их вне контекста HTML-документов. Если потребуется проверить работу небольшого фрагмента JavaScript-кода, можно ввести URL-адрес javascript: непосредственно в адресную строку браузера. Другое узаконенное применение URL-адресов javascript: – создание закладок в браузерах, как описывается ниже.

13.2.5.1. Букмарклеты

«Закладкой» в веб-браузере называется сохраненный URL-адрес. Если закладка содержит URL-адрес javascript:, такая закладка играет роль мини-программы на языке JavaScript, которая называется букмарклетом (bookmarklet). Букмарклеты легко можно запустить из меню или панели инструментов. Программный код в букмарклете выполняется, как если бы он являлся сценарием в странице; он может читать и изменять содержимое документа, его представление и поведение. Если букмарклет не возвращает какое-либо значение, он может манипулировать содержимым любого отображаемого документа, не замещая его новым содержимым.

Взгляните на следующий фрагмент URL javascript: в теге <a>. Щелчок на ссылке открывает простейший обработчик JavaScript-выражений, который позволяет вычислять выражения и выполнять инструкции в контексте страницы:

Обратите внимание: несмотря на то что этот программный код записан в нескольких строках, синтаксический анализатор разметки HTML обработает его как одну строку, а потому однострочные комментарии (//) здесь работать не будут. Кроме того, не забывайте, что весь этот программный код является частью значения HTML-атрибута, заключенного в одиночные кавычки, поэтому этот программ; ный код не может содержать одиночные кавычки.

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

содержание 13.3. Выполнение JavaScript-программ содержание

Вообще говоря, не существует формального определения программы на клиентском языке JavaScript. Можно лишь сказать, что программой является весь программный код на языке JavaScript, присутствующий в веб-странице (встроенные сценарии, обработчики событий в разметке HTML и URL-адреса javascript:), а также внешние сценарии JavaScript, на которые ссылаются атрибуты src тегов <script>. Все эти отдельные фрагменты программного кода совместно используют один и тот же глобальный объект Window. Это означает, что все они видят один и тот же объект Document и совместно используют один и тот же набор глобальных функций и переменных: если сценарий определяет новую глобальную переменную или функцию, эта переменная или функция будет доступна любому программному коду на языке JavaScript, который будет выполняться после этого сценария.

Если веб-страница содержит встроенный фрейм (элемент <iframe>), JavaScript-код во встроенном документе будет работать с другим глобальным объектом, отличным от глобального объекта в объемлющем документе, и его можно рассматривать как отдельную JavaScript-программу. Однако напомню, что не существует формального определения, устанавливающего границы JavaScript-программы. Если оба документа, вмещающий и вложенный, получены с одного сервера, то программный код в одном документе сможет взаимодействовать с программным кодом в другом документе и их можно считать взаимодействующими частями одной программы. Подробнее о глобальном объекте Window и о взаимодействии программ, выполняющихся в разных окнах и фреймах, рассказывается в разделе 14.8.3.

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

Программы на языке JavaScript выполняются в два этапа. На первом этапе производится загрузка содержимого документа и запускается программный код в элементах <script> (и встроенные сценарии, и внешние). Обычно (но не всегда – подробнее об этом рассказывается в разделе 13.3.1) сценарии выполняются в порядке их следования в документе. Внутри каждого сценария программный код выполняется последовательно, от начала до конца, с учетом условных инструкций, циклов и других инструкций управления потоком выполнения.

После загрузки документа и выполнения всех сценариев начинается второй этап выполнения JavaScript-программы, асинхронный и управляемый событиями. На протяжении этого этапа, управляемого событиями, веб-браузер вызывает функции обработчиков (которые определены в HTML-атрибутах обработчиков событий, установлены сценариями, выполненными на первом этапе, или обработчиками событий, вызывавшимися ранее) в ответ на события, возникающие асинхронно. Обычно обработчики событий вызываются в ответ на действия пользователя (щелчок мышью, нажатие клавиши и т. д.), но могут также вызываться в ответ на сетевые взаимодействия, по истечении установленного промежутка времени или при возникновении ошибочных ситуаций в JavaScript-коде. Подробнее о событиях и обработчиках событий рассказывается в главе 17. Некоторая дополнительная информация о них будет также приводиться в разделе 13.3.2. Обратите внимание, что URL-адреса javascript:, встроенные в веб-страницу, также можно отнести к категории обработчиков событий, т. к. они не выполняются, пока не будут активированы действиями пользователя, такими как щелчок мышью на ссылке или отправка формы на сервер.

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

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

содержание 13.3.1. Синхронные, асинхронные и отложенные сценарии

Когда поддержка JavaScript впервые появилась в веб-браузерах, не существовало никаких инструментов обхода и управления структурой содержимого документа. Единственный способ, каким JavaScript-код мог влиять на содержимое документа, – это генерировать содержимое в процессе загрузки документа. Делалось это с помощью метода document.write(). В примере 13.3 показано, как выглядел ультрасовременный JavaScript-код в 1996 году.

Пример 13.3. Генерация содержимого документа во время загрузки

Когда сценарий передает текст методу document.write() , этот текст добавляется во входной поток документа, и механизм синтаксического анализа разметки HTML действует так, как если бы элемент <script> был замещен этим текстом. Использование метода document.write() более не считается хорошим стилем программирования, но его применение по-прежнему возможно (раздел 15.10.2), и этот факт имеет важное следствие. Когда механизм синтаксического анализа разметки HTML встречает элемент <script>, он должен, по умолчанию, выполнить сценарий, прежде чем продолжить разбор и отображение документа. Это не является проблемой для встроенных сценариев, но если сценарий находится во внешнем файле, на который ссылается атрибут src, это означает, что часть документа, следующая за сценарием, не появится в окне браузера, пока сценарий не будет загружен и выполнен.

Такой синхронный, или блокирующий, порядок выполнения действует только по умолчанию. Тег <script> может иметь атрибуты defer и async, которые (в браузерах, поддерживающих их) определяют иной порядок выполнения сценариев. Это логические атрибуты – они не имеют значения; они просто должны присутствовать в теге <script>. Согласно спецификации HTML5 эти атрибуты принимаются во внимание, только когда используются вместе с атрибутом src, однако некоторые браузеры могут поддерживать атрибут defer и для встроенных сценариев:

<scriptdefer src="deferred.js"> </script>
<scriptasync src="async.js"> </script>

Оба атрибута, defer и async, сообщают браузеру, что данный сценарий не использует метод document.write() и не генерирует содержимое документа, и что браузер может продолжать разбор и отображение документа, пока сценарий загружается. Атрибут defer заставляет браузер отложить выполнение сценария до момента, когда документ будет загружен, проанализирован и станет готов к выполнению операций. Атрибут async заставляет браузер выполнить сценарий, как только это станет возможно, но не блокирует разбор документа на время загрузки сценария. Если тег <script> имеет оба атрибута, браузер, поддерживающий оба этих атрибута, отдаст предпочтение атрибуту async и проигнорирует атрибут defer.

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

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

Имеется возможность загружать и выполнять сценарии асинхронно, даже если браузер не поддерживает атрибут async, для чего достаточно динамически создать элемент <script> и вставить его в документ. Это действие реализует функция loadasync(), представленная в примере 13.4. Используемые ею приемы описываются в главе 15.

Пример 13.4. Асинхронная загрузка и выполнение сценария

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

содержание 13.3.2. Выполнение, управляемое событиями

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

События и обработка событий – это тема главы 17, а данный раздел содержит лишь краткий обзор. События имеют имена, такие как «click», «change», «load», «mouseover», «keypress» или «readystatechange», указывающие общий тип события. События также имеют адресата – объект, в котором возникло событие. Ведя речь о событии, необходимо указывать не только его тип (имя), но и адресата, например: событие «click» в oбъeктe HTMLButtonElement или coбытиe «readystatechange» в объекте XMLHttpRequest.

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

window.onload = function() { ... };
document.getElementById("button1").onclick = function() { ... };
function handleResponse() { ... }
request.onreadystatechange = handleResponse;

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

В большинстве браузеров для большинства типов событий обработчики получают в виде аргумента объект, свойства которого содержат информацию о событии. Например, объект, передаваемый обработчику события «click», будет иметь свойство, определяющее кнопку мыши, которой был выполнен щелчок (в IE информация о событии хранится в глобальном объекте и не передается функции-обработчику). Иногда значение, возвращаемое обработчиком события, используется, чтобы определить, достаточно ли ограничиться выполненной обработкой события и следует ли предотвратить выполнение действий по умолчанию, предусматриваемых браузером.

События, адресатом которых является элемент документа, часто распространяются вверх по дереву документа, и этот процесс называется «всплытием». Если, например, пользователь щелкнет мышью на элементе <button>, событие «click» будет передано кнопке. Если это событие останется необработанным (и его распространение не будет остановлено) функцией, зарегистрированной в элементе кнопки, событие всплывет до элемента, в который вложена эта кнопка, и будет вызван обработчик события «click», зарегистрированный в этом контейнерном элементе.

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

window.addEventListener("load", function() {...}, false);
request.addEventListener("readystatechange", function() {...}, false);

Обратите внимание, что первым аргументом этой функции передается имя события. Несмотря на то, что метод addEventListener() был определен стандартом уже более десяти лет тому назад, корпорация Microsoft только недавно реализовала его в IE9.  В IE8 и в более ранних версиях необходимо использовать похожий метод, который называется attachEvent()::

window.attachEvent("onload", function() {...});

Подробнее о функциях addEventListener() и attachEvent() рассказывается в главе 17.

Клиентские JavaScript-программы используют еще одну разновидность асинхронных извещений, которые, строго говоря, не являются событиями. Если свойству onerror объекта Window присвоить функцию, она будет вызываться при появлении ошибочных ситуаций (или необработанных исключений) в программном коде (раздел 14.6). Кроме того, функции setTimeout() и setInterval() (они являются методами глобального объекта Window и потому в клиентском JavaScript считаются глобальными функциями) вызывают указанные им функции по истечении определенного интервала времени. Функции, которые передаются setTimeout(), регистрируются не так, как настоящие обработчики событий, и обычно они называются «функциями обратного вызова», а не «обработчиками», но они, как и обработчики событий, выполняются асинхронно. Подробнее о функциях setTimeout() и setInterval() рассказывается в разделе 14.1.

Пример 13.5 демонстрирует применение функций setTimeout(), addEventListener() и attachEvent() внутри функции onLoad(), которая регистрирует обработчик события окончания загрузки документа.
onLoad() – весьма полезная функция, и мы часто будем использовать ее в примерах на протяжении оставшейся части книги.

Пример 13.5. onLoad(): вызов функции по окончании загрузки документа

содержание 13.3.3. Модель потоков выполнения в клиентском JavaScript

Ядро языка JavaScript не имеет механизма одновременного выполнения нескольких потоков управления, и клиентский язык JavaScript не добавляет такой возможности. Стандарт HTML5 определяет механизм поддержки фонового потока выполнения «Web Workers» (подробнее об этом механизме рассказывается ниже), тем не менее JavaScript-код на стороне клиента выполняется в единственном потоке управления. Даже когда параллельное выполнение возможно, интерпретатор JavaScript не может обнаружить этот факт.

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

Выполнение в единственном потоке означает, что веб-браузер должен прекратить откликаться на действия пользователя на время выполнения сценария или обработчика события. Это накладывает определенные требования: сценарии и обработчики событий в JavaScript не должны исполняться слишком долго. Если сценарий производит объемные и интенсивные вычисления, это вызовет задержку во время загрузки документа, и пользователь не увидит его содержимое, пока сценарий не закончит свою работу. Если продолжительные по времени операции выполняются в обработчике события, браузер может оказаться неспособным откликаться на действия пользователя, заставляя его думать, что программа «зависла».

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

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

Стандарт HTML5 определяет управляемую форму параллельного выполнения – механизм фонового потока выполнения под названием «web worker». Web worker – это фоновый поток выполнения, предназначенный для выполнения продолжительных вычислений и предотвращения блокирования пользовательского интерфейса. Программный код, выполняемый в фоновом потоке web worker, не имеет доступа к содержимому документа, к информации, используемой главным потоком выполнения или другими потоками web worker, и может взаимодействовать с главным потоком и другими фоновыми потоками только посредством асинхронных событий. Благодаря этому параллельное выполнение не оказывает влияния на главный поток, а фоновые потоки не меняют базовую однопоточную модель выполнения JavaScript-программ. Подробнее о фоновых потоках выполнения рассказывается в разделе 22.4.

содержание 13.3.4. Последовательность выполнения клиентских сценариев

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

  1. Веб-браузер создает объект Document и начинает разбор веб-страницы, добавляя в документ объекты Element и текстовые узлы в ходе синтаксического анализа HTML-элементов и их текстового содержимого. На этой стадии свойство document.readyState получает значение «loading».
  2. Когда механизм синтаксического анализа HTML встречает элементы <script> , не имеющие атрибута async и/или defer, он добавляет эти элементы в документ и затем выполняет встроенные или внешние сценарии. Эти сценарии выполняются синхронно, а на время, пока сценарий загружается (если это необходимо) и выполняется, синтаксический анализ документа приостанавливается. Такие сценарии могут использовать метод document.write() для вставки текста во входной поток. Этот текст станет частью документа, когда синтаксический анализ продолжится. Синхронные сценарии часто просто определяют функции и регистрируют обработчики событий для последующего использования, но они могут исследовать и изменять дерево документа, доступное на момент их запуска. То есть синхронные сценарии могут видеть собственный элемент <script> и содержимое документа перед ним.
  3. Когда механизм синтаксического анализа встречает элемент <script> , имеющий атрибут async, он начинает загрузку сценария и продолжает разбор документа. Сценарий будет выполнен сразу же по окончании его загрузки, но синтаксический анализ документа не приостанавливается на время загрузки сценария. Асинхронные сценарии не должны использовать метод document.write(). Они могут видеть собственный элемент <script> , все элементы документа,
  4. По окончании анализа документа значение свойства document.readyState изменяется на «interactive».
  5. Выполняются все сценарии, имеющие атрибут defer, в том порядке, в каком они встречаются в документе. В этот момент также могут выполняться асинхронные сценарии. Отложенные сценарии имеют доступ к полному дереву документа и не должны использовать метод document.write().
  6. Браузер возбуждает событие «DOMContentLoaded» в объекте Document. Это событие отмечает переход от этапа синхронного выполнения сценариев к управляемому событиями асинхронному этапу выполнения программы. Следует, однако, отметить, в этот период также могут выполняться асинхронные сценарии, которые не были еще выполнены.
  7. К этому моменту синтаксический анализ документа завершен, но браузер все еще может ожидать окончания загрузки дополнительного содержимого, такого как изображения. Когда все содержимое будет загружено и все асинхронные сценарии будут выполнены, свойство document.readyState получит значение «complete» и веб-браузер возбудит событие «load» в объекте Window.
  8. С этого момента будут асинхронно вызываться обработчики событий в ответ на действия пользователя, сетевые операции, истечение таймера и т. д.

Это идеализированная последовательность выполнения, и не все браузеры придерживаются ее в точности. Событие «load» поддерживается повсеместно: его возбуждают все браузеры, и оно является универсальным инструментом определения момента окончания загрузки документа и его готовности к выполнению операций. Событие «DOMContentLoaded» возбуждается перед событием «load» и поддерживается всеми текущими браузерами, кроме IE. Свойство document.readyState реализовано в большинстве текущих браузеров на момент написания этих строк, но значения, которые получает это свойство, отличаются между браузерами. Атрибут defer поддерживается всеми современными версиями IE, но только недавно был реализован в других браузерах. Поддержка атрибута async до сих пор не получила широкого распространения, но асинхронное выполнение сценариев с использованием приема, представленного в примере 13.4, поддерживается всеми текущими браузерами (однако имейте в виду, что возможность динамической загрузки сценариев с помощью функции, такой как loadasync(), размывает границы между этапом загрузки сценариев и этапом выполнения программы, управляемым событиями).

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

содержание 13.4. Совместимость на стороне клиента содержание

Веб-браузер – это своего рода операционная система для веб-приложений, но Всемирная паутина – это разнородная среда, и ваши веб-документы и приложения будут просматриваться и выполняться в браузерах разных возрастов (от ультрасовременных бета-версий до браузеров, возраст которых исчисляется десятилетиями, таких как IE6), разных производителей (Microsoft, Mozilla, Apple, Google, Opera) и выполняющихся в разных операционных системах (Windows, Mac OS, Linux, iPhone OS, Android). Поэтому достаточно сложно написать нетривиальную клиентскую программу на языке JavaScript, которая будет корректно работать на таком многообразии платформ.

Проблемы совместимости на стороне клиента делятся на три основные категории:

Эволюционные

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

Отсутствие реализации

Иногда мнения производителей браузеров относительно полезности той или иной особенности расходятся. Некоторые производители реализуют ее, другие – нет. Это не проблема отличий между новыми версиями браузеров, поддерживающих особенность, и старыми версиями, не поддерживающими ее. Эта проблема связана с производителями браузеров, одни из которых решили реализовать особенность, а другие – нет. Например, IE8 не поддерживает элемент <canvas>, хотя все остальные браузеры реализовали его поддержку. Еще более вопиющий пример: корпорация Microsoft долго отказывалась от намерений реализовать спецификацию DOM Level 2 Events (которая определяет метод addEventListener() и связанные с ним). Эта спецификация была выпущена почти десять лет тому назад, и другие производители давно реализовали ее поддержку.
Справедливости ради следует отметить, что версия IE9 поддерживает и элемент <canvas>, и метод addEventListener().

Ошибки

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

К счастью, реализации самого языка JavaScript, выполненные различными производителями браузеров, являются совместимыми и не являются источником описываемых проблем. Все браузеры имеют совместимые реализации ЕСМА-Script 3, и к моменту написания этих строк все производители уже работали над реализацией ECMAScript 5. Переход от ECMAScript 3 к ECMAScript 5 также может породить проблемы несовместимости из-за того, что одни браузеры будут поддерживать строгий режим, а другие – нет, но, как ожидается, производители браузеров обеспечат совместимость своих реализаций ECMAScript 5.

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

https://developer.mozilla.org

Центр разработчиков Mozilla (Mozilla Developer Center)

http://msdn.microsoft.com

Сообщество разработчиков, использующих продукты Microsoft (Microsoft Developer Network)

http://developer.apple.com/safari

Центр разработки Safari (Safari Dev Center) на сайте по связям с разработчиками, использующими продукты Apple (Apple Developer Connection)

http://code.google.com/doctype

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

https://en.wikipedia.org/wiki/Comparison_of_layout_engines_(HTML5)

(Похожая статья в Википедии на русском языке: http://ru.wikipedia.org/wiki/Сравнение_браузеров_(HTML5). – Прим. перев. )
Статья в Википедии, в которой оценивается степень реализации особенностей и API стандарта HTML5 в различных браузерах.

https://en.wikipedia.org/wiki/Comparison_ofi_layout_engines_(Document_Object_Model)

Аналогичная статья, в которой оценивается степень реализации особенностей DOM.

http://a.deveria.com/caniuse

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

http://www.quirksmode.org/dom

Содержит таблицы совместимости различных браузеров со спецификациями W3C DOM.

http://webdevout.net/browser-support

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

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

Конечно, понимание проблем несовместимости между браузерами – это только первый шаг. Далее необходимо решить, как обращаться с этими проблемами. Одна из стратегий заключается в том, чтобы ограничиться использованием только тех особенностей, которые одинаково хорошо реализованы (или легко имитируются) во всех браузерах, поддержку которых вам требуется обеспечить. На эту стратегию опирается веб-сайт «Когда я смогу воспользоваться...», упомянутый выше (http://a.deveria.com/caniuse): здесь перечислены особенности, ставшие пригодными к широкому использованию после того, как сократилась доля браузера IE6 и он перестал занимать ведущее положение на рынке. В следующих подраз» делах описывается несколько менее пассивных стратегий, которые можно использовать для обхода несовместимостей на стороне клиента.

Несколько слов о «текущих браузерах»

Тема клиентского JavaScript изменчива, что стало особенно заметно с появлением ES5 и HTML5. Поскольку платформа развивается очень быстро, я не буду ограничиваться рекомендациями, касающимися конкретных версий тех или иных браузеров: любые такие рекомендации устареют задолго до того, как появится новое издание этой книги. Поэтому вы часто будете видеть, что я специально подстраховываюсь, используя достаточно расплывчатую фразу «все текущие браузеры» (или иногда «все текущие браузеры, кроме IE»). Чтобы добавить конкретики, замечу, что на тот момент, когда я писал эту главу, текущими браузерами (здесь не имеются в виду бета-версии) были:

Когда эта книга поступит в продажу, текущими браузерами, скорее всего, будут: Internet Explorer 9, Firefox 4, Safari 5, Chrome 11 и Opera 11.

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

В пятом издании этой книги вместо выражения «текущие браузеры» использовалось выражение «современные браузеры». То издание было опубликовано в 2006 году, когда текущими браузерами были Firefox 1.5, IE6, Safari 2 и Opera 8.5 (браузер Chrome, созданный компанией Google, тогда еще не существовал). Любые ссылки на «современные браузеры», оставшиеся в этой книге, теперь можно интерпретировать как «все браузеры», потому что версии, более старые, чем эти, практически вышли из употребления.

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

содержание 13.4.1. Библиотеки обеспечения совместимости

Один из самых простых способов избавиться от проблемы несовместимости заключается в использовании библиотек, реализующих обходные решения. Рассмотрим в качестве примера элемент <canvas>, предназначенный для создания графических изображений на стороне клиента (эта тема обсуждается в главе 21). Браузер IE является единственным текущим браузером, не поддерживающим эту особенность. Однако он поддерживает собственный, малоизвестный язык создания графических изображений на стороне клиента, который называется VML, с помощью которого можно было бы имитировать действие элемента <canvas>. Открытым проектом «explorercanvas» (http://code.google.com/p/explorercanvas) была выпущена библиотека, реализующая эту имитацию: достаточно подключить к веб-странице единственный файл excanvas.js с программным кодом на языке JavaScript, и браузер IE будет вести себя так, как если бы он поддерживал элемент <canvas>.

Библиотека excanvas.js может служить ярким примером библиотеки обеспечения совместимости. Точно так же можно написать другие библиотеки, реализующие конкретные особенности. Методы массивов, введенные стандартом ES5 (раздел 7.9), такие как forEach(), map() и reduce(), с успехом можно реализовать в ES3, и добавляя соответствующую библиотеку к страницам, можно получить возможность использовать эти мощные методы как часть базовой платформы любого браузера.

Однако иногда невозможно создать полноценную (или эффективную) реализацию особенности в браузерах, не поддерживающих ее. Как уже упоминалось, IE – единственный браузер, который не реализует стандартный API обработки событий, включая метод addEventListener() регистрации обработчиков. Браузер IE поддерживает похожий метод с именем attachEvent(). Однако метод attachEvent() не такой мощный, как addEventListener(), и в действительности не существует очевидного способа реализовать все стандартные методы на основе возможностей, предоставляемых браузером IE. Вместо этого разработчики иногда определяют компромиссный метод обработки событий – часто давая ему имя addEvent() – который переносимым способом может использовать либо addEventListener(), либо attachEvent(). Затем они пишут свой программный код, использующий метод addEvent() вместо addEventListener() или attachEvent().

На деле многие веб-разработчики в своих веб-страницах используют фреймворки на языке JavaScript, такие как jQuery (описывается в главе 19). Одна из функций, которая делает эти фреймворки такими необходимыми, - определение нового клиентского прикладного интерфейса, совместимого со всеми браузерами. В jQuery, например, регистрация обработчиков событий выполняется с помощью метода bind О. Если вы начнете использовать jQuery во всех своих разработках, вам никогда не придется задумываться о несовместимости методов addEventListener() и attachEvent(). Подробнее о клиентских фреймворках рассказыватся в разделе 13.7.

содержание 13.4.2. Классификация браузеров

Классификация браузеров - это прием тестирования и оценки качества, введенный и отстаиваемой компанией Yahoo!, который привносит определенную долю здравомыслия в иначе неуправляемое разрастание вариантов браузеров разных версий от разных производителей и для разных операционных систем. В двух словах, классификация возможностей браузеров подразумевает выделение на основе тестирования браузеров категории «A», которые обеспечивают полную поддержку всех возможностей, и менее мощных браузеров категории «С». Браузеры категории «A» получают полнофункциональные веб-страницы, а браузеры катет гории «C» - минимальные HTML-версии страниц, в которых не используются сценарии JavaScript и каскадные таблицы стилей CSS. Браузеры, которые не могут быть отнесены к категории «A» или «C», попадают в категорию «X»: обычно это совершенно новые или особенно редкие браузеры. Считается, что браузеры этой категории обеспечивают полную поддержку всех возможностей, и они получают полнофункциональные веб-страницы, однако официально они не поддерживаются и не тестируются.

Подробности о системе классификации возможностей браузеров, используемой компанией Yahoo!, можно найти на странице (http://developer.yahoo.com/yui/articles/gbs). На этой же странице приводится текущий список браузеров, включенных компанией Yahoo! в категории «A» и «C» (этот список обновляется ежеквартально). Даже если вы не собираетесь использовать прием классификации браузеров, список браузеров категории «A» может пригодиться для определения, какие браузеры являются текущими и занимают значительную долю рынка.

содержание 13.4.3. Проверка особенностей

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

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

} Самое главное, что дает проверка особенностей, - программный код, который не привязан к конкретным браузерам или их версиям. Этот прием работает со всеми браузерами, существующими ныне, и должен продолжить работать с будущими версиями браузеров независимо от того, какой набор особенностей они реализуют. Это означает, что производители браузеров должны определять свойства и методы, обладающие полной функциональностью. Если бы корпорация Microsoft определила метод addEventListener(), реализовав спецификации W3C лишь частично, это привело бы к нарушениям работоспособности большого числа сцена; риев, в которых перед вызовом addEventListener() реализован механизм проверки особенностей.

содержание 13.4.4. Режим совместимости и стандартный режим

Когда корпорация Microsoft выпустила браузер IE6, в него была добавлена поддержка некоторых стандартных особенностей CSS, которые не поддерживались в IE5. Однако чтобы обеспечить обратную совместимость с существующими вебстраницами, в нем было реализовано два режима отображения. В «стандартном режиме», или в «режиме совместимости с CSS», браузер следует стандартам CSS. В «режиме совместимости» браузер проявляет нестандартное поведение, свойственное версиям IE4 и IE5. Выбор режима отображения зависит от объявления D0CTYPE в начале HTML-файла. Страницы, вообще не имеющие объявления D0CTYPE и страницы с определенными объявлениями типа документа, типичными в эру использования IE5, отображаются в режиме совместимости. Страницы со строгими объявлениями типа документа (или, для совместимости снизу вверх, с нераспознаваемыми объявлениями типа документа) отображаются в стандартном режиме. Страницы с объявлением, определяемым стандартом HTML5 (<!D0CTYPE html>), отображаются в стандартном режиме во всех современных браузерах.

Такое различие между режимом совместимости и стандартным режимом прошло проверку временем. Новые версии IE по-прежнему реализуют его, как и другие современные браузеры, и существование этих двух режимов было узаконено спецификацией HTML5. Различия между режимом совместимости и стандартным режимом обычно имеют значение только для тех, кто пишет HTML- и CSS-код. Однако иногда клиентским сценариям на языке JavaScript бывает необходимо определить, в каком режиме отображается документ. Определить режим отображения можно с помощью свойства document.compatMode. Если оно имеет значение «CSS1Compat», документ отображается в стандартном режиме. Если оно имеет значение «BackCompat» (или undefined, если такое свойство вообще не существует), документ отображается в режиме совместимости. Все современные браузеры реализуют свойство compatMode, и оно стандартизовано спецификацией HTML5.

На практике необходимость проверять свойство compatMode возникает редко. Тем не менее в примере 15.8 демонстрируется один из случаев, когда эта проверка действительно необходима.

содержание 13.4.5. Проверка типа браузера

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

На стороне клиента сделать это можно с помощью объекта Navigator, о котором рассказывается в главе 14. Программный код, который определяет производителя и версию браузера, часто называют анализатором браузера (browser sniffer) или анализатором клиента (client sniffer). Простой анализатор такого типа приводится в примере 14.3. Методика определения типа клиента широко использовалась на ранних этапах развития Всемирной паутины, когда Netscape и IE имели серьезные отличия и были несовместимы. Ныне ситуация с совместимостью стабилизировалась, и анализ типа клиента утратил свою актуальность и проводится лишь в тех случаях, когда это действительно необходимо.

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

содержание 13.4.6. Условные комментарии в Internet Explorer

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

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

В качестве более конкретного примера возьмем библиотеку excanvas.js, о которой выше говорилось, что она реализует поддержку элемента <canvas> в Internet Explorer. Поскольку эта библиотека требуется, только когда веб-страница отображается в IE (и работает только в IE), есть смысл оформлять ее подключение внутри условного комментария, чтобы другие браузеры даже не загружали ее:

<!--[if IE]> <script src="excanvas.js"> </script> <[endif]-->

Условные комментарии также поддерживаются интерпретатором JavaScript в IE. Программисты, знакомые с языком C/C++, найдут их похожими на инструкции препроцессора #ifdef/#endif. Условные JavaScript-комментарии в IE начинаются с комбинации символов /*@cc_on и завершаются комбинацией @*/ (префиксы «cc» и «cc_on» происходят от фразы «condition compilation», т. е. «условная компиляция»). Следующий условный комментарий содержит программный код, который будет выполняться только в IE:

/*@cc_on
  @if (@_jscript)
    // Следующий код находится внутри JS-комментария, но IE выполнит его.
    alert("In IE");
  @end
@*/

Внутри условных комментариев могут указываться ключевые слова @if, @else и @end, предназначенные для отделения программного кода, который должен выполняться интерпретатором JavaScript в IE по определенному условию. В большинстве случаев вам достаточно будет использовать показанное в предыдущем фрагменте условие @if (@_jscript). JScript – это название интерпретатора JavaScript, которое было дано ему в Microsoft, а переменная @_jscript в IE всегда имеет значение true.

При грамотном чередовании условных и обычных JavaScript-комментариев можно определить, какой блок программного кода должен выполняться в IE, а какой – во всех остальных браузерах:

содержание 13.5. Доступность содержание

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

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

Другое важное замечание относительно доступности касается пользователей, которые могут работать с клавиатурой, но не могут (или не хотят) применять указывающие устройства, такие как мышь. Если программный код ориентирован на события, возникающие от действий мышью, вы ограничиваете доступность страницы для тех, кто не пользуется мышью. Веб-браузеры позволяют задействовать клавиатуру для перемещения по веб-странице и выполнять операции с элементами графического интерфейса в них, то же самое должен позволять делать и ваш JavaScript-код. Как демонстрируется в главе 17, наряду с поддержкой событий, зависящих от типа устройства, таких как onmouseover или onmousedown, JavaScript обладает поддержкой событий, не зависящих от типа устройства, таких как onfocus и onchange. Для достижения максимальной доступности следует отдавать предпочтение событиям, не зависящим от типа устройства.

Создание максимально доступных веб-страниц – нетривиальная задача, а обсуждение проблем обеспечения доступности выходит за рамки этой книги. Разработчики веб-приложений, для которых проблемы доступности имеют немаловажное значение, должны ознакомиться со стандартами WAI-ARIA (Web Accessibility Initiative – Accessible Rich Internet Applications) http://www.w3.org/WAI/intro/aria

содержание 13.6. Безопасность содержание

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

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

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

содержание 13.6.1. Чего не может JavaScript

Браузеры – это первая линия обороны против злонамеренного кода, поэтому они просто не поддерживают некоторые функциональные возможности. Например, клиентский JavaScript не предоставляет никакого способа записи или удаления файлов и каталогов на клиентском компьютере. То есть программа на языке JavaScript не может удалить данные или установить вирус. (Тем не менее в разделе 22.6.5 вы узнаете, как на языке JavaScript реализовать чтение файлов, выбранных пользователем, а в разделе 22.7 – как настроить безопасную частную файловую систему, в пределах которой программы на языке JavaScript смогут работать с файлами.)

Аналогично клиентский JavaScript не имеет универсальных механизмов сетевых взаимодействий. Клиентский сценарий на языке JavaScript может управлять протоколом HTTP (как описывается в главе 18). А другой стандарт, примыкающий к стандарту HTML5, известный как WebSockets, определяет прикладной программный интерфейс, напоминающий сокеты, позволяющий взаимодействовать со специализированными серверами. Но ни один из этих интерфейсов не позволяет получить непосредственный доступ к сети. На клиентском JavaScript нельзя написать программу универсального сетевого клиента или сервера.

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

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

содержание 13.6.2. Политика общего происхождения

Политикой общего происхождения называется радикальное ограничение, связанное с безопасностью, накладываемое на веб-содержимое, с которым может взаимодействовать JavaScript-код. Обычно политика общего происхождения вступает в игру, когда веб-страница содержит элементы <iframe> или открывает другие окна браузера. В этом случае политика общего происхождения ограничивает возможность JavaScript-кода в одном окне взаимодействовать с содержимым в других окнах и фреймах. В частности, сценарий может читать только свойства окон и документов, имеющих общее с самим сценарием происхождение (о том, как использовать JavaScript для работы с несколькими окнами и фреймами, рассказывается в разделе 14.8).

Происхождение документа определяется протоколом, именем хоста и номером порта URL-адреса, откуда был загружен документ. Документы, загружаемые с других веб-серверов, имеют другое происхождение. Документы, загруженные с разных портов одного и того же хоста, также имеют другое происхождение. Наконец, документы, загруженные по протоколу http:, по происхождению отличаются от документов, загруженных по протоколу https:, даже если загружены с одного и того же веб-сервера.

Важно понимать, что происхождение самого сценария не имеет никакого отношения к политике общего происхождения: значение имеет происхождение документа, в который встраивается сценарий. Предположим, что сценарий, хранящийся на сервере A, включается (с помощью атрибута src элемента <script> ) в вебстраницу, обслуживаемую сервером B. С точки зрения политики общего происхождения будет считаться, что этот сценарий происходит с сервера B, и он получит полный доступ ко всему содержимому этого документа. Если этот сценарий откроет второе окно и загрузит в него документ с сервера B, он также будет иметь полный доступ к содержимому этого второго документа. Но если сценарий откроет третье окно и загрузит в него документ с сервера С (или даже с сервера A), в дело вступит политика общего происхождения и ограничит сценарий в доступе к этому документу.

Политика общего происхождения на самом деле применяется не ко всем свойствам всех объектов в окне, имеющем другое происхождение, но она применяется ко многим из них, в частности, практически ко всем свойствам объекта Document. В любом случае можно считать, что любое окно или фрейм, содержащий документ, полученный с другого сервера, для ваших сценариев будут закрыты. Если такое окно было открыто самим сценарием, он сможет закрыть его, но не может «заглянуть внутрь» окна. Кроме того, политика общего происхождения действует при работе по протоколу HTTP с применением объекта XMLHttpRequest (глава 18). Этот объект позволяет JavaScript-сценариям, выполняющимся на стороне клиента, отправлять произвольные HTTP-запросы, но только тому веб-серверу, откуда был загружен документ, содержащий сценарий.

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

13.6.2.1. Ослабление ограничений политики общего происхождения

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

Политика общего происхождения создает определенные проблемы для крупных веб-сайтов, которые функционируют на нескольких серверах. Например, сценарий с сервера home.example.com мог бы на вполне законных основаниях читать свойства документа, загруженного с developer.example.com, а сценариям с orders. example.com может потребоваться прочитать свойства из документов с catalog. example.com. Для поддержки таких крупных веб-сайтов можно использовать свойство domain объекта Document. По умолчанию свойство domain содержит имя сервера, с которого был загружен документ. Это свойство можно установить только равным строке, являющейся допустимым доменным суффиксом первоначального значения. Другими словами, если значение свойства domain первоначально было равно строке «home.example.com», то можно установить его равным «example.com», но не «home.example» или «ample.com». Кроме того, значение свойства domain должно содержать, по крайней мере, одну точку, чтобы его нельзя было установить равным «com» или другому имени домена верхнего уровня.

Если два окна (или фрейма) содержат сценарии, установившие одинаковые значения свойства domain, политика общего происхождения для этих двух окон ослабляется, и каждое из окон может читать значения свойств другого окна. Например, взаимодействующие сценарии в документах, загруженных с серверов orders. example.com и catalog.example.com, могут установить свойства document.domain равными «example.com», тем самым указывая на общность происхождения документов и разрешая каждому из документов читать свойства другого.

Второй прием ослабления ограничений политики общего происхождения предполагается стандартизовать под названием «Cross-Origin Resource Sharing» (http://www.w3.org/TR/cors/). Этот проект стандарта дополняет протокол HTTP новым заголовком запроса Origin: и новым заголовком ответа Access-Control-Allow-Origin. Он позволяет серверам использовать заголовок для явного определения списка доменов, которые могут запрашивать файл, или использовать шаблонные символы, чтобы обеспечить возможность получения файла любым сайтом. Браузеры, такие как Firefox 3.5 и Safari 4, используют этот новый заголовок, чтобы разрешить выполнение междоменных HTTP-запросов с использованием объекта XML-HttpRequest, которые иначе были бы невозможны из-за ограничений политики общего происхождения.

Еще один новый прием, известный как «обмен сообщениями между документами» (cross-document messaging), позволяет сценарию из одного документа передавать текстовые сообщения сценарию в другом документе независимо от домена происхождения сценария. Вызов метода postMessage() объекта Window производит асинхронную отправку сообщения (получить которое можно в обработчике события onmessage) документу в этом окне. Сценарий в одном документе по-прежнему лишен возможности вызывать методы или читать свойства другого документа, но они могут безопасно взаимодействовать друг с другом, используя прием обмена сообщениями. Подробнее API обмена сообщениями между документами рассматривается в разделе 22.3.

содержание 13.6.3. Взаимодействие с модулями расширения и элементами управления ActiveX

Хотя в базовом языке JavaScript и базовой объектной модели на стороне клиента отсутствуют средства для работы с сетевым окружением и файловой системой, которые необходимы наихудшему злонамеренному программному коду, тем не менее ситуация не такая простая, как кажется на первый взгляд. Во многих браузерах JavaScript-код используется как «механизм выполнения» других программных компонентов, таких как элементы управления ActiveX (в IE) и модули расширения (в других браузерах). Самыми распространенными примерами являются модули расширения, обеспечивающие поддержку Flash и Java, и они предоставляют в распоряжение клиентских сценариев дополнительные мощные возможности.

Вопросы безопасности приобретают особую важность, когда речь заходит о передаче управления элементам ActiveX и модулям расширения. Апплеты Java, например, могут иметь низкоуровневый доступ к сетевым возможностям. Защитная «песочница» для Java не позволяет апплетам взаимодействовать с серверами, отличными от того, откуда они были получены; тем самым закрывается брешь в системе безопасности. Но остается основная проблема: если модуль расширения может управляться из сценария, необходимо полное доверие не только системе безопасности браузера, но и системе безопасности самого модуля расширения. На практике модули расширения Java и Flash, похоже, не имеют проблем с безопасностью и не вызывают появление этих проблем в клиентских сценариях на языке JavaScript. Однако элементы управления ActiveX имеют более пестрое прошлое. Браузер IE обладает возможностью доступа из сценариев к самым разным элементам управления ActiveX, которые являются частью операционной системы Windows и которые раньше уже были источниками проблем безопасности.

содержание 13.6.4. Межсайтовый скриптинг

Термин межсайтовый скриптинг (cross-site scripting), или XSS, относится к области компьютерной уязвимости, когда атакующий внедряет HTML-теги или сценарии в документы на уязвимом веб-сайте. Организация защиты от XSS-атак – обычное дело для веб-разработчиков, занимающихся созданием серверных сценариев. Однако программисты, разрабатывающие клиентские JavaScript-сценарии, также должны знать о XSS-атаках и предпринимать меры защиты от них.

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

<script>
var name = decodeURIComponent(window.location.search.substring(1)) || "";
document.write("Hello " + name);
</script>

В этом двустрочном сценарии используется метод window.location.search, с помощью которого извлекается часть адресной строки, начинающаяся с символа ?. Затем с помощью метода document.write() добавляется динамически сгенерированное содержимое документа. Этот сценарий предполагает, что обращение к веб-странице будет производиться с помощью следующего URL-адреса:

http://www.example.com/greet.html?name=David

В этом случае будет выведен текст «Привет, Давид». Но что произойдет, если страница будет запрошена с использованием следующего URL-адреса:

http://www.example.com/greet.html?%3Cscript%3Ealert('David')%3C/script%3E

С таким содержимым URL-адреса сценарий динамически сгенерирует другой сценарий (коды %ЗС и %ЗЕ – это угловые скобки)! В данном случае вставленный сценарий просто отобразит диалоговое окно, которое не представляет никакой опасности. Но представьте себе такой случай:

http://siteA/greet.html?name=%3Cscript src=siteB/evil.js%3E%3C/script%3E

Межсайтовый скриптинг потому так и называется, что в атаке участвует более одного сайта. Сайт B (или даже сайт C) включает специально сконструированную ссылку (подобную только что показанной) на сайт A, в которой содержится сценарий с сайта B. Сценарий evil.js размещается на сайте злоумышленника B, но теперь этот сценарий оказывается внедренным в сайт A и может делать все, что ему заблагорассудится с содержимым сайта A. Он может стереть страницу или вызвать другие нарушения в работе сайта (такие как отказ в обслуживании, о чем рассказывается в следующем разделе). Это может отрицательно сказаться на посетителях сайта A. Гораздо опаснее, что такой злонамеренный сценарий может прочитать содержимое cookies, хранящихся на сайте A (возможно, содержащих учетные номера или другие персональные сведения), и отправить эти данные обратно на сайт B. Внедренный сценарий может даже отслеживать нажатия клавиш и отправлять эти данные на сайт B.

Универсальный способ предотвращения XSS-атак заключается в удалении HTML-тегов из всех данных сомнительного происхождения, прежде чем использовать их для динамического создания содержимого документа. Чтобы исправить эту проблему в показанном ранее файле greet.html, нужно добавить следующую строку в сценарий, которая призвана удалять угловые скобки, окружающие тег <script>:

name = name.replace(/<s/g, "<").replace(/>/g, ">");

Простейшая операция, представленная выше, заменит в строке все угловые скобки на соответствующие им мнемоники языка HTML, тем самым экранировав и деактивировав любые HTML-теги, присутствующие в строке. IE8 определяет более точный метод toStaticHTML(), который удаляет все теги <script> (и любое другое выполняемое содержимое) без изменения невыполняемой разметки HTML. Метод toStaticHTML() не является стандартным, но для вас не составит труда самостоятельно написать аналогичную функцию экранирования разметки HTML на базовом JavaScript.

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

Межсайтовый скриптинг представляет собой уязвимость, глубоко уходящую корнями в архитектуру Всемирной паутины. Вы должны осознавать всю глубину этой уязвимости, но дальнейшее ее обсуждение выходит далеко за рамки темы данной книги. В Интернете есть немало ресурсов, которые помогут вам организовать защиту от атак подобного рода. Наиболее важный из них принадлежит группе компьютерной «скорой помощи» CERT Advisory: http://www.cert.org/advisories/CA-2000-02.html.

содержание 13.6.5. Атаки типа отказа в обслуживании

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

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

содержание 13.7. Клиентские фреймворки содержание

Многие веб-разработчики считают удобным конструировать свои веб-приложения на основе библиотек клиентских фреймворков. Эти библиотеки являются «основой» в том смысле, что они создают новый, высокоуровневый прикладной интерфейс для клиентских сценариев, действующий поверх стандартного и собственного API веб-браузеров: приняв за основу какой-либо фреймворк, вам остается только использовать определяемый им прикладной интерфейс в своем программном коде. Одним из очевидных преимуществ использования фреймворков является высокоуровневый прикладной интерфейс, позволяющий выполнять больше операций за счет меньшего объема программного кода. Кроме того, добротно выполненные фреймворки решают многие проблемы совместимости, безопасности и доступности, описанные выше.

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

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

Prototype

Как и jQuery, библиотека Prototype (http://prototypejs.org) основное внимание уделяет работе с DOM и Ajax и добавляет довольно много вспомогательных функций в базовый язык. В дополнение к ней можно добавить библиотеку Scriptaculous (http://script.aculo.us/), которая реализует анимационные и визуальные эффекты.

Dojo

Dojo (http://dojotoolkit.org) – это огромный фреймворк, имеющий «невероятную глубину». Он включает обширный набор графических элементов пользовательского интерфейса, системный пакет, уровень абстракции данных и многое другое.

YUI

YUI (http://developer.yahoo.com/yui/) – это библиотека, созданная компанией Yahoo! для внутренних нужд и используемая ею в своих веб-страницах. Подобно Dojo, это большая, всеохватывающая библиотека утилит и функций для работы с DOM, виджетами и т. д. Существует две несовместимые версии, известные как YUI 2 и YUI 3.

Closure

Библиотека Closure (http://code.google.com/closure/library/) – это клиентская библиотека, которую компания Google использует в своих веб-приложениях Gmail, Google Docs и в других. Эта библиотека предназначена для использования совместно с компилятором Closure (http://code.google.com/closure/compiler/), который удаляет из библиотеки неиспользуемые функции. Благодаря тому, что перед развертыванием веб-приложения из него удаляется весь ненужный программный код, создатели библиотеки Closure избежали необходимости заботиться о ее компактности и добавили в библиотеку Closure обширный набор утилит.

GWT

GWT, Google Web Toolkit (http://code.google.com/webtoolkit/), – это совершенно иной тип клиентских фреймворков. Он определяет прикладной интерфейс веб-приложений на языке Java и предоставляет компилятор для преобразования Java-программ в совместимые сценарии на клиентском JavaScript. Фреймворк GWT используется в некоторых программных продуктах компании Google, но не так широко, как библиотека Closure.