В главе 13 уже говорилось, что объект Window выступает в качестве глобального объекта для клиентского JavaScript_кода, а окно – в качестве контекста исполне_ ния для всего содержащегося в нем JavaScript_кода. Это относится и к фреймам: каждый фрейм представляет собою независимый контекст исполнения JavaScript_ кода. Каждый объект Window является отдельным глобальным объектом, поэтому в каждом окне определено собственное пространство имен и свой набор глобаль_ ных переменных. Если смотреть с точки зрения работы с несколькими фреймами или окнами, то глобальные переменные уже не кажутся такими глобальными!
Несмотря на то что каждое окно или фрейм определяет независимый контекст исполнения JavaScript_кода, это не значит, что код, исполняющийся в одном ок_ не, изолирован от кода в других окнах. Код, исполняющийся в одном фрейме, имеет в вершине своей цепочки областей видимости объект Window, отличный от того, который имеет код, исполняющийся в другом фрейме. Однако код из обоих фреймов исполняется одним и тем же интерпретатором JavaScript в одной и той же среде. Как мы видели, фрейм может ссылаться на любой другой фрейм с по_ мощью свойств frames, parent и top. Поэтому, хотя JavaScript_код в разных фрей_ мах исполняется с различными цепочками областей видимости, тем не менее код в одном фрейме может обращаться к переменным и функциям, определен_ ным в коде другого фрейма, и использовать их.
Предположим, что код во фрейме A определяет переменную i:
var i = 3;
Это переменная представляет собой свойство глобального объекта, т. е. свойство объекта Window. Код во фрейме A может явно ссылаться на эту переменную как на свойство с помощью любого из двух выражений:
window.i
self.i
Теперь предположим, что у фрейма A имеется смежный фрейм B, который пыта_ ется установить значение переменной i, определенной в коде фрейма A. Если фрейм B просто присвоит значение переменной i, он лишь успешно создаст новое свойство собственного объекта Window. Поэтому он должен явно сослаться на свойство i смежного объекта с помощью следующего кода:
parent.frames[0].i = 4;
310 Глава 14. Работа с окнами броузера
Вспомните, что ключевое слово function, определяющее функцию, объявляет пе_ ременную так же, как ключевое слово var. Если JavaScript_код во фрейме A объ_ являет функцию f, эта функция определяется только внутри фрейма A. Код во фрейме A может вызывать функцию f следующим образом:
f();
Однако код во фрейме B должен ссылаться на f, как на свойство объекта Window фрейма A:
parent.frames[0].f();
Если код во фрейме B часто вызывает эту функцию, можно присвоить ее пере_ менной фрейма B, так чтобы было удобнее ссылаться на функцию:
var f = parent.frames[0].f;
Теперь код во фрейме B может вызывать функцию как f() точно так же, как код фрейма A.
Разделяя подобным образом функции между фреймами или окнами, очень важ_ но помнить о правилах лексического контекста. Функции исполняются в том контексте, в котором они определены, а не в том, из которого они вызываются. Следовательно, продолжая предыдущий пример, если функция f ссылается на глобальные переменные, поиск этих переменных выполняется в свойствах фрейма A, даже когда функция вызывается из фрейма B.
Если не обращать на это особого внимания, могут получаться программы, веду_ щие себя неожиданным и запутанным образом. Предположим, что вы определи_ ли в секции <head> документа, содержащего несколько фреймов, следующую функцию, думая, что она поможет вам при отладке:
function debug(msg) {
alert("Отладочное сообщение от фрейма: " + name + "\n" + msg);
}
JavaScript_код в каждом из ваших фреймов может ссылаться на эту функцию так: top.debug(). Однако при ее вызове функция будет искать переменную name в контексте окна верхнего уровня, в котором определена функция, а не в контек_ сте фрейма, из которого она вызвана. В результате отладочные сообщения всегда будут содержать имя окна верхнего уровня, а не имя фрейма, посылающего со_ общение, как это предполагалось.
Помните, что конструкторы – это тоже функции, поэтому, когда вы определяете класс объектов с функцией_конструктором и связанным с ним объектом_прототи_ пом, этот класс оказывается определен только для одного окна. Вспомним класс Complex, который мы определили в главе 9, и рассмотрим следующий HTML_доку_ мент с несколькими фреймами:
<head>
<script src="Complex.js"></script>
</head>
<frameset rows="50%,50%">
<frame name="frame1" src="frame1.html">
<frame name="frame2" src="frame2.html">
</frameset>
14.9. Пример: панель навигации во фрейме
JavaScript_код в файлах frame1.html и frame2.html не может создать объект Com_ plex с помощью примерно такого выражения:
var c = new Complex(1,2); // Не работает ни из одного фрейма
Он должен явно ссылаться на функцию_конструктор:
var c = new top.Complex(3,4);
В качестве альтернативы код в любом фрейме может определять собственные пе_ ременные для более удобного обращения к функции_конструктору:
var Complex = top.Complex; var c = new Complex(1,2);
В отличие от пользовательских конструкторов, предопределенные конструкто_ ры оказываются автоматически определенными во всех окнах. Однако следует заметить, что каждое окно имеет независимую копию конструктора и независи_ мую копию объекта_прототипа конструктора. Например, каждое окно имеет собственную копию конструктора String() и объекта String.prototype. Поэтому если вы создадите новый метод для работы с JavaScript_строками и сделаете его методом класса String, присвоив его объекту String.prototype текущего окна, все строки в этом окне смогут использовать новый метод, однако этот новый метод будет недоступен строкам, определенным в других окнах. Обратите внимание: не имеет значения, в каком из окон содержится ссылка на строку; имеет значе_ ние только окно, в котором фактически создана строка.
Пример: панель навигации во фрейме
Эта глава заканчивается примером, демонстрирующим несколько наиболее важных приемов работы с окнами, которые были здесь описаны:
• Запрос текущего URL_адреса с помощью свойства location.herf и загрузка но_ вого документа установкой нового значения в свойство location.
• Использование методов back() и forward() объекта History.
• Использование метода setTimeout() для отложенного вызова функции.
• Открытие нового окна броузера методом window.open().
• Использование JavaScript_кода из одного фрейма для взаимодействия с дру_ гим фреймом.
• Демонстрация ограничений, накладываемых политикой общего происхож_ дения.
Пример 14.7 представляет собой сценарий и несложную HTML_форму, предна_ значенную для работы с документом в другом фрейме. В одном фрейме создается простая панель навигации, которая используется для управления содержимым другого фрейма. Панель навигации включает в себя кнопки Назад и Вперед, а так_ же текстовое поле, в которое вводится URL_адрес. Панель навигации можно ви_ деть в нижней части окна броузера на рис. 14.4.
В теге <script> примера определяются функции, а кнопки и текстовое поле для ввода URL_адреса – в теге <form>. Для вызова функций используются обработчики событий щелчков на кнопках. Хотя до сих пор обработчики событий для HTML_ форм еще не обсуждались, для понимания данного примера это несущественно.
312 Глава 14. Работа с окнами броузера
Рис. 14.4. Панель навигации
Из примера 14.7 вы узнаете, как используются объекты History и Location, функ_ ции setTimeout() и Window.open(). Увидите, как JavaScript_код из фрейма с пане_ лью навигации ссылается на другой фрейм по имени. Кроме того, вам встретятся блоки try/catch в тех местах, где согласно положениям политики общего проис_ хождения могут генерироваться исключения.
Пример 14.7. Панель навигации
<!__
Этот файл реализует панель навигации, предназначенную для фрейма в нижней части окна. Включите ее в набор фреймов следующим образом: