Подготовил: Евгений Рыжков Дата публикации: 15.11.2010
Дерево документа (document tree) — это схема построения HTML документа, которая показывает связи между различными элементами страницы: порядок следования и вложенность элементов. Эта схема помогает ориентироваться в этой, на первый взгляд, хаотичной каше HTML тегов.
Веб разработчику дерево документа помогает при написании CSS правил и Javascript сценариев.
ЗаметкаНе нужно путать дерево документа и объектную модель документа (DOM). DOM — понятие более сложное (о нем будет написано немного позже).
Чтобы не вдаваться в долгие и нудные объяснения, почему дерево документов назвали деревом, рассмотрим пример — возьмем простой HTML код:
Загоовок страницы Основной заголовок
абзац текста.
- пункт 1
- пункт 2
Так видят HTML код непросвещенные аборигены, которые случайно нажали просмотр кода страницы. А вот наметанный глаз веб разработчика разберет его по полочкам, увидит все уровни вложенности и взаимосвязи. Он выстроит из хаоса четкую иерархическую структуру в виде дерева (потому что схема похожа на очертания дерева):
Родственные связиМежду элементами дерева документа существуют определенные связи. Рассмотрим их.
Предки и потомкиИз схематического изображения дерева, да и из самого HTML кода, понятно, что одни элементы являются вложенными в другие. Элементы, которые содержат другие, являются предками (ancestor) по отношению к во всем вложенным в него. Вложенные в свою очередь являются его потомками (descendant).
Для наглядности рассмотрим одну ветку нашего дерева:
Каждый предок может иметь неограниченное число потомков. Каждый потомок будет иметь число предков в зависимости от структуры дерева и в какой ветке он будет расположен, но в любом случае как минимум один предок будет.
Родители и дочериРодитель (parent) — это непосредственный предок (предок первого уровня) элемента. И наоборот, непосредственный потомок (потомок первого уровня) называется дочерним элементом (child).
Каждый родитель может иметь неограниченное число дочерей. У дочернего элемента будет только один родитель.
Элемент-родитель еще называют прямой предок , а дочерний элемент — прямой потомок . Это что-то вроде семантических названий.
Сестринские элементыСестринские элементы (siblings) — это группа из двух и более элементов, у которых общий родитель. Элементы не обязательно должны быть одного типа, просто у них должен быть общий родитель.
Смежные элементыСмежные элементы (adjacent) — это сестринские элементы, которые расположены «по соседству».
Предыдущий сестринский и следующий сестринскийТут должно быть все понятно из самих названий терминов. Предыдущий сестринский (preceding sibling) — предыдущий по коду сестринский элемент. На нашей примере-ветке для
- это будет
- , вложенных в него; кроме того, каждый элемент
- может иметь узлы
- того же уровня.
Операции дерева
Любое дерево содержит узлы, которые могут являться отдельными конструкторами дерева, и мы определим операции для обоих конструкторов: Node и Tree .
Node- data — здесь хранятся значения;
- parent — указывает на родительский элемент узла;
- children — указывает на следующий узел в списке.
- _root — указывает на корневой узел дерева;
- traverseDF(callback) — проходит узлы дерева с помощью метода DFS ;
- traverseBF(callback) — проходит узлы дерева с помощью метода BFS ;
- contains(data, traversal) — ищет узел дерева;
- add(data, toData, traverse) — добавляет узел к дереву;
- remove(child, parent) — удаляет узел дерева.
Теперь давайте напишем код дерева.
Свойства NodeДля реализации мы сначала определим функцию с именем Node , а затем конструктор с именем Tree :
function Node(data) { this.data = data; this.parent = null; this.children = ; }
Каждый экземпляр Node содержит три свойства: data , parent и children . Первое свойство используется для хранения данных, связанных с узлом. Второе свойство указывает на один узел. Третье свойство указывает на дочерние узлы.
Свойства TreeОпределим наш конструктор для Tree , который в своем теле содержит конструктор Node :
function Tree(data) { var node = new Node(data); this._root = node; }
Tree содержит две строки кода. Первая строка создает новый экземпляр Node ; вторая строка назначает node в качестве корневого элемента дерева.
Для определения Tree и Node требуется лишь несколько строк кода. Но этого достаточно, чтобы помочь нам задать иерархические данные. Чтобы доказать это, давайте используем несколько примеров для создания экземпляра Tree :
var tree = new Tree("CEO"); // {data: "CEO", parent: null, children: } tree._root;
Благодаря parent и children мы можем добавлять узлы в качестве дочерних элементов для _root , а также назначать _root в качестве родительского элемента для этих узлов. Другими словами, мы можем задать иерархию данных.
Методы дереваМы создадим следующие пять методов:
- traverseDF(callback) ;
- traverseBF(callback) ;
- contains(data, traversal) ;
- add(child, parent) ;
- remove(node, parent) .
Так как для каждого метода требуется прохождение дерева, мы сначала реализуем методы, которые определяют различные типы прохождения дерева.
Метод traverseDF(callback)Метод для прохождения дерева с помощью поиска в глубину:
Tree.prototype.traverseDF = function(callback) { // это рекурсивная и мгновенно вызываемая функция (function recurse(currentNode) { // шаг 2 for (var i = 0, length = currentNode.children.length; i < length; i++) { // шаг 3 recurse(currentNode.children[i]); } // шаг 4 callback(currentNode); // шаг 1 })(this._root); };
traverseDF(callback) содержит параметр с именем обратного вызова. (callback) — функция, которая будет вызываться позже в traverseDF(callback) .
Тело traverseDF(callback) включает в себя еще одну функцию с именем recurse . Эта рекурсивная функция, ссылающаяся сама на себя и заканчивающаяся автоматически. Используя шаги, описанные в комментариях к функции recurse , я опишу весь процесс, который использует recurse, чтобы пройти все дерево.
Вот эти шаги:
- Мы вызываем recurse с корневым узлом дерева в качестве аргумента. На данный момент currentNode указывает на текущий узел;
- Входим в цикл for и повторяем его один раз для каждого дочернего узла для currentNode , начиная с первого;
- Внутри тела цикла for вызываем рекурсивную функцию с дочерним узлом для узла currentNode . Какой конкретно это узел, зависит от текущей итерации цикла for ;
- Когда currentNode больше не имеет дочерних элементов, мы выходим из цикла for и вызываем (callback ), который мы передали во время вызова traverseDF(callback) .
Шаги 2 (завершающий себя ), 3 (вызывающий себя ) и 4 (обратный вызов ) повторяются до прохождения каждого узла дерева.
Рекурсия — это очень сложная тема. Для ее понимания можно поэкспериментировать с нашей текущей реализацией traverseDF(callback ) и попытаться понять, как это работает.
Следующий пример демонстрирует проход по дереву с помощью traverseDF(callback) . Для этого примера я сначала создам дерево. Подход, который я буду использовать, не является идеальным, но он работает. Лучше было бы использовать метод add(value) , который мы реализуем в шаге 4:
var tree = new Tree("one"); tree._root.children.push(new Node("two")); tree._root.children.parent = tree; tree._root.children.push(new Node("three")); tree._root.children.parent = tree; tree._root.children.push(new Node("four")); tree._root.children.parent = tree; tree._root.children.children.push(new Node("five")); tree._root.children.children.parent = tree._root.children; tree._root.children.children.push(new Node("six")); tree._root.children.children.parent = tree._root.children; tree._root.children.children.push(new Node("seven")); tree._root.children.children.parent = tree._root.children; /* создаем следующее дерево one ├── two │ ├── five │ └── six ├── three └── four └── seven */
Теперь, давайте вызовем traverseDF(callback) :
tree.traverseDF(function(node) { console.log(node.data) }); /* выводим следующие строки на консоль "five" "six" "two" "three" "seven" "four" "one" */
Метод traverseBF(callback)Метод для прохождения дерева по ширине. Разница между поиском в глубину и поиском в ширину заключается в последовательности прохождения узлов дерева. Чтобы проиллюстрировать это, давайте используем дерево, которое мы создали для реализации метода traverseDF(callback) :
/* tree one (depth: 0) ├── two (depth: 1) │ ├── five (depth: 2) │ └── six (depth: 2) ├── three (depth: 1) └── four (depth: 1) └── seven (depth: 2) */
Теперь давайте передадим в traverseBF(callback) тот же обратный вызов, который мы использовали для traverseDF(callback) :
tree.traverseBF(function(node) { console.log(node.data) }); /* выводим следующие строки на консоль "one" "two" "three" "four" "five" "six" "seven" */
Выводим строки на консоль, и диаграмма нашего дерева покажет нам картину, отображающую принцип поиска в ширину. Начните с корневого узла; затем пройдите один уровень и посетите каждый узел этого уровня слева направо. Повторите этот процесс до тех пор, пока все уровни не будут пройдены. Реализуем код, с помощью которого этот пример будет работать:
Tree.prototype.traverseBF = function(callback) { var queue = new Queue(); queue.enqueue(this._root); currentTree = queue.dequeue(); while(currentTree){ for (var i = 0, length = currentTree.children.length; i < length; i++) { queue.enqueue(currentTree.children[i]); } callback(currentTree); currentTree = queue.dequeue(); } };
Определение traverseBF(callback) я объясню пошагово:
- Создаем экземпляр Queue ;
- Добавляем узел, который вызывается traverseBF(callback) для экземпляра Queue ;
- Объявляем переменную currentNode и инициализируем ее для node , который мы только что добавили в очередь;
- Пока currentNode указывает на узел, выполняем код внутри цикла while ;
- Используем цикл for для прохождения дочерних узлов currentNode ;
- В теле цикла for добавляем каждый дочерний узел в очередь;
- Принимаем currentNode и передаем его в качестве аргумента для callback ;
- Устанавливаем в качестве currentNode узел, удаляемый из очереди;
- До тех пор, пока currentNode указывает на узел, должен быть пройден каждый узел дерева. Для этого повторяем шаги с 4 по 8.
Определим метод, который позволит нам находить конкретное значение в дереве. Чтобы использовать любой из методов прохождения дерева, я устанавливаю для contains(callback, traversal) два принимаемых аргумента: данные, которые мы ищем, и тип прохождения:
Tree.prototype.contains = function(callback, traversal) { traversal.call(this, callback); };
В теле contains(callback, traversal) для передачи this и callback мы используем метод с именем call . Первый аргумент связывает traversal с деревом, для которого вызывается contains(callback, traversal) ; второй аргумент — это функция, которая вызывается на каждом узле дерева.
Представьте себе, что мы хотим вывести на консоль все узлы, которые содержат данные с нечетным числом и пройти каждый узел дерева с помощью метода BFS . Для этого нужно написать следующий код:
// дерево - это пример корневого узла tree.contains(function(node){ if (node.data === "two") { console.log(node); } }, tree.traverseBF);
Метод add(data, toData, traversal)Теперь у нас есть метод для поиска узла в дереве. Давайте определим метод, который позволит нам добавить узел к конкретному узлу:
Tree.prototype.add = function(data, toData, traversal) { var child = new Node(data), parent = null, callback = function(node) { if (node.data === toData) { parent = node; } }; this.contains(callback, traversal); if (parent) { parent.children.push(child); child.parent = parent; } else { throw new Error("Cannot add node to a non-existent parent."); } };
add(data, toData, traversal) определяет три параметра. data используется для создания нового экземпляра узла. toData — используется для сравнения каждого узла в дереве. Третий параметр, traversal — это тип прохождения дерева, используемый в этом методе.
В теле add(data, toData, traversal) мы объявляем три переменные. Первая переменная, child , инициализируется как новый экземпляр Node . Вторая переменная, parent , инициализируется как null ; но позже она будет указывать на любой узел в дереве, который соответствует значению toData . Переназначение parent выполняется в третьей переменной — callback .
callback — это функция, которая сравнивает toData со свойством data каждого узла. Если узел удовлетворяет условию оператора if , он назначается в качестве parent .
Само сравнение каждого узла с toData осуществляется внутри add(data, toData, traversal) . Тип прохождения и callback должны передаваться в качестве аргументов add(data, toData, traversal) .
Если parent в дереве не существует, мы помещаем child в parent.children ; мы также назначаем в качестве parent родительский элемент для child , иначе выдается ошибка.
Давайте используем add(data, toData, traversal) в нашем примере:
var tree = new Tree("CEO"); tree.add("VP of Happiness", "CEO", tree.traverseBF); /* наше дерево "CEO" └── "VP of Happiness" */
Вот более сложный пример использования add(data, toData, traversal) :
var tree = new Tree("CEO"); tree.add("VP of Happiness", "CEO", tree.traverseBF); tree.add("VP of Finance", "CEO", tree.traverseBF); tree.add("VP of Sadness", "CEO", tree.traverseBF); tree.add("Director of Puppies", "VP of Finance", tree.traverseBF); tree.add("Manager of Puppies", "Director of Puppies", tree.traverseBF); /* дерево "CEO" ├── "VP of Happiness" ├── "VP of Finance" │ ├── "Director of Puppies" │ └── "Manager of Puppies" └── "VP of Sadness" */
Метод remove(data, fromData, traversal)Для полной реализации Tree нам нужно добавить метод с именем remove(data, fromData, traversal) . Аналогично удалению узла из DOM этот метод будет удалять узел и все его дочерние узлы:
Tree.prototype.remove = function(data, fromData, traversal) { var tree = this, parent = null, childToRemove = null, index; var callback = function(node) { if (node.data === fromData) { parent = node; } }; this.contains(callback, traversal); if (parent) { index = findIndex(parent.children, data); if (index === undefined) { throw new Error("Node to remove does not exist."); } else { childToRemove = parent.children.splice(index, 1); } } else { throw new Error("Parent does not exist."); } return childToRemove; };
Так же, как add(data, toData, traversal) , метод удаления проходит все дерево, чтобы найти узел, который содержит второй аргумент, равный в настоящее время fromData . Если этот узел найден, то parent указывает на него в первом операторе if .
Согласно модели DOM:
- Весь документ представляется узлом документа;
- Каждый HTML тэг является узлом элемента;
- Текст внутри HTML элементов представляется текстовыми узлами;
- Каждому HTML атрибуту соответствует узел атрибута;
- Комментарии являются узлами комментариев.
HTML документ
Заголовок
Просто текст
Пример 6.2
В этом примере корневым узлом является тэг . Все остальные узлы содержатся внутри . У этого узла имеется два дочерних узла: и . Узел содержит узел , а узел содержит узлы и
Следует обратить особое внимание на то, что текст, расположенный в узле элемента соответствует текстовому узлу. В примере HTML документ узел элемента содержит текстовый узел " HTML документ ", то есть " HTML документ " не является значением элемента . Тем не менее, в рамках HTML DOM значение текстового узла может быть доступно посредством свойства innerHTML.
Все узлы HTML документа могут быть доступны посредством дерева, при этом их содержимое может быть изменено или удалено, а также можно добавить новые элементы.
Все узлы дерева находятся в иерархических отношениях между собой. Для описания этих отношений используются термины родитель , дочерний элемент и потомок . Родительские узлы имеют дочерние узлы, а дочерние элементы одного уровня называются потомками (братьями или сестрами).
В отношении узлов дерева соблюдаются следующие принципы:
- Самый верхний узел дерева называется корневым;
- Каждый узел, за исключением корневого, имеет ровно один родительский узел;
- Узел может иметь любое число дочерних узлов;
- Конечный узел дерева не имеет дочерних узлов;
- Потомки имеют общего родителя.
Программный интерфейс HTML DOM
В рамках DOM модели HTML можно рассматривать как множество узловых объектов . Доступ к ним осуществляется с помощью JavaScript или других языков программирования. Программный интерфейс DOM включает в себя набор стандартных свойств и методов .
Свойства представляют некоторые сущности (например, ), а методы - действия над ними (например, добавить ).
К типичным свойствам DOM относятся следующие:
- x.innerHTML – внутреннее текстовое значение HTML элемента x ;
- x. nodeName – имя x ;
- x.nodeValue – значение x ;
- x.parentNode – родительский узел для x ;
- x.childNodes – дочерний узел для x ;
- x.attributes – узлы атрибутов x.
Узловой объект, соответствующий HTML элементу поддерживает следующие методы:
- x.getElementById(id) – получить элемент с указанным id ;
- x.getElementsByTagName(name) – получить все элементы с указанным именем тэга (name);
- x.appendChild(node) – вставить дочерний узел для x ;
- x.removeChild(node) – удалить дочерний узел для x.
Для получения текста из элемента
Со значением атрибута id "demo" в HTML документе можно использовать следующий код:
txt = document.getElementById("demo").innerHTML
Тот же самый результат может быть получен по-другому:
txt=document.getElementById("demo").childNodes.nodeValue
В рамках DOM возможны 3 способа доступа к узлам:
- С помощью метода getElementById(ID). При этом возвращается элемент с указанным ID.
- С помощью метода getElementsByTagName(name). При этом возвращаются все узлы с указанным именем тэга (в виде индексированного списка). Первый элемент в списке имеет нулевой индекс.
- Путем перемещения по дереву с использованием отношений между узлами.
- parentNode ;
- firstChild ;
- lastChild.
- document.documentElement – для доступа к корневому узлу документа;
- document.body – для доступа к тэгу .
- nodeName ;
- nodeValue ;
- nodeType.
- Свойство nodeName предназначено только для чтения;
- Свойство nodeName узла элемента точно соответствует имени тэга;
- Свойство nodeName узла атрибута соответствует имени атрибута;
- Свойство nodeName текстового узла всегда равно #text
- Свойство nodeName узла документа всегда равно #document
- Свойство nodeValue узла элемента не определено;
- Свойство nodeValue текстового узла указывает на сам текст;
- Свойство nodeValue узла атрибута указывает на значение атрибута.
- Alert. Применяется для уведомления пользователя, работающего с веб-браузером.
- Confirm. Применяется для выбора пользователем одного из двух вариантов ответа "Да/Нет". Соответственно Confirm возвращает значение true/false.
- Prompt. Применяется для ввода пользователем значения. При нажатии "OK" возвращается введенное значение, в случае "Cancel" возвращается значение null.
Для
— , а для не будет предыдущего сестринского.
Аналогично и следующий сестринский (following sibling): для —
Для
—
- , для
- — нет.
Предыдущий и следующий
Предыдущий элемент (preceeding) — такой же предыдущий элемент по коду, только без ограничений сестринских отношений. Для нашей ветки: для
- это будет
Для
— , для — .
DOM API не особенно сложен, но прежде чем переходить к обсуждению программирования с использованием DOM, следует разобраться с некоторыми вопросами архитектуры DOM.
Представление документов в виде деревьев
HTML-документы имеют иерархическую структуру, представленную в DOM в виде дерева. Узлы дерева представляют различные типы содержимого документа. В первую очередь, древовидное представление HTML-документа содержит узлы, представляющие элементы или теги, такие как и
И узлы, представляющие строки текста. HTML-документ также может содержать узлы, представляющие комментарии HTML.1 Рассмотрим следующий
простой HTML-документ.
Sample Document
An HTML DocumentThis is a simple document.
Тем, кто еще не знаком с древовидными структурами в компьютерном программировании, полезно знать, что они заимствуют терминологию у генеалогических деревьев. Узел, расположенный непосредственно над данным узлом, называется родительским по отношению к данному узлу. Узлы, расположенные на один уровень ниже другого узла, являются дочерними по отношению к данному узлу.
Узлы, находящиеся на том же уровне и имеющие того же родителя, называются братьями. Узлы, расположенные на любое
число уровней ниже другого узла, являются его потомками. Родительские,прародительские и любые другие узлы, расположенные выше данного узла, являются его предками.
Узлы
Древовидная структура DOM представляет собой дерево объектов Node различных типов. Интерфейс Node1 определяет свойства и методы для перемещения по дереву и манипуляций с ним. Свойство childNodes объекта Node возвращает список дочерних узлов, свойства firstChild, lastChild, nextSibling, previousSibling и parentNode предоставляют способ обхода узлов дерева. Такие методы, как appendChild(), removeChild(), replaceChild() и insertBefore(), позволяют добавлять узлы в дерево документа и удалять их.
Типы узлов
Типы узлов в дереве документа представлены специальными подынтерфейсами интерфейса Node. У любого объекта Node есть свойство nodeType, определяющее тип данного узла. Если свойство nodeType узла равно, например, константе Node.ELEMENT_NODE, значит, объект Node является также объектом Element, и можно использовать с ним все методы и свойства, определенные ин-терфейсом Element.
Свойство document-Element этого объекта ссылается на объект Element, представляющий корневой элемент документа. Для HTML-документов это тег, явно или неявно присутствующий в документе. (Помимо корневого элемента узел Document может иметь другие дочерние элементы, такие как объекты Comment.)
Основная часть дерева DOM состоит из объектов Element, представляющих такие теги, как и , и объектов Text, представляющих строки текста. Если анализатор документа сохраняет комментарии, эти комментарии представляются в дереве как объекты DOM Comment.
Атрибуты
Атрибуты элемента (например, атрибуты src и width тега ) могут быть прочитаны, установлены и удалены с помощью методов getAttribute(), set-Attribute() и removeAttribute() интерфейса Element.
Другой, менее удобный способ работы с атрибутами – это метод getAttribute-Node(), который возвращает объект Attr, представляющий атрибут и его значение. (Одной из причин выбора этой менее удобной технологии является наличие у интерфейса Attr свойства specified, позволяющего определять, указан ли данный атрибут в документе явно или для него принимается значение по умолчанию.) Однако обратите внимание, что объекты Attr не присутствуют в массиве childNodes элемента и непосредственно не являются частью дерева документа, как узлы Element и Text.
Спецификация DOM позволяет обращаться к узлам Attr через массив attributes интерфейса Node, но в Microsoft Internet Explorer определяется другой несовместимый массив attributes, что делает невозможным использование этого массива переносимым образом.
DOM HTML API
Стандарт DOM предназначен для работы как с XML, так и с HTML. Базовый DOM API – интерфейсы Node, Element, Document и другие – относительно универсален и применим к обоим типам документов. Стандарт DOM также включает интерфейсы, специфические для документов HTML. HTMLDocument – это специфический для HTML подынтерфейс интерфейса Document, а HTMLElement – специфический для HTML подынтерфейс интерфейса Element. Кроме того, DOM определяет интерфейсы для многих элементов HTML, относящиеся к конкретным тегам. Эти интерфейсы, такие как HTMLBodyElement и HTMLTitleElement, обычно определяют набор свойств, отражающих атрибуты данного тега HTML. Интерфейс HTMLDocument определяет различные свойства документа и методы, поддерживавшиеся броузерами до появления стандарта W3C. В их число входят свойство location, массив forms и метод write().
Интерфейс HTMLElement определяет свойства id, style, title, lang, dir и className, обеспечивающие удобный доступ к значениям атрибутов id, style,title, lang, dir и class, которые могут применяться со всеми тегами HTML.
Теги HTML не принимают никаких атрибутов, кроме шести только что перечисленных, и потому полностью представимы интерфейсом HTMLElement.
Для всех остальных тегов HTML в части спецификации DOM, относящейся
к HTML, определяются специальные интерфейсы. Для многих тегов HTML
эти интерфейсы не делают ничего, кроме предоставления набора свойств, соответствующих атрибутам HTML. Например, тегу
- соответствует интерфейс HTMLU ListElement, а для тега есть соответствующий интерфейс HTMLBodyElement. Поскольку эти интерфейсы просто определяют свойства, стандартизованные в HTML, они не документируются в этой книге подробно.
Можно спокойно предположить, что объект HTMLElement, представляющий определенный тег HTML, имеет свойства для каждого из стандартных атрибутов этого тега (см. соглашения о назначении имен в следующем разделе). Обратите внимание, что стандарт DOM определяет свойства для атрибутов HTML для удобства создателей сценариев. Общий (и, пожалуй, предпочтительный) способ чтения и установки значений атрибутов предоставляют методы getAttribute() и setAttribute() объекта Element. Некоторые из интерфейсов, описанных в HTML DOM, определяют дополнительные свойства или методы, отличные от тех, что соответствуют значениям атрибутов HTML. Например, интерфейс HTMLInputElement определяет методы focus() и blur(), а интерфейс HTMLFormElement – методы submit() и reset() и свойство length. Такие методы и свойства обычно присутствовали до стандартизации DOM и были сделаны частью стандарта для обратной совместимости с принятой практикой программирования. Подобные интерфейсы документированы в «Справочнике по W3C DOM» (часть V). Кроме того, информацию о частях этих интерфейсов, относящихся к «принятой практике», можно найти в части IV «Справочник по клиентскому JavaScript», хотя зачастую эта информация приведена под именем, использовавшимся до стандартизации DOM, – так, HTMLFormElement и HTMLInputElement в «Справочнике по клиентскому JavaScript» описаны в разделах «Form» и «Input».
Соглашения о назначении имен для HTML
При работе со специфическими для HTML частями стандарта DOM необхомимо иметь в виду некоторые простые соглашения о назначении имен. Имена свойств, специфических для интерфейсов HTML, начинаются со строчных букв. Если имя свойства состоит из нескольких слов, первые буквы второго и последующих слов являются прописными. Таким образом, атрибут maxlength тега транслируется в свойство maxLength интерфейса HTMLInputElement.
Когда имя атрибута HTML конфликтует с ключевым словом JavaScript, для разрешения конфликта к имени добавляется префикс «html». Например, атрибут for тега транслируется в свойство htmlFor интерфейса HTMLLabelElement. Исключение из этого правила составляет атрибут class (кото-рый может быть указан для любого элемента HTML) – он транслируется в свойство className1 интерфейса HTMLElement.
Уровни и возможности DOM
Имеются две версии, или два «уровня», стандарта DOM. DOM Level 1 был стандартизован в октябре 1998 года. Он определяет базовые интерфейсы DOM, такие как Node, Element, Attr и Document, а также различные интерфейсы, специфические для HTML. DOM Level 2 был стандартизован в ноябре 2000 года.2 Кроме некоторых изменений в базовых интерфейсах, эта версия DOM была сильно расширена за счет определения стандартных API для работы с событиями документа и каскадными таблицами стилей (CSS), а также с целью предоставления дополнительных инструментальных средств для работы с непрерывными областями документов. На момент написания этой книги рабочая группа DOM в W3C занимается стандартизацией DOM Level 3. Кроме того, иногда вы можете встретить упоминание о DOM Level 0. Этот термин не относится к какому-либо формальному стандарту, а служит для неформальной ссылки на общие средства объектных моделей документа, реализованных в Netscape и Internet Explorer до стандартизации консорциумом W3C. В DOM Level 2 стандарт стал модульным. Базовый модуль, определяющий основную древовидную структуру документа с помощью (среди прочих) интерфейсов Document, Node, Element и Next, – это единственный модуль, являющийся обязательным. Все остальные модули не обязательны и могут либо поддерживаться, либо нет, в зависимости от реализации. Реализация DOM в веб-броузере, очевидно, должна поддерживать модуль HTML, так как веб-документы пишутся на HTML. Броузеры, поддерживающие таблицы стилей CSS, обычно поддерживают модули StyleSheets и CSS, поскольку стили CSS играют ключевую роль в программировании Dynamic HTML. Аналогично, так как большинство интересных программ JavaScript требует наличия средств обработки событий, можно предполагать поддержку веб-броузерами модуля Events спецификации DOM.
К сожалению, модуль Events был лишь недавно определен спецификацией DOM Level 2 и не имел широкой поддержки на момент написания этой книги.
Соответствие DOM
На момент написания этой книги не существовало броузера, полностью соответствующего стандарту DOM. Последние релизы Mozilla ближе всего по-дошли к этому, и полная совместимость с DOM Level 2 является целью проекта Mozilla. Броузер Netscape 6.1 соответствует большинству важных модулей Level 2, а Netscape 6.0 отличает достаточно хорошая совместимость, но с некоторым пробелами. Internrt Explorer 6 в основном совместим (по крайней мере, за одним неприятным исключением) c DOM Level 1, но не поддерживает многие модули Level 2, в частности, модуль Events. Internet Explorer 5 и 5.5 имеют значительные пробелы в совместимости, но достаточно хорошо поддерживают ключевые методы DOM Level 1, чтобы запускать большинство примеров из этой главы. В версии IE для Macintosh реализована значительно более полная поддержка DOM, чем в IE 5 для Windows. Кроме Mozilla, Netscape, Internet Explorer и несколько других броузеров предлагают как минимум частичную поддержку DOM. Количество доступных броузеров стало слишком велико, а изменения в сфере поддержки стандартов происходят слишком быстро, чтобы даже пытаться в этой книге определенно утверждать, какие средства DOM поддерживает конкретный броузер. Следовательно, при определении совместимости реализации DOM в любом конкретном броузере вам придется полагаться на другие источники информации. Одним из источников информации о совместимости является сама реализация. В совместимой реализации свойство implementation объекта Document ссылается на объект DOMImplementation, определяющий метод под названием hasFeature(). Посредством этого метода (если он существует) можно получить сведения о наличии поддержки определенного модуля (или возможностей) стандарта DOM. Например, определить, поддерживает ли реализация DOM в веб-броузере базовые интерфейсы DOM Level 1 для работы с документами HTML, можно с помощью следующего кода:
If (document.implementation &&
document.implementation.hasFeature &&
document.implementation.hasFeature("html", "1.0")) {
// Броузер заявляет о поддержке базовых интерфейсов Level 1
// и интерфейсов HTML
}
Метод hasFeature() принимает два аргумента: первый – это имя проверяемого модуля, а второй – номер версии в виде строки. Он возвращает true, если указанная версия указанного модуля поддерживается.
Например, если hasFeature() указывает, что поддерживается модуль MouseEvents, это подразумевает, что также поддерживается модуль UIEvents, который, в свою очередь, подразумевает поддержку модулей Events, Views и Core.В Internet Explorer 6 (под Windows) hasFeature() возвращает true только для модуля «HTML» и версии «1.0». Он не сообщает о совместимости с любыми другими модулями
В Netscape 6.1 hasFeature() возвращает true для большинства имен модулей и номеров версий, за исключением модулей Traversal и Mutation-Events. Метод возвращает false для модулей Core и CSS2 версии 2.0, указывая на неполную совместимость (даже несмотря на то, что поддержка этих модулей очень хороша).
В этой книге документируются интерфейсы, составляющие все модули DOM. Модули Core, HTML, Traversal и Range рассматриваются в этой главе. Модули StyleSheets, CSS и CSS2 рассматриваются в главе 18, а различные модули, относящиеся к событиям (за исключением MutationEvents) – в главе 19. Часть V «Справочник по W3C DOM» содержит полное описание всех модулей.
Метод hasFeature() не абсолютно надежен. Как отмечалось выше, IE 6 сообщает о совместимости Level 1 со средствами HTML, даже несмотря на то, что в этой совместимости есть некоторые проблемы. С другой стороны, Netscape 6.1 сообщает о несовместимости с Level 2 Core, даже несмотря на то, что этот броузер почти совместим с этим модулем. В обоих случаях нужны более подробные сведения о том, что именно совместимо, а что нет. Но объем этой ин-формации слишком велик, и она слишком изменчива, чтобы включать ее в печатное издание.
Те, кто активно занимается веб-разработкой, несомненно, уже знают или скоро узнают о многих специфических для броузеров деталях совместимости. Кроме того, в Интернете есть ресурсы, которые могут оказаться полезными. Самое важное, что организация W3C (в сотрудничестве с Национальным институтом стандартов и технологии США) работает над созданием набора с открытыми исходными текстами для тестирования реализаций DOM. На
момент написания этой книги разработка тестового набора только начиналась, но он должен стать неоценимым средством тонкой проверки на совместимость реализации DOM. Подробности можно найти на сайте http://www.w3c.org/DOM/Test/.
Организация Mozilla имеет несколько тестовых наборов для различных стандартов, в том числе для DOM Level 1 (доступных на странице http://www.mozilla.org/qualitybrowser_sc.html). Компания Netscape опубликовала тестовый набор, включающий некоторые тесты для DOM Level 2 (доступный на странице http://developer.netscape.com/evangelism/tools/testsuites/). Netscape также опубликовала предвзятое (и устаревшее) сравнение совместимости с DOM ранних релизов Mozilla и IE 5.5 (доступное на странице http://home.netscape.com/browsers/future/standards.html). И наконец, вы также можете найти информацию о совместимости и соответствии стандартам на независимых сайтах в Интернете. Один из достойных упоминания сайтов публикует Петер-Пауль Кох (Peter-Paul Koch). Ссылку на таблицу совместимости DOM можно найти на его главной странице, посвященной JavaScript (http://www.xs4all.nl/~ppk/js/).
Совместимость с DOM в Internet Explorer
Поскольку IE – наиболее широко используемый веб-броузер, несколько особых замечаний о его совместимости со спецификациями DOM будут здесь уместны. IE 5 и более поздние версии достаточно хорошо поддерживают базовые и HTML-средства Level 1, чтобы запускать примеры из этой главы, и достаточно хорошо поддерживают ключевые возможности CSS Level 2 для того, чтобы запускать большинство примеров8. К сожалению, IE 5, 5.5 и 6 не поддерживают модуль Events из DOM Level 2, даже несмотря на то, что корпорация Microsoft участвовала в определении этого модуля и имела достаточно времени для его реализации в IE 6. Как мы увидим в главе 19, обработка событий играет ключевую роль в клиентском Java-Script, и отсутствие в IE поддержки стандартной модели обработки событий затрудняет разработку развитых клиентских веб-приложений. Хотя IE 6 заявляет (через свой метод hasFeature()) о поддержке базовых и HTML-интерфейсов стандарта DOM Level 1, фактически эта поддержка не полна. Наиболее вопиющая проблема, с которой вы, скорее всего, столкнетесь, – небольшая, но неприятная: IE не поддерживает константы типов узлов, определяемых в интерфейсе Node. Вспомните, что каждый узел в документе имеет свойство nodeType, задающее тип данного узла. СпецификацияDOM также утверждает, что интерфейс Node определяет константы, представляющие каждый из определяемых им типов узлов. Например, константа Node.ELEMENT_NODE представляет узел Element. В IE (по крайней мере, до версии 6 включительно) эти константы просто не существуют.
Примеры в этой главе были изменены, чтобы обойти это препятствие, и содержат целые литералы вместо соответствующих символических констант.
Например:
if (n.nodeType == 1 /*Node.ELEMENT_NODE*/)
// Проверяем, что n является объектом Element
Хороший стиль программирования требует, чтобы в код помещались константы, а не жестко кодируемые целые литералы, и те, кто захочет сделать код переносимым, могут включить в программу следующий код для определения констант, если они отсутствуют:
If (!window.Node) {
var Node = { // Если объект Node отсутствует, определяем
ELEMENT_NODE: 1, // его со следующими свойствами и значениями.
ATTRIBUTE_NODE: 2, // Обратите внимание, что это только типы узлов
TEXT_NODE: 3, // HTML. Для XML-узлов вам нужно определить
COMMENT_NODE: 8, // здесь другие константы.
DOCUMENT_NODE: 9,
DOCUMENT_FRAGMENT_NODE: 11
}
}
Независимые от языка интерфейсы DOM
Хотя стандарт DOM появился благодаря желанию иметь общий API для программирования динамического HTML, модель DOM интересна не только веб-программистам. На самом деле сейчас этот стандарт наиболее интенсивно используется серверными программами на Java и C++ для анализа документов XML и манипуляции ими. Из-за наличия многих вариантов использования стандарт DOM был определен как независимый от языка. В данной книге описывается только привязка DOM API к JavaScript, но необходимо иметь в виду и некоторые другие моменты. Во-первых, заметьте, что свойства объекта в привязке к JavaScript обычно соответствуют паре методов get/set в привязке к другим языкам. Следовательно, когда программист, пишущий на Java, спрашивает вас о методе getFirstChild() интерфейса Node, надо понимать, что в JavaScript привязка Node API не определяет метода getFirst-Child(). Вместо этого она просто определяет свойство firstChild, и чтение этого свойства в JavaScript эквивалентно вызову метода getFirstChild() в Java. Другая важная особенность привязки DOM API к JavaScript в том, что некоторые объекты DOM ведут себя как массивы JavaScript. Если интерфейс определяет метод с именем item(), объекты, реализующие этот интерфейс, ведут себя так же, как доступные только для чтения массивы с числовым индексом. Предположим, что в результате чтения свойства childNodes узла по-лучен объект NodeList. Отдельные объекты Node из списка можно получить, во-первых, передав номер нужного узла методу item(), а во-вторых, рассматривая объект NodeList как массив и обращаясь к нему по индексу. Следующий код иллюстрирует эти две возможности:
Var n = document.documentElement; // Это объект Node.
var children = n.childNodes; // Это объект NodeList.
var head = children.item(0); // Это один из способов
// использования NodeList.
var body = children; // Но есть более простой способ!
Аналогично, если у объекта DOM есть метод namedItem(), передача строки этому методу – то же самое, что использование строки как индекса массива. Например, следующие строки кода представляют собой эквивалентные способы доступа к элементу формы:
Var f = document.forms.namedItem("myform");
var g = document.forms["myform"];
var h = document.forms.myform;
Стандарт DOM может использоваться различными способами, поэтому разработчики стандарта аккуратно определили DOM API таким образом, чтобы это не ограничивало возможность реализации API другими разработчиками. В частности, стандарт DOM определяет интерфейсы вместо классов. В объектно-ориентированном программировании класс – это фиксированный тип данных, который должен быть реализован в точном соответствии со своим определением. С другой стороны, интерфейс – это коллекция методов и свойств, которые должны быть реализованы вместе. Следовательно, реализация DOM может определять любые классы, которые считает нужным, но эти классы должны определять методы и свойства различных интерфейсов DOM. Такая архитектура имеет пару важных следствий. Во-первых, имена классов, используемые в реализации, могут не соответствовать напрямую именам интерфейсов, используемым в стандарте DOM (и в этой книге). Во-вторых, один класс может реализовывать более одного интерфейса. Рассмотрим, например, объект Document. Этот объект является экземпляром некоторого класса, определенного реализацией веб-броузера. Мы не знаем, какой именно это класс, но знаем, что он реализует интерфейс Document; то есть все методы и свойства, определенные интерфейсом Document, доступны нам через объект Document. Поскольку веб-броузеры работают с HTML-документами, мы также знаем, что объект Document реализует интерфейс
HTMLDocument и что нам также доступны все методы и свойства, определенные этим интерфейсом. Кроме того, если веб-броузер поддерживает таблицы стилей CSS и реализует модуль CSS DOM, объект Document также реализует интерфейсы DOM DocumentStyle и DocumentCSS. И если веб-броузер поддерживает модули Events и Views, Document также реализует интерфейсы DocumentEvent и DocumentView.
Модель DOM разбита на независимые модули, поэтому в ней определяется несколько дополнительных второстепенных интерфейсов, таких как Docu-mentStyle, DocumentEvent и DocumentView, каждый из которых определяет только один или два метода. Подобные интерфейсы никогда не реализуются независимо от базового интерфейса Document, и по этой причине я не описываю их отдельно. Ознакомившись с описанием интерфейса Document в «Справочнике по W3C DOM», вы обнаружите, что в нем также перечислены методы и свойства различных дополнительных интерфейсов. Аналогично, посмотрев на описание дополнительных интерфейсов, вы просто найдете перекрестную ссылку на базовый интерфейс, с которым они связаны. Исключения из этого правила составляют случаи, когда дополнительный интерфейс сложен. Например, интерфейс HTMLDocument всегда реализуется тем же объектом, что реализует интерфейс Document, но так как HTMLDocument
добавляет значительный объем новой функциональности, я отвел ему отдельную справочную страницу.
Важно также понимать, что поскольку стандарт DOM определяет интерфейсы, а не классы, он не определяет никаких методов-конструкторов. Если, например, требуется создать новый объект Text для вставки в документ, то нельзя просто написать:
Var t = new Text("это новый текстовый узел"); // Такого конструктора нет!
Стандарт DOM не может определять конструкторы, но определяет в интерфейсе Document несколько полезных методов-фабрик для создания объектов. Поэтому для создания нового узла Text в документе надо написать:
Var t = document.createTextNode("это новый текстовый узел");
Методы-фабрики, определенные в DOM, имеют имена, которые начинаются со слова «create». Кроме методов-фабрик, определяемых интерфейсом Document, несколько таких методов определяются интерфейсом DOMImplementation и доступны через document.implementation.
Дерево — одна из наиболее часто используемых в веб-разработке структур данных. Каждый веб-разработчик, который писал HTML -код и загружал его в браузер, создавал дерево, которое называют объектной моделью документа (DOM ).
Например, статья, которую вы читаете в данный момент, отображается в браузере в виде дерева. Абзацы представлены в виде элементов
; элементы
Вложены в элемент ; а вложен в элемент .
Вложение данных похоже на генеалогическое древо. Элемент является родительским, является дочерним от него, а элемент является дочерним от элемента .
В этой статье мы используем два различных метода прохождения дерева: поиск в глубину (DFS ) и поиск в ширину (BFS ). Оба этих типа прохождения подразумевают различные способы взаимодействия с деревом и включают в себя использование структур данных, которые мы рассмотрели в этой серии статей. DFS использует стек, а BFS использует очередь.
Дерево (поиск в глубину и поиск в ширину)В информатике дерево представляет собой структуру, которая задает иерархические данные с узлами. Каждый узел дерева содержит собственные данные и указатели на другие узлы.
Давайте сравним дерево со структурой организации. Эта структура имеет должность верхнего уровня (корневой узел ), например генерального директора. Ниже этой должности располагаются другие должности, такие как вице-президент (VP ).
Для представления их подчиненности мы используем стрелки, указывающие от генерального директора на вице-президента. Такие должности, как генеральный директор, является узлами; связи, которые мы обозначили от генерального директора к вице-президенту, являются указателями. Для создания других связей в нашей структуре организации мы повторяем этот процесс — добавляем указатели на другие узлы.
Давайте рассмотрим DOM . DOM содержит элемент , который является элементом верхнего уровня (корневой узел ). Этот узел указывает на элементы и . Этот процесс повторяется для всех узлов в DOM .
Одним из преимуществ этой конструкции является возможность вкладывать узлы: элемент
- , например, может содержать множество элементов
Для определения длины списка узлов используется свойство length.
x = document.getElementsByTagName("p");
for (i = 0; i < x.length; i++)
document.write(x[i].innerHTML);
document.write("
");
Присер 6.4
В данном примере внутрь HTML документа вставляется в виде списка текстовое содержимое всех элементов соответствующих тэгу
Для навигации по дереву в ближайших окрестностях текущего узла можно использовать следующие свойства:
Для непосредственного доступа к тэгам можно использовать 2 специальных свойства:
Свойства узлов
В HTML DOM каждый узел является объектом, который может иметь методы (функции) и свойства. Наиболее важными являются следующие свойства:
Свойство nodeName указывает на имя узла. Это свойство имеет следующие особенности:
Замечание : nodeName всегда содержит имя тэга HTML элемента в верхнем регистре.
Свойство nodeValue указывает на значение узла. Это свойство имеет следующие особенности:
Свойство nodeType возвращает тип узла. Это свойство предназначено только для чтения:
Наиболее важными типами узлов являются следующие:
Изменение HTML элементов
HTML элементы могут быть изменены с посредством использования JavaScript, HTML DOM и событий.
В примере 6.5 показано, как можно динамически изменять текстовое содержимое тэга
Hello World!
document.getElementById("p1").innerHTML="New text!";
Пример 6.5.
Диалоговые элементы
В JavaScript поддерживается работа со следующими диалоговыми элементами интерфейса:
2. Синтаксис:
alert("сообщение");
4. Синтаксис:
confirm("вопрос");
7. Синтаксис:
prompt("вопрос/запрос","значение по умолчанию");
Ниже приводится код веб-страницы, в которой пользователь имеет возможность выбрать цвет текста с помощью диалогового элемента
// здесь будет отображаться текст
Вы выбрали цвет текста: черный
// пользователь выбирает цвет текста
var tcolor = prompt("Выберите цвет текста: red, blue, green, yellow, black","black");
// задается текст
document.getElementById("c").innerHTML = "Вы выбрали цвет текста: " + tcolor;
// задается цвет текста
document.getElementById("c").style.color = tcolor;
В этой статье хотелось бы рассказать что такое дерево документа в html и как связаны элементы в нём.
В html документе многие теги являются вложенными в другие, на пример:
< html> < head> < title> Примерный заголовок < body> < div class = ”container”> < h1> Что- то важное < div class = ”left”> < h2> Чуть менее важная инфо < p> Содержимое страницы < ul> < li> Пункт 1 < li> Пункт 2 < li> Пункт 3
Так выглядит код страницы, однако разработчик видит его иначе. Этот хаотический набор символов благодаря пониманию структуры документа выстраивается в четко определенную схему. Так он видит что head и body вложены в html, а h2 p ul в div class=”left” и так дальше.
Дерево документа – схематическое представление кода страницы, отражающие взаимосвязи тегов между собой.
В свою очередь в зависимости от характера связи объединяющего элементы страницы они подразделяются на предков и потомков, родителей и дочерей, сестринские элементы и прочее.
Предки и потомкиКак было сказано выше, в html коде элемент может внутри себя включать другие. Следовательно, тэг внутри которого находятся прочие тэги, называется предком (ancestor), а эти «другие» называются потомками (descendant).
Количество потомков у предков ничем не ограничивается. Количество же предков будет зависеть от расположения элемента внутри структуры документа.
Родители и дочериПредок, имеющий первый уровень вложенности, называется родитель (parent), а потомок содержащийся непосредственно в нем, имеет название дочерний элемент (child).
Количество дочерних элементов содержащихся внутри родителя ничем не ограничено, но дочерние могут иметь только одного родителя.
Родителей и дочерей еще иногда называют прямыми предками и прямыми потомками .
Рассмотрим на нашем примереHead выступает родителем и предком для title, а он выступает дочерним элементом и потомком для head; h2, p, ul, li выступают потомками для div class=”left”, в тоже время как li одновременно является потомком для ul и div class=”left”, а родителем li выступает только ul . В этом и заключается основное отличие предка от родителя, и дочери от потомка.
Сестринские элементы (siblings)К ним относятся дочерние элементы с общим родителем.
Сестринским тегам важно только наличие общего родителя, а тип элемента может быть разным, например:
h2, p, ul, являются сестринскими и относятся к родителю div class=”left”, в то же время все li также будут сёстрами только под родителем ul.
Смежные (adjacent)Теги стоящие в структуре документа рядом и имеющие единого родителя.
Так, для нашего примера, h2 смежный c p, ul смежный p, а p смежный ul и h2 одновременно.
Предыдущий и следующий сестринскиеТут всё просто: следующий сестринский элемент (following sibling) – по коду идет сразу после интересующего нас тега и имеет с ним общего родителя, а предыдущий сестринский (preceding sibling) представляет из себя тег предшествующий нашему и также имеющий с ним единого родителя.
Предыдущий и следующийКрайне похожи на только что описанные нами связи, но есть нюансы - в этом случае для нас не имеет значения наличие общего родителя. Например если для h2 нету предыдущего сестринского тега, то просто предыдущим (preceeding) для него будет div class=”left”, со следующим (preceding) та же картина.
Первый и последний ребенокПервый ребёнок (first-child) – это дочерний элемент, который является первым для своего родителя, последний ребёнок (last-child) – соответственно последний тег внутри родителя.
Например для div class=”left” первым ребёнком будет h2, а в качестве последнего выступает ul.
Корневой элементИм считается тег, который не имеет ни предков, ни родителей. Он включает в себя все «ветви» нашего дерева и открывается в самом начале документа ( < html> ), а закрывается в самом конце ( ).
Подводя итогиПонимание структуры дерева элементов, а также всех родственных связей в нем, поможет не только легко и быстро ориентироваться в коде, а ещё даст возможность легче и точнее выбирать в CSS необходимые нам теги.