14. Объект Window

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

содержание 14.1. Таймеры содержание

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

Метод setTimeout() объекта Window планирует запуск функции через определенное число миллисекунд. Метод setTimeout() возвращает значение, которое может быть передано методу clearTimeout(), чтобы отменить запланированный ранее запуск функции.

Метод setInterval() похож на setTimeout(), за исключением того, что он автоматически заново планирует повторное выполнение через указанное количество миллисекунд:

setInterval(updateClock, 60000); // Вызывать updateClock() через каждые 60 сек.

Подобно setTimeout(), метод setInterval() возвращает значение, которое может быть передано методу clearInterval().), чтобы отменить запланированный запуск функции.

В примере 14.1 определяется вспомогательная функция, которая ожидает указанный интервал времени, многократно вызывает указанную функцию и затем отменяет запланированные вызовы по истечении другого заданного интервала времени. Этот пример демонстрирует использование методов setTimeout(), setInterval(), и clearInterval().).

Пример 14.1. Вспомогательная функция для работы с таймером

По исторически сложившимся причинам в первом аргументе методам setTimeout() и setInterval() допускается передавать строку. В этом случае строка будет интерпретироваться (как с применением функции eval()) через указанный интервал времени. Спецификация HTML5 (и все браузеры, кроме IE) допускает передавать методам setTimeout() и setInterval() дополнительные аргументы после первых двух. Все эти дополнительные аргументы будут передаваться функции, вызов которой планируется этими методами. Однако если требуется сохранить совместимость с IE, эту возможность использовать не следует.

Если методу setTimeout() указать величину интервала 0 миллисекунд, указанная функция будет вызвана не сразу, а «как только такая возможность появится», т. е. как только завершат работу все обработчики событий.

содержание 14.2. Адрес документа и навигация по нему содержание

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

Свойство location объекта Document также ссылается на объект location:

window.location === document.location // всегда верно

Кроме того, объект Document имеет свойство URL, хранящее статическую строку с адресом URL документа. При перемещении по документу с использованием идентификаторов фрагментов (таких как «#table-of-contents») внутри документа объект location будет обновляться, отражая факт перемещения, но свойство document. URL останется неизменным.

содержание 14.2.1. Анализ URL

Свойство location окна является ссылкой на объект location и представляет URL- адрес документа, отображаемого в данный момент в текущем окне. Свойство href объекта location – это строка, содержащая полный текст URL-адреса. Метод toString() объекта location возвращает значение свойства href, поэтому в контекстах, где неявно подразумевается вызов метода toString(), вместо конструкции location.href можно писать просто location.

Другие свойства этого объекта, такие как protocol, host, hostname, port, pathname, search и hash, определяют отдельные части URL-адреса. Они известны как свойства «декомпозиции URL» и также поддерживаются объектами Link (которые создаются элементами <a> и <area> в HTML-документах). Полное описание объектов location и Link приводится в четвертой части книги.

Свойства hash и search объекта location представляют особый интерес. Свойство hash возвращает «идентификатор фрагмента» из адреса URL, если он имеется: символ решетки (#) со следующим за ним идентификатором. Свойство search содержит часть URL-адреса, следующую за вопросительным знаком, если таковая имеется, включая сам знак вопроса. Обычно эта часть URL-адреса является строкой запроса. В целом эта часть URL-адреса используется для передачи параметров и является средством встраивания аргументов в URL-адрес. Хотя эти аргументы обычно предназначены для сценариев, выполняющихся на сервере, нет никаких причин, по которым они не могли бы также использоваться в страницах, содержащих JavaScript-код. В примере 14.2 приводится определение универсальной функции urlArgs(), позволяющей извлекать аргументы из свойства search URL-адреса. В примере используется глобальная функция decodeURIComponent(), имеющаяся в клиентском JavaScript. (Смотрите справочную статью «Global» в третьей части книги.)

Пример 14.2. Извлечение аргументов из строки search URL-адреса

содержание 14.2.2. Загрузка нового документа

Метод assign() объекта Location заставляет окно загрузить и отобразить документ по указанному URL-адресу. Метод replace() выполняет похожую операцию, но перед открытием нового документа он удаляет текущий документ из списка посещавшихся страниц. Когда сценарию просто требуется загрузить новый документ, часто предпочтительнее использовать метод replace(), а не assign(). В противном случае кнопка Back (Назад) браузера вернет оригинальный документ и тот же самый сценарий снова загрузит новый документ. Метод location. replace() можно было бы использовать для загрузки версии веб-страницы со статической разметкой HTML, если сценарий обнаружит, что браузер пользователя не обладает функциональными возможностями, необходимыми для отображения полноценной версии:

// Если браузер не поддерживает объект XMLHttpRequest, выполнить
// переход к статической
if (!XMLHttpRequest) location.replace("staticpage.html");

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

Кроме методов assign() и replace() объект Location определяет также метод reload(), который заставляет браузер

Однако более традиционный способ заставить браузер перейти к новой странице заключается в том, чтобы просто присвоить новый URL-адрес свойству location:

location = "http://www.oreilly.com"; // Перейти, чтобы купить несколько книг!

Свойству location можно также присваивать относительные URL-адреса. Они разрешаются относительно текущего URL:

location = "page2.html"; // Загрузить следующую страницу

Идентификатор фрагмента – это особый вид относительного URL-адреса, который заставляет браузер просто прокрутить страницу, чтобы отобразить новый раздел, а не загружать новый документ. Идентификатор #top имеет специальное назначение: если в документе отсутствует элемент с идентификатором «top», он вынудит браузер перейти в начало документа:

location = "#top"; // Перейти в начало документа

Свойства декомпозиции URL объекта Location доступны для записи, и их изменение влечет за собой изменение URL-адреса в свойстве location и вынуждает браузер загрузить новый документ (или, в случае изменения свойства hash, выполнить переход внутри текущего документа):

location.search = "?page=" + (pagenum+1); // загрузить следующую страницу

содержание 14.3. История посещений содержание

Свойство history объекта Window ссылается на объект History данного окна. Объект history хранит историю просмотра страниц в окне в виде списка документов и сведений о них. Свойство length объекта History позволяет узнать количество элементов в списке, но по причинам, связанным с безопасностью, сценарии не имеют возможности получить хранящиеся в нем URL-адреса. (Иначе любой сценарий смог бы исследовать историю посещения веб-сайтов.)

Методы back() и forward() действуют подобно кнопкам Back (Назад) и Forward (Вперед) браузера: они заставляют браузер перемещаться на один шаг назад и вперед по истории просмотра данного окна. Третий метод, go(), принимает целочисленный аргумент и пропускает заданное число страниц, двигаясь вперед (если аргумент положительный) или назад (если аргумент отрицательный) в списке истории.

history.go(-2); // Переход назад на 2 элемента, как если бы пользователь
                // дважды щелкнул на кнопке Back (Назад)

Если окно содержит дочерние окна (такие как элементы <iframe>nbsp;– подробности смотрите в разделе 14.8.2), истории посещений в дочерних окнах хронологически чередуются с историей посещений в главном окне. То есть вызов history.back() (например) в главном окне может вызвать переход назад, к ранее отображавшемуся в одном из дочерних окон документу, оставив главное окно в текущем состоянии.

Современные веб-приложения способны динамически изменять содержимое страницы без загрузки нового документа (примеры приводятся в главах 15 и 18). Приложениям, действующим подобным образом, может потребоваться предоставить, пользователям возможность использовать кнопки Back и Forward для перехода между этими динамически созданными состояниями приложения. Стандарт HTML5 определяет два способа реализации, и оба они описываются в разделе 22.2.

Управление историей посещений до появления стандарта HTML5 представляло собой довольно сложную задачу. Приложение, управляющее собственной историей, должно было создавать новые записи в списке истории окна, связывать эти записи с информацией о состоянии, определять момент щелчка на кнопке Back, чтобы перейти к другой записи в списке, получать информацию, связанную с этой записью, и воссоздавать предыдущее состояние приложения в соответствии с этой информацией. Один из приемов реализации такого поведения опирается на использование скрытого элемента <iframe>, в котором сохраняется информация о состоянии и создаются записи в списке истории просмотра. Чтобы создать новую запись, в этот скрытый фрейм динамически записывается новый документ с помощью методов open() и write() объекта Document (раздел 15.10.2). Документ должен включать всю информацию, необходимую для воссоздания соответствующего состояния приложения. Когда пользователь щелкнет на кнопке Back, содержимое скрытого фрейма изменится. До появления стандарта HTML5 не предусматривалось никаких событий, которые извещали бы об этом изменении, поэтому чтобы определить момент щелчка на кнопке Back, приходилось использовать функцию setInterval() (раздел 14.1) и с ее помощью 2 – 3 раза в секунду проверять наличие изменений в скрытом фрейме.

Однако на практике когда требуется реализовать подобное управление историей просмотра, разработчики предпочитают использовать готовые решения. Многие фреймворки JavaScript включают такие решения. Например, для библиотеки jQuery существует расширение history. Существуют также автономные библиотеки управления историей. Например, одной из наиболее популярных является библиотека RSH (Really Simple History – действительно простое управление историей). Найти ее можно по адресу http://code.google.com/p/reallysimplehistory/. В разделе 22.2 описывается, как реализуется управление историей в HTML5.

содержание 14.4. Информация о браузере и об экране содержание

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

содержание 14.4.1. Объект Navigator

Свойство navigator объекта Window ссылается на объект Navigator, содержащий общую информацию о номере версии и о производителе браузера. Объект Navigator назван «в честь» браузера Netscape Navigator, но он также поддерживается во всех других браузерах. (Кроме того, IE поддерживает свойство clientInformation как нейтральный синоним для navigator. К сожалению, другие браузеры свойство с таким именем не поддерживают.)

В прошлом объект Navigator обычно использовался сценариями для определений типа браузера – Internet Explorer или Netscape. Однако такой подход к определению типа браузера сопряжен с определенными проблемами, т. к. требует постоянного обновления с появлением новых браузеров или новых версий существующих браузеров. Ныне более предпочтительным считается метод на основе проверки функциональных возможностей (раздел 13.4.3). Вместо того чтобы делать какие-либо предположения о браузерах и их возможностях, гораздо проще прямо проверить наличие требуемой функциональной возможности (например, метода или свойства).

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

appName
Название веб-браузера. В IE это строка «Microsoft Internet Explorer». В Firefox значением этого свойства является строка «Netscape». Для совместимости с существующими реализациями определения типа браузера значением этого свойства в других браузерах часто является строка «Netscape».
appVersion
Обычно значение этого свойства начинается с номера версии, за которым следует другая информация о версии браузера и его производителе. Обычно в начале строки указывается номер 4.0 или 5.0, свидетельствующий о совместимости с четвертым или пятым поколением браузеров. Формат строки в свойстве appVersion не определяется стандартом, поэтому невозможно организовать разбор этой строки способом, не зависящим от типа браузера.
userAgent
Строка, которую браузер посылает в http-заголовке USER-AGENT. Это свойство обычно содержит ту же информацию, что содержится в свойстве appVersion, а также может включать дополнительные сведения. Как и в случае со свойством appVersion, формат представления этой информации не стандартизован. Поскольку это свойство содержит больше информации, именно оно обычно используется для определения типа браузера.
platform
Строка, идентифицирующая операционную систему (и, возможно, аппаратную платформу), в которой работает браузер.

Сложность свойств объекта Navigator делает невозможной универсальную реализацию определения типа браузера. На раннем этапе развития Всемирной паутины было написано немало программного кода, зависящего от типа браузера, проверяющего свойства, такие как navigator.appName. Создавая новые браузеры, производители обнаружили, что для корректного отображения содержимого существующих веб-сайтов они должны устанавливать значение «Netscape» в свойстве appName. По тем же причинам потерял свою значимость номер в начале значения свойства appVersion, и в настоящее время реализация определения типа браузера должна опираться на строку в свойстве navigator.userAgent, имеющую более сложный формат, чем ранее. Пример 14.3 демонстрирует, как с помощью регулярных, выражений (взятых из библиотеки jQuery) можно получить из свойства navigator.userAgent название браузера и номер версии.

Пример 14.3. Определение типа браузера с помощью свойства navigator.userAgent

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

onLine
Свойство navigator.onLine (если существует) определяет, подключен ли браузер к сети. Приложениям может потребоваться сохранять информацию о состоянии локально (с использованием приемов, описываемых в главе 20), если браузер не подключен к сети.
geolocation
Объект Geolocation, определяющий API для выяснения географического положения пользователя. Подробнее об этом рассказывается в разделе 22.1.
javaEnabled()
Нестандартный метод, который должен возвращать true, если браузер способен выполнять Java-апплеты.
cookiesEnabled()
Нестандартный метод, который должен возвращать true, если браузер способен сохранять cookies. Если браузер настроен на сохранение cookies только для определенных сайтов, этот метод может возвращать некорректное значение.

содержание 14.4.2. Объект Screen

Свойство screen объекта Window ссылается на объект Screen, предоставляющий ин; формацию о размере экрана на стороне пользователя и доступном количестве цветов. Свойства width и height возвращают размер экрана в пикселах. Свойства availWidth и availHeight возвращают фактически доступный размер экрана; из них исключается пространство, требуемое для таких графических элементов, как панель задач. Свойство colorDepth возвращает количество битов на пиксел, определяющих цвет. Типичными значениями являются 16, 24 и 32.

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

содержание 14.5. Диалоги содержание

Объект Window обладает тремя методами для отображения простейших диалогов. Метод alert() выводит сообщение и ожидает, пока пользователь закроет диалоговое окно. Метод confirm() предлагает пользователю щелкнуть на кнопке OK или Cancel (Отмена) и возвращает логическое значение. Метод prompt() выводит сообщение, ждет ввода строки пользователем и возвращает эту строку. Ниже демонстрируется пример использования всех трех методов:

Методы alert(), confirm() и prompt() чрезвычайно просты в использовании, но правила хорошего дизайна требуют, чтобы они применялись как можно реже. Диалоги, подобные этим, нечасто используются в Веб, и большинство пользователей сочтет диалоговые окна, выводимые этими методами, выпадающими из обычной практики. Единственный вариант, когда имеет смысл обращаться к этим методам, – это отладка. JavaScript-программисты часто вставляют вызов метода alert() в программный код, пытаясь диагностировать возникшие проблемы.

Обратите внимание, что текст, отображаемый методами alert(), confirm() и prompt() в диалогах, – это обычный неформатированный текст. Его можно форматировать только пробелами, переводами строк и различными знаками пунктуации.

Методы confirm() и prompt() являются блокирующими, т. е. они не возвращают управление, пока пользователь не закроет отображаемые ими диалоговые окна.

Обычно такие окна называют модальными. – Прим. науч. ред.

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

В дополнение к методам alert(), confirm() и prompt() в объекте Window имеется более сложный метод showModalDialog(). Он отображает модальный диалог, содержащий разметку HTML, и позволяет передавать аргументы и получать возвращаемое значение. Метод showModalDialog() выводит модальный диалог в отдельном окне браузера. Первым аргументом методу передается URL, определяющий HTML-содержимое диалога. Во втором аргументе может передаваться произвольное значение (допускается передавать массивы и объекты), которое будет доступно сценарию в диалоге как значение свойства Window.dialogArguments. Третий аргумент – нестандартный список пар имя/значение, разделенных точками с запятой, который, если поддерживается, может использоваться для настройки размеров и других атрибутов диалогового окна. Для определения размеров окна диалога можно использовать параметры «dialogwidth» и «dialogheight», а чтобы позволить пользователю изменять размеры окна, можно определить параметр «resizable=yes».

Окно, отображаемое эти методом, является модальным, и метод showModalDialog() не возвращает управление, пока окно не будет закрыто. После закрытия окна значение свойства window.returnValue становится возвращаемым значением метода. Обычно разметка HTML диалога должна включать кнопку OK, которая записывает желаемое значение в свойство returnValue и вызывает window.close() (раздел 14.8.1.1).

В примере 14.4 приводится разметка HTML для использования с методом showModalDialog(). Комментарий в начале примера включает пример вызова showModalDialog(), а на рис. 14.1 показан диалог, созданный вызовом из примера. Обратите внимание, что большая часть текста, отображаемого в диалоге, передается методу showModalDialog() во втором аргументе, а не является жестко определенной частью разметки HTML.

cheme

Рис. 14.1. Диалог, отображаемый методом showModalDialog()

содержание 14.6. Обработка ошибок содержание

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

Исторически сложилось так, что обработчику события onerror объекта Window передается три строковых аргумента, а не единственный объект события, как в других обработчиках. (Другие объекты в клиентском JavaScript также имеют обработчики onerror, обрабатывающие различные ошибочные ситуации, но все они являются обычными обработчиками событий, которым передается единственный объект события.) Первый аргумент обработчика Window.onerror – это сообщение, описывающее произошедшую ошибку. Второй аргумент – это строка, содержащая URL-адрес документа с JavaScript-кодом, приведшим к ошибке. Третий аргумент – это номер строки в документе, где произошла ошибка.

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

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

содержание 14.7. Элементы документа как свойства окна содержание

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

Как вы уже знаете, объект Window играет роль глобального объекта, находящегося на вершине цепочки областей видимости в клиентском JavaScript. Таким образом, вышесказанное означает, что атрибуты id в HTML-документах становятся глобальными переменными, доступными сценариям. Если, например, документ включает элемент <button id="okay"/> , на него можно сослаться с помощью глобальной переменной okay.

Однако важно отметить, что этого не происходит, если объект Window уже имеет свойство с таким именем. Элементы с атрибутами id имеющими значение «history», «location» или «navigator», например, не будут доступны через глобальные переменные, потому что эти имена уже используются. Аналогично, если HTML-документ включает элемент с атрибутом id, имеющим значение «x», и в сценарии объявляется и используется глобальная переменная x, явно объявленная переменная скроет неявную переменную, ссылающуюся на элемент. Если переменная объявляется в сценарии, который в документе находится выше именованного элемента, наличие переменной будет препятствовать появлению нового свойства окна. А если переменная объявляется в сценарии, который находится ниже именованного элемента, первая же инструкция присваивания значения этой переменной затрет значение неявно созданного свойства.

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

После выполнения этого фрагмента свойства ui.input, ui.prompt и ui.heading будут ссылаться на элементы документа. Вместо ui.input и ui.heading сценарий мог бы использовать глобальные переменные input и heading. Но, как вы помните из 14.5, объект Window имеет метод с именем prompt(), поэтому сценарий не сможет использовать глобальную переменную prompt, вместо свойства ui.prompt.

Неявное использование идентификаторов элементов в качестве глобальных переменных – это пережиток истории развития веб-браузеров. Эта особенность необходима для сохранения обратной совместимости с существующими веб-страницами, но использовать ее сейчас не рекомендуется – в любой момент производители браузеров могут определить новое свойство в объекте Window, что нарушит работу любого программного кода, использующего неявно определяемое свойство с этим именем. Поиск элементов лучше выполнять явно с помощью метода document.getElementById(). А его использование будет менее трудоемким, если дать ему более короткое имя:

var $ = function(id) { return document.getElementById(id); };
ui.prompt = $("prompt");

Многие клиентские библиотеки определяют функцию $, которая отыскивает элементы по их идентификаторам, как в приведенном примере. (В главе 19 мы узнаем, что функция $ в библиотеке jQuery является универсальным методом выбора элементов, который может возвращать один или более элементов, опираясь на их идентификаторы, имена тегов, атрибут class или исходя из других критериев.)

Любой HTML-элемент с атрибутом id становится значением глобальной переменной, если значение атрибута id еще не используется объектом Window. Следующие HTML-элементы ведут себя подобным образом, если в них определен атрибут name:

Атрибут id элемента необходим для придания ему уникальности внутри документа: два элемента не могут иметь одинаковые значения атрибута id. Однако это не относится к атрибуту name. Если сразу несколько элементов, из перечисленных выше, будут иметь одно и то же значение атрибута name (или, если один элемент имеет атрибут name, а другой – атрибут id с тем же значением), неявно созданная глобальная переменная с этим именем будет ссылаться на объект, подобный массиву, хранящий все элементы с этим именем.

Элементы <iframe> с атрибутом name или id обрабатываются иначе. Переменная, неявно созданная для таких элементов, будет ссылаться не на объект Element, представляющий сам элемент, а наобъект Window, представляющий вложенный фрейм, созданный элементом <iframe>. К этой теме мы еще вернемся в разделе 14.8.2 .

содержание 14.8. Работа с несколькими окнами и фреймами содержание

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

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

HTML-документы могут содержать вложенные документы, используя для этого элементы <iframe>. Элемент <iframe>создает вложенный контекст просмотра, представленный отдельным объектом Window . Устаревшие и не рекомендуемые к использованию элементы <frameset> и <frame> также создают вложенные контексты просмотра, и каждый элемент <iframe>представлен собственным объектом Window . Различия между окнами, вкладками, плавающими фреймами (элемент <iframe>) и фреймами в клиентском JavaScript весьма несущественны: все они являются отдельными контекстами просмотра и в сценариях все они представлены объектами Window . Вложенные контексты просмотра не изолированы друг от друга, как обычно бывают изолированы независимые вкладки. Сценарий, выполняющийся в одном фрейме, всегда имеет доступ к вмещающим и вложенным фреймам, и только политика общего происхождения может не позволять сценарию просматривать документы в этих фреймах. Вложенные фреймы рассматриваются в разделе 14.8.2.

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

содержание 14.8.1. Открытие и закрытие окон

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

Первый аргумент open() – это URL-адрес документа, отображаемого в новом окне. Если этот аргумент отсутствует (либо является пустой строкой), будет открыт специальный URL пустой страницы about:blank.

Второй аргумент open() – это строка с именем окна. Если окно с указанным именем уже существует (и сценарию разрешено просматривать содержимое этого окна), используется это существующее окно. Иначе создается новое окно и ему присваивается указанное имя. Если этот аргумент опущен, будет использовано специальное имя «_blank», т. е. будет открыто новое неименованное окно.

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

Имена окон

Имя окна играет важную роль, потому что оно позволяет указать существующее окно в вызове метода open(), а также потому, что оно может использоваться как значение HTML-атрибута target элементов <a> и <form>, ссылающихся на документ (или результат обработки формы), который должен быть отображен в именованном окне. Атрибуту target этих элементов можно также присвоить значение «blank», «parent» или «_top», чтобы открыть документ в новом пустом окне, родительском окне или фрейме или в окне верхнего уровня.

Имя окна, если оно имеется, хранится в свойстве name объекта Window. Данное свойство доступно для записи, и сценарии могут изменять его по мере необходимости. Если методу Window.open() передать имя (отличное от «_blank»), окно, созданное вызовом этого метода, получит указанное имя, как начальное значение свойства name. Если элемент <iframe> имеет атрибут name, объект Window, представляющий этот фрейм, будет использовать значение атрибута name как начальное значение свойства name.

Третий необязательный аргумент open() – это список параметров, определяющих размер и видимые элементы графического пользовательского интерфейса нового окна. Если опустить этот аргумент, окно получает размер по умолчанию и полный набор графических элементов: строку меню, строку состояния, панель инструментов и т. д. В браузерах, поддерживающих вкладки, это обычно приводит к созданию новой вкладки. Указав этот аргумент, можно явно определить размер окна и набор имеющихся в нем элементов управления. (Если явно указать размер, в большинстве случаев это приведет к созданию нового окна, а не вкладки.) Например, маленькое окно с изменяемым размером, имеющее строку состояния, но не содержащее меню, панели инструментов и адресную строку, можно открыть посредством следующим образом:

var w = window.open("smallwin.html", "smallwin",
                    "width=400,height=350,status=yes,resizable=yes");

Этот третий аргумент является нестандартным, и спецификация HTML5 требует, чтобы браузеры игнорировали его. Подробнее о том, какие параметры можно указывать в этом аргументе, рассказывается в описании метода Window.open() в четвертой части книги. Обратите внимание, что, когда указывается третий аргумент, любые не заданные явно элементы управления отсутствуют. По ряду причин, связанных с проблемами безопасности, браузеры накладывают ограничения на характеристики, которые можно передать методу. Так, например, невозможно открыть слишком маленькое окно или открыть его за пределами видимой области экрана; кроме того, некоторые браузеры не допускают возможности создания окон без строки состояния.

Указывать четвертый аргумент open() имеет смысл, только если второй аргумент определяет имя существующего окна. Этот аргумент – логическое значение, определяющее, должен ли URL-адрес, указанный в первом аргументе, заменить текущую запись в истории просмотра окна (true) или требуется создать новую запись (false). Если этот аргумент опущен, используется значение по умолчанию false. Значение, возвращаемое методом open() , является объектом Window , представляющим вновь созданное окно. Этот объект позволяет сослаться в JavaScript-коде на новое окно так же, как исходный объект Window ссылается на окно, в котором выполняется сценарий:

В окнах, созданных методом Window.open() , свойство opener ссылается на объект Window сценария, открывшего его. В других случаях свойство opener получает значение null:

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

14.8.1.1. Открытие и закрытие окон

Новое окно открывается при помощи метода open() и закрывается при помощи метода close(). Если объект Window был создан сценарием, то этот же сценарий сможет закрыть его следующей инструкцией:

w.close();

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

window.close();

Обратите внимание на явное использование идентификатора window для устранения неоднозначности между методом close()объекта Window и методом close() объекта Document – это важно, если метод close() вызывается внутри обработчика события.

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

Объект Window продолжает существовать и после закрытия представляемого им окна. Однако не следует использовать какие-либо его свойства или методы, исключая проверку свойства closed. Если окно было закрыто, это свойство будет иметь значение true, свойство document – значение null, а методы окна обычно не выполняются.

содержание 14.8.2. Отношения между фреймами

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

Как вы уже знаете, сценарий в любом окне или фрейме может сослаться на собственное окно или фрейм с помощью свойства window или self. Фрейм может сослаться на объект Window вмещающего окна или фрейма с помощью свойства parent:

parent.history.back();

Объект Window, представляющий окно верхнего уровня или вкладку, не имеет вмещающего окна, поэтому его свойство parent просто ссылается на само окно:

parent == self; // Для любых окон верхнего уровня

Если фрейм находится внутри другого фрейма, содержащегося в окне верхнего уровня, то он может сослаться на окно верхнего уровня так: parent.parent. Однако в качестве универсального сокращения имеется свойство top: независимо от глубины вложенности фрейма его свойство top ссылается на содержащее его окно самого верхнего уровня. Если объект Window представляет окно верхнего уровня, свойство top просто ссылается на само окно. Для фреймов, непосредственно принадлежащих окну верхнего уровня, значение свойства top совпадает со значением свойства parent.

Свойства parent и top позволяют сценариям ссылаться на родительские окна или фреймы. Существует несколько способов сослаться на дочерние окна или фреймы. Фреймы создаются с помощью элементов <iframe>. Получить ссылку на объект Element, представляющий элемент <iframe>, можно точно так же, как на объект, представляющий любой другой элемент. Допустим, что документ содержит тег <iframe id="f1">. Тогда получить ссылку на объект Element, представляющий этот элемент iframe, можно следующим образом:

var iframeElement = document.getElementById("f1");

Элементы <iframe> имеют свойство contentWindow, которое ссылается на объект Window фрейма, поэтому ссылку на объект Window этого фрейма можно получить так:

var childFrame = document.getElementById("f1").contentWindow;

Имеется возможность пойти обратным путем – от объекта Window , представляющего фрейм, к объекту Element элемента <iframe>, содержащего фрейм, – с помощью свойства frameElement объекта Window. Объекты Window, представляющие окна верхнего уровня, а не фреймы, имеют значение null в свойстве frameElement:

Однако чтобы получить ссылки на дочерние фреймы, обычно не требуется использовать метод getElementById() и свойство contentWindow. Каждый объект Window имеет свойство frames, хранящее ссылки на дочерние фреймы, содержащиеся в окне или фрейме. Свойство frames ссылается на объект, подобный массиву, который может индексироваться числовыми индексами или именами фреймов. Получить ссылку на первый дочерний фрейм в окне можно с помощью выражения frames[0]. Сослаться на третий дочерний фрейм во втором дочернем фрейме можно с помощью выражения frames[1].frames[2]. Сценарий, выполняющийся во фрейме, может сослаться на соседний фрейм одного с ним уровня как parent.frames[1]. Обратите внимание, что элементами массива frames[] являются объекты Window, а не элементы <iframe>.

Если в элементе <iframe> указать атрибут name или id, в качестве индекса этого фрейма можно будет использовать не только число, но и имя. Например, ссылку на фрейм с именем «f1» можно получить с помощью выражения frames["f1"] или frames.f1

В разделе 14.7 говорилось, что имена или идентификаторы элементов <iframe> и других автоматически превращаются в свойства объекта Window и что элементы <iframe> интерпретируются иначе, чем другие элементы: в случае с фреймами значениями этих автоматически создаваемых свойств становятся ссылки на объекты Window, а не на объекты Element. Это означает, что на фрейм с именем «f1» можно сослаться как на свойство f1 вместо frames.f1. В действительности, стандарт HTML5 указывает, что свойство frames, подобно свойствам window и self, ссылается на сам объект Window, который действует как массив фреймов. Это означает, что ссылкой на первый дочерний фрейм может служить window[0], а получить количество фреймов можно, обратившись к свойству window.length или просто length. Однако использование свойства frames вместо window в подобных случаях делает программный код более понятным. Обратите внимание, что не во всех текущих браузерах выполняется условие frame==window, но даже в браузерах, где это условие не выполняется, разрешается индексировать дочерние фреймы числами и именами, обращаясь к любому из этих двух объектов.

С помощью атрибута name или id элементу <iframe> можно присвоить имя, которое будет доступно для использования в JavaScript-коде. Однако если использовать атрибут name, указанное имя также будет использоваться в качестве значения свойства name объекта Window, представляющего фрейм. Имя, указанное таким способом, можно использовать в качестве значения атрибута target ссылки и передавать методу window.open() во втором аргументе.

содержание 14.8.3. JavaScript во взаимодействующих окнах

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

Представим себе веб-страницу с двумя элементами <iframe>, имеющими имена «A» и «B», и предположим, что эти фреймы содержат документы, полученные с одного и того же сервера, и эти документы содержат взаимодействующие сценарии. Сценарий во фрейме A определяет переменную i:

var i = 3;

Эта переменная представляет собой свойство глобального объекта, т. е. свойство объекта Window. Сценарий во фрейме A может явно сослаться на эту переменную как на свойство с помощью объекта window:

window.i

Благодаря тому что сценарий во фрейме B может ссылаться на объект Window во фрейме A, он также может ссылаться на свойства этого объекта окна:

parent.A.i = 4; // Изменит значение переменной во фрейме A

Напомню, что ключевое слово function, определяющее функцию, объявляет переменную так же, как ключевое слово var. Если JavaScript-код во фрейме B объявляет функцию f, эта функция станет глобальной переменной во фрейме B, и сценарий во фрейме B сможет вызывать функцию f как f(). Однако сценарий во фрейме A должен ссылаться на f как на свойство объекта Window во фрейме B:

parent.B.f(); // Вызовет функцию, объявленную во фрейме B

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

var f = parent.B.f;

Теперь сценарий во фрейме A сможет вызывать функцию как f() точно так же, как сценарий во фрейме B.

Разделяя подобным образом функции между фреймами или окнами, очень важно помнить о правилах лексического контекста. Функции выполняются в том контексте, в котором они определены, а не в том, из которого они вызываются. Следовательно, если функция f ссылается на глобальные переменные, поиск этих переменных выполняется в свойствах фрейма B, даже когда функция вызывается из фрейма A.

Напомню, что конструкторы – это тоже функции, поэтому когда вы определяете класс объектов (см. главу 9) с функцией-конструктором и связанным с ним объектом-прототипом, этот класс будет определен только для одного окна. Предположим, что окно, содержащее фреймы A и B, включает класс Set из примера 9.6.

Сценарии в окне верхнего уровня смогут создавать новые объекты Set, как показано ниже:

var s = new Set();

Но сценарии в обоих фреймах должны явно ссылаться на конструктор Set() как на свойство родительского окна:

var s = new Set();

В качестве альтернативы сценарий в любом фрейме может определить собственные переменные для более удобного обращения к функции-конструктору:

var Set = top.Set();
var s = new Set();

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

Тот факт, что каждый объект Window имеет собственные объекты-прототипы, означает, что оператор instanceof не будет работать с объектами в разных окнах. Например, оператор instanceof будет возвращать false при сопоставлении строки из фрейма B с конструктором String() из фрейма A. В разделе 7.10 описываются похожие сложности с определением типов массивов в разных окнах.

Объект WindowProxy

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

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

Этот промежуточный объект ведет себя как настоящий глобальный объект, за исключением того, что живет гораздо дольше. Если бы вы могли сравнить эти два объекта, вы едва ли смогли бы отличить их. Однако на самом деле нет никакой возможности сослаться на настоящий клиентский глобальный объект. Глобальный объект находится на вершине цепочки областей видимости, но свойства window, self, top, parent и frames ссылаются на промежуточные объекты. Метод window.open() возвращает промежуточный объект. Даже ключевое слово this в функциях верхнего уровня ссылается на промежуточный объект, а не на настоящий глобальный объект.

Этот последний пункт является небольшим отступлением от стандартов ES3 и ES5, но это необходимо для поддержки взаимодействующих контекстов выполнения в клиентском JavaScript.