Теперь, описав, что такое объект, и что в JavaScript мы работаем только с объектами того или иного типа, нужно описать главные объекты браузера, с которыми работает клиентский код страницы. Эти объекты "отображаются" внутрь языка именно для того, чтобы ими можно было манипулировать в программе. Это называется "программной объектной моделью".
Любой объект программной модели, содержит внутри себя:
- свойства - какие-то вложенные объекты, доступные по тем или иным названиям;
- методы - функции для выполнения тех или иных действий над объектом или просто собранные в одном месте на "общую тему";
- события - они есть не у всех объектов, но механизм обработки событий является одним из важнейших элементов программирования страницы.
Про свойства и методы я уже говорил выше - повторяться не будут. Скажу о событиях.
В простейшем случае событие есть поле внутри объекта, к которому обращается сам объект в тех случаях, когда по ходу своей работы фиксирует наступление события. События обычно начинаются с префикса "on...". Например, "onload" - событие "load" (загрузка). Т.е. когда объект выполняет (завершает) загрузку, он обращается к своему собственному полю "onload" и смотрит, что там в этом поле есть. Если там есть функция, он её вызывает, если там ничего нет, или нет функции, ничего не делает.
Соответственно, если программист хочет, чтобы при возникновении события срабатывал какой-то написанный программистом код, нужно назначить событию обработчик - какую-то написанную программистом функцию. После того как назначили эту функцию, она будет срабатывать каждый раз, как происходит событие. Отменили назначение - не будет больше срабатывать.
Простой способ назначения обработчика события - просто присвоить функцию соответствующему полю:
window.onload = function() {
alert("Hello world");
}
или, если функция написана отдельно:
function loadHandler() {
alert("Hello world");
}
window.onload = loadHandler;
При статической вёрстке HTML обработчики событий обычно указываются в соответствующих элементах вёрстки:
<body onload="loadHandler()">
Но здесь в строчке идёт не просто название функции, а её вызов. Т.е. вся строчка выполняется как кусочек программы.
Способ удалить ранее назначенный обработчик тоже предельно прост:
window.onload = null;
В тех случаях, когда на одно событие нужно "повесить" много разных обработчиков, вышеописанный простой способ недостаточен - каждый новый обработчик затрёт (и отменит) ранее назначенный. В этом случае либо надо либо писать собственный диспетчер событий (для собственных объектов), либо использовать стандартные методы addEventListener для добавления и removeEventListener для удаления обработчиков (работает лишь в стандартных объектах браузера, причём в IE соответствующие функции называются attachEvent и detachEvent). Однако вряд ли мы будем этим пользоваться, поэтому в эту тему углубляться не буду.
В ряде случаев событие помимо самого факта своего наступления сопровождается дополнительной информацией. Например, события о движении мыши или нажатии кнопок клавиатуры пользователем сопровождаются информацией о том, какие новые координаты у мыши, какие кнопки нажал пользователь. Если много однотипных объектов вызывают одну и ту же функцию-обработчик, хорошо бы различать, в каком конкретно объекте случилось событие. Для этих целей у обработчиков стандартных объектов может быть специальный параметр - объект event, в котором содержится вся дополнительная информация. Т.е. если эту дополнительную информацию хочется получать и обрабатывать, обработчик должен выглядеть как функция с параметром:
function myHandler(event) {
}
Если по стандарту объект event передаётся как параметр обработчика, то в IE он является полем объекта window (см. ниже). Но мы пока во все эти тонкости различий браузеров углубляться не будем.
Теперь о самих объектах программной модели.
1) Любая страница показывается в окне (вкладке). Окно представлено объектом window, доступным в любом месте программы.
В окне нас интересуют в первую очередь события onload и onunload. Первое происходит
после загрузки страницы, а второе
перед выгрузкой страницы. Если с событием unload более-менее понятно: бывает, что перед сменой страницы или закрытием окна хочет что-то сделать, например, зафиксировать, что пользователь ушёл - тогда на это событие добавляется обработчик. То событие load требует некоторого пояснения. Если мы просто напишем минимально корректный HTML версии 4 (в котором можно пропускать элементы html, head, body), у нас либо сразу начнётся HTML-вёрстка, либо в начале будет идти секция скриптов.
<SCRIPT type="text/javascript">
<!--
var x = 3;
-->
</SCRIPT>
Нужно понимать, что любой скрипт начинается выполняться немедленно после своей загрузки. Т.е. программа уже начнёт работать, хотя быть может ещё не все части HTML загрузились с сервера. В этом случае объекты программной модели будут ещё не готовы к работе. Поэтому важно, чтобы основной рабочий код начинал работу после полной загрузки страницы. Это как раз и обеспечивает событие load у объекта окна:
<SCRIPT type="text/javascript">
<!--
window.onload = function() {
alert("Загрузились");
}
window.onunload = function() {
alert("Выгружаемся");
}
-->
</SCRIPT>
Функция alert выдаёт на экран простое окошко с сообщением и приостанавливает работу программы.
Здесь запомним, что всегда будем начинать работу программы внутри обработчика window.onload.
Ещё в окне нас будут интересовать функции работы с таймерами: setTimeout, setInterval, clearInterval, clearTimeout. Они позволяют создать таймер для вызова функции-обработчик через какой-то промежуток времени - отложенный во времени вызов. Timeout срабатывает - вызывает обработчик - один раз, interval повторяет вызов обработчика периодически через равные промежутки времени. Функции set включают таймер и возвращают его код, функции clear отключают таймер по коду. Например:
<script type="text/javascript">
var timer = null;
window.onload = function() {
// Отсюда программа начинает свою полезную работу. И мы хотим вызвать функцию tick через 5 секунд.
timer = window.setTimeout(tick, 5000); // 5000 - это миллисекунды
// В общей для всех функций - глобальной - переменной timer запоминаем номер используемого таймера.
}
function tick() {
// Если время истекло, а страницу ещё не закрыли, выполняем какое-нибудь действие.
alert("Tick");
// И сбрасываем номер уже нерабочего таймера.
timer = null;
}
window.onunload = function() {
// При закрытии страницы, если таймер ещё не сработал, мы отменяем его работу.
if(timer != null) {
window.clearTimeout(timer);
}
}
</script>
2) Другим важнейшим объектом является document - он есть программная модель объектов, полностью описывающих весь HTML страницы. Он также доступен в любом месте программы.
HTML, как это обычно видно по самому его тексту, состоит из вложенных друг в друга элементов. Каждый элемент ограничивается открывающим и закрывающим тэгом:
<html></html>
Хотя в HTML 4 разрешается в некоторых случаях пропускать некоторые тэги - для простоты. Вместе элементы образуют структуру данных, называемую дерево. В дереве каждый промежуточный элемент - называется узел - имеет обязательно:
- 1 узел "предка" (parent), в состав которого включён;
- 1 или несколько узлов "потомков" (child), которых содержит внутри себя.
Узел, не имеющий предка, называется корнем. Узел, не имеющий потомков, называется листом.
Так вот, объект document внутри себя содержит (из нам интересного):
- корень всего дерева элементов страницы;
- множество методов создания новых элементов;
- методы поиска элементов.
В первую очередь нас, конечно, интересуют методы поиска. Их несколько.
Метод document.getElementsByTagName позволяет получить все элементы документа с одинаковым тэгом (например, все div). Но поскольку таких элементов много, этим методом интересно пользоваться разве что для доступа к главной секции документа - body.
var body = document.getElementsByTagName("body")[0];
Эта секция есть всегда, даже если её тэги не описаны в самом документе. И именно в ней содержатся все элементы вёрстки страницы. 0 в примере означает следующее: поскольку метод возвращает все элементы - это получается массив. Разумеется, body есть только один, поэтому мы сразу смело из этого массива берём самый первый элемент: он там заведомо один, и он там заведомо есть.
Аналогичным образом мы можем получить элемент head, чтобы работать со скриптами, стилями, заголовком и метаописаниями страницы, которые помещаются именно в него.
Второй метод поиска document.getElementById - позволяет находить элемент по идентификатору этого элемента. Идентификатор, это например так:
<div id="MyDiv"></div>
Тогда в JavaScript мы можем получить объект-элемент, соответствующий этому div, так:
var myDiv = document.getElementById("MyDiv");
Это важнейший метод поиска в тех случаях, когда статическая вёрстка HTML сочетается с кодом на JavaScript. Важно, что каждый id должен обязательно быть уникальным во всём документе.
Ещё один метод поиска - по имени:
<div name="MyDiv"></div>
Тогда в JavaScript мы можем получить объект-элемент, соответствующий этому div, так:
var myDiv = document.getElementsByName("MyDiv")[0];
В отличие от идентификатора, одинаковое имя можно (а для радиокнопок - нужно) задавать для нескольких элементов. И соответствующий метод getElementsByName находит все элементы с одинаковым именем.
Есть и другие методы поиска, например, getElementsByClassName - по классу стилей. Мы туда углубляться не будем.
Во вторую очередь нас интересуют методы создания новых узлов дерева. Вообще узлы (node) в документе бывают разных видов:
- элементы (element) - описываются тэгами;
- атрибуты (attribute) элементов (например, как тот же id выше) - описываются в открывающем тэге;
- просто куски текста (textNode) - содержатся внутри какого-нибудь элемента;
- комментарии и всякие служебные узлы - не будем в них углубляться.
Важно отметить, что текстовые узлы, атрибуты, комментарии и служебные узлы являются листьями - т.е. внутри них других узлов быть не может.
Чтобы создать элемент, нужно вызвать метод документа:
var myDiv = document.createElement("div");
Обязательно нужно указать имя тэга для элемента - здесь это div. Но таким образом создаётся всё: ссылки a, поля ввода input, строчные элементы span, таблицы table и т.д.
Для создания атрибута есть метод:
var myDivWidth = document.createAttribute("width");
Но поскольку тот же атрибут id специальный встроенный, обработчики событий можно назначать в коде, а та же ширина и т.п. визуальные свойства элемента описываются стилями, в HTML этот метод теперь имеет весьма ограниченную область применения.
Для создания текстового узла служит метод:
var t = document.createTextNode("Hello world");
Сам текст - это строчка при вызове метода.
Но просто создать узел мало. Созданный узел должен быть привязан к какому-то месту в дереве, иначе его всё равно что не существует. Поэтому важна другая группа методов и свойств, которая есть у любого элемента.
Во-первых, метод добавления внутрь себя потомка appendChild. Например:
var body = document.getElementsByTagName("body")[0];
var myDiv = document.createElement("div");
body.appendChild(myDiv);
Здесь мы создали новый div и добавили его в body текущего HTML-документа. Вот только после этого мы можем увидеть этот div на экране (если в нём, конечно, есть какое-то содержимое - тот же textNode, который тоже нужно создать и добавить, но уже внутрь div).
Во-вторых, это средство для перебора всех потомков узла. Внутри узла есть особый объект - коллекция, доступная по свойству childNodes. Она немного похожа на массив. У неё есть свойство length - количество узлов. Но это уже настоящий length. У неё же есть метод item для получения узла по индексу. Т.е. полный перебор всех потомков узла (дочерних узлов) можно сделать в цикле вида:
for(var i = 0; i < p.childNodes.length; ++i) {
var c = p.childNodes.item(i);
/* Тут p - объект родительского узла, c - очередной объект дочернего узла по отношению к родительскому.
Дальше с ними можно что-то делать. */
}
appendChild добавляет новый узел в конец коллекции childNodes. Это не всегда удобно. Поэтому есть и метод insertBefore, который вставляет новый узел перед каким-то уже имеющимся внутри childNodes.
Ну и наконец, метод удаления узла removeChild у родительского узла - ему требуется передать параметром тот самый дочерний объект-узел, который надо удалить. Например, полная очистка элемента от дочерних узлов:
while(p.childNodes.length > 0) {
p.removeChild(p.childNodes.item(0));
}
Здесь всегда удаляем самый первый дочерний узел, пока их совсем не останется.
Разумеется, есть масса других вспомогательных методов, чтобы простые вещи по манипуляции деревом записывать более компактно. Их можно смотреть в справочниках, и они бывают разными в зависимости от браузера. Но конкретно эти методы, во-первых, стандартные, во-вторых, применимы для работы не только с HTML, но и с XML. Я пока более в это углубляться не буду.
Чтобы получить текст из объекта textNode, нужно использовать его свойство nodeValue.
var tn = document.createTextNode("X");
var t = tn.nodeValue; // теперь t содержит строчку "X".
Т.е. надо понимать, что текст и текстовый узел в HTML - это разные вещи. Текстовый узел как объект содержит внутри себя строчку с текстом. Хотя при статической вёрстке HTML эта разница никак не видна.
Ещё одной важной вещью является стилизация элементов HTML. В программной модели у каждого элемента есть особое свойство style, содержащее в себе объект с описанием стилей текущего элемента. Стили задают внешний вид элемента. С объектом style можно работать как с обычным объектом, и вот тут удобен оператор with, поскольку стили обычно описываются большими группами:
var myDiv = document.createElement("div");
with(myDiv.style) {
width = "100px";
height = "100px";
backgroundColor = "#ffe0e0";
}
Для элемента div задаются стили: ширина (width) и высота (height) по 100 пикселей и цвет фона светло-красный.
Но вообще стили, таблицы их описаний, классы стилей и т.д. - это отдельная большая тема. Пока она не нужна.