Как говорилось в разделе 8.8, JavaScript_функции относятся к лексическому кон_ тексту. Это значит, что они работают в той области видимости, в которой опреде_ лены, а не в той, из которой вызываются. Если обработчик события определяется путем присваивания HTML_атрибуту строки JavaScript_кода, то при этом неявно определяется JavaScript_функция. Важно понимать, что область видимости для обработчика события, определенного подобным образом, не совпадает с областью видимости других глобальных JavaScript_функций, определенных обычным об_ разом. Это значит, что обработчики событий, определенные в виде HTML_атри_ бутов, выполняются в контексте, отличном от контекста других функций.1
Вспомните, в главе 4 мы говорили, что область видимости функции определяет_ ся цепочкой областей видимости или списком объектов, которые по очереди про_ сматриваются при поиске определения переменной. Когда переменная x разы_ скивается или разрешается в обычной функции, интерпретатор JavaScript сна_ чала ищет локальную переменную или аргумент, проверяя объект вызова функ_ ции на наличие свойства с таким именем. Если такое свойство не найдено, JavaScript переходит к следующему объекту в цепочке областей видимости – глобальному объекту. Интерпретатор проверяет свойства глобального объекта, чтобы выяснить, является ли переменная глобальной.
Обработчики событий, определенные как HTML_атрибуты, имеют более сложную цепочку областей видимости, чем только что описанная. Началом цепочки облас_ тей видимости является объект вызова. Здесь определены любые аргументы, пе_ реданные обработчику события (далее в этой главе мы увидим, что в некоторых развитых моделях обработки событий обработчикам передается аргумент), а так_ же любые локальные переменные, определенные в теле обработчика. Следующим объектом в цепочке областей видимости является, однако, не глобальный объект, а объект, который вызвал обработчик события. Предположим, что объект Button в HTML_форме определяется с помощью тега <input>, а затем назначением атрибу_ та onclick определяется обработчик события. Если в коде обработчика есть пере_ менная с именем form, то она разрешается в свойство form объекта Button. Это мо_ жет быть удобным при создании обработчиков событий в виде HTML_атрибутов.
<form>
<!__ В обработчиках событий ключевое слово "this" ссылается __> <!__ на элемент_источник события __>
<!__ Благодаря этому можно получить ссылку на соседний элемент формы __> <!__ следующим образом __>
<!__ И элемент <form> находится в цепочке областей видимости, __> <!__ поэтому можно опустить идентификатор "form". __>
1 Это важно понимать, однако хотя приведенное ниже обсуждение интересно, оно довольно сложное. При первом прочтении этой главы можно его пропустить и вернуться к нему позже.
<!__ Объект Document находится в цепочке областей видимоси, поэтому можно __> <!__ вызывать его методы без добавления идентификатора "document". __>
<!__ Хотя такой стиль нельзя считать правильным. __> <input id="b4" type="button" value="Button 4"
onclick="alert(getElementById('b3').value);">
</form>
Как видно из этого простого примера, цепочка областей видимости обработчика события не заканчивается на объекте, определяющем обработчик события: она продолжается вверх по иерархии и включает в себя как минимум элемент <form>, содержащий кнопку, и объект Document, который содержит форму.1 По_ следним объектом в цепочке области видимости является объект Window, как и всегда в клиентском JavaScript_коде.
Другой способ представить себе расширенную цепочку областей видимости обра_ ботчиков событий заключается в том, чтобы проанализировать порядок трансля_ ции JavaScript_кода, находящегося в атрибуте HTML_обработчика события, в Ja_ vaScript_функцию. Рассмотрим следующие строки из предыдущего примера:
Повторяющиеся инструкции with создают расширенную цепочку областей види_ мости. Если вы забыли о назначении этой не часто используемой инструкции, прочитайте раздел 6.18.
Наличие целевого объекта в цепочке областей видимости может быть полезным. В то же время наличие расширенной цепочки областей видимости, включающей другие элементы документа, может оказаться досадным неудобством. Заметьте, например, что и объект Window, и объект Document определяют методы с именем open(). Если идентификатор open применяется без уточнения, то почти всегда имеет место обращение к методу window.open(). Однако в обработчике события, определенном как HTML_атрибут, объект Document расположен в цепочке облас_ тей видимости раньше объекта Window, и отдельный идентификатор open будет ссылаться на метод document.open(). Аналогично посмотрим, что произойдет, ес_
1 Точный состав цепочки областей видимости никогда не был стандартизован и может зависеть от реализации.
414 Глава 17. События и обработка событий
ли добавить свойство с именем window объекту Form (или определить поле ввода со свойством name="window"). В этом случае, если определить внутри формы обработ_ чик события, в котором есть выражение window.open(), идентификатор window разрешится как свойство объекта Form, а не как глобальный объект Window, и у об_ работчиков событий внутри формы не будет простого способа обращения к гло_ бальному объекту Window или вызова метода window.open()!
Мораль в том, что при определении обработчиков событий как HTML_атрибутов необходима аккуратность. Подобным образом лучше создавать только очень простые обработчики. В идеале они должны просто вызывать глобальную функ_ цию, определенную в другом месте, и, возможно, возвращать ее результат:
<script>function validateForm() { /* Код проверки формы */ }</script> <form onsubmit=”return validateForm();">...</form>
Даже используя такую необычную цепочку областей видимости, простой обра_ ботчик событий, подобный приведенному, будет функционировать, однако со_ кращая программный код, вы минимизируете вероятность того, что длинная це_ почка областей видимости нарушит правильность функционирования обработ_ чика. Несмотря на это следует помнить, что при исполнении функций использу_ ется область видимости, в которой они определены, а не область видимости, из которой они вызываются. Поэтому, хотя метод validateForm() вызывается из об_ ласти видимости, которая отличается от обычной, он будет выполняться в собст_ венной глобальной области видимости.
Кроме того, поскольку нет стандарта на точный состав цепочки областей види_ мости для обработчика события, лучше всего предполагать, что она содержит только целевой элемент и глобальный объект Window. Например, нужно ссылать_ ся на целевой элемент посредством this, а если целевым является элемент <in_ put>, ссылаться на содержащий его объект Form с помощью form, вместо this.form. Но не полагайтесь на наличие в цепочке областей видимости объектов Form или Document. Например, не используйте идентификатор action вместо form.action
или getElementById() вместо document.getElementById().
Не забывайте, что все это обсуждение области видимости обработчика события применимо только к обработчикам событий, определенным как HTML_атрибу_ ты. Если обработчик события задается путем присваивания функции соответст_ вующему свойству_обработчику события, то никакой специальной цепочки об_ ластей видимости не возникает, и функция выполняется в той области видимо_ сти, в которой она определена. Ею почти всегда является глобальная область ви_ димости, если только это не вложенная функция, тогда цепочка областей видимости снова становится интересной!