Введение. Выразительный JavaScript: Document Object Model (объектная модель документа)

Этот раздел представляет краткое знакомство с Объектной Моделью Документа (DOM) - что такое DOM, каким образом предоставляются структуры HTML и XML документов, и как взаимодействовать с ними. Данный раздел содержит справочную информацию и примеры.

Что такое Объектная Модель Документа (DOM)?

Объектная Модель Документа (DOM) – это программный интерфейс (API) для HTML и XML документов. DOM предоставляет структурированное представление документа и определяет то, как эта структура может быть доступна из программ, которые могут изменять содержимое, стиль и структуру документа. Представление DOM состоит из структурированной группы узлов и объектов, которые имеют свойства и методы. По существу, DOM соединяет веб-страницу с языками описания сценариев либо языками программирования.

Веб-страница – это документ. Документ может быть представлен как в окне браузера, так и в самом HTML-коде. В любом случае, это один и тот же документ. DOM предоставляет другой способ представления, хранения и управления этого документа. DOM полностью поддерживает объектно-ориентированнное представление веб-страницы, делая возможным её изменение при помощи языка описания сценариев наподобие JavaScript.

Каким образом доступен DOM?

Вы не должны делать ничего особенного для работы с DOM. Различные браузеры имеют различную реализацию DOM, эти реализации показывают различную степень соответсвия с действительным стандартом DOM (это тема, которую мы пытались не затрагивать в данной документации), но каждый браузер использует свой DOM, чтобы сделать веб страницы доступными для взаимодествия с языками сценариев.

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

В следующем примере внутри элемента определен код JavaScript, данный код устанавливает функцию при загрузке документа (когда весь DOM доступен для использования). Эта функция создает новый элемент H1, добавляет текст в данный элемент, а затем добавляет H1 в дерево документа:

< html > < head > < script > // запуск данной функции при загрузке документа window. onload = function () { // создание нескольких элементов // в пустой HTML странице heading = document. createElement( "h1" ) ; heading_text = document. createTextNode( "Big Head!" ) ; heading. appendChild( heading_text) ; document. body. appendChild( heading) ; } < body > Важные типы данных

Данный раздел предназначен для краткого описания различных типов и объектов в простой и доступной манере. Существует некоторое количество различных типов данных, которые используются в API, на которые вы должны обратить внимание. Для простоты, синтаксис примеров в данном разделе обычно ссылается на узлы как на element s, на массивы узлов как на nodeList s (либо просто element s) и на атрибуты узла, просто как на attribute s.

Ниже таблица с кратким описанием этих типов данных.

document Когда член возвращает объект типа document (например, свойство элемента ownerDocument возвращает документ к которому он относится), этот обьект document является собственным корневым обьектом. В DOM document Reference разделе описан объект document.
element
element обозначает элемент или узел типа element, возвращаемый членом DOM API. Вместо того, чтобы говорить, что метод document.createElement() возвращает ссылку на node, мы просто скажем, что этот элемент возвращает element, который просто был создан в DOM. Объекты element реализуют DOM element интерфейс и также более общий Node интерфейс. Оба интерфейса включены в эту справку.
nodeList
NodeList

массив элементов, как тот, что возвращается методом Document.getElementsByTagName(). Конкретные элементы в массиве доступны по индексу двумя способами:

  • list.item(1)

Эти способы эквивалентны. В первом способе item() - единственный метод объекта NodeList. Последний использует обычный синтаксис массивов, чтобы получить второе значение в списке.

attribute Когда attribute возвращается членом API (например, метод createAttribute()) - это будет ссылка на объект, который предоставляет специальный (хоть и небольшой) интерфейс для атрибутов. Атрибуты - это узлы в DOM, как и элементы, хотя вы можете редко использовать их в таком виде.
namedNodeMap namedNodeMap подобна массиву, но элементы доступны по имени или индексу. Доступ по индексу - это лишь для удобства перечисления, т.к. элементы не имеют определенног порядка в списке. Этот тип данных имеет метод item() для этих целей и вы можете также добавлять и удалять элементы из namedNodeMap
DOM-интерфейсы (DOM interfaces)

Это руководство об объектах и реальных вещах, которые вы можете использовать для управления DOM-иерархией. Есть много моментов, где понимание того, как это работает, может удивлять. Например, объект, представляющий HTML form элемент, берет своё свойство name из интерфейса HTMLFormElement, а свойство className - из интерфейса HTMLElement. В обоих случаях свойство, которое вы хотите, находится в этом объекте формы.

Кроме того, отношение между объектами и интерфейсами, которые они реализуют в DOM может быть удивительным и этот раздел пытается рассказать немного о существующих интерфейсах в DOM и о том, как они могут быть доступны.

Интерфейсы и объекты (Interfaces and objects)

Многие объекты реализуют действия из нескольких интерфейсов. Объект таблицы, например, реализует специальный , который включает такие методы как createCaption и insertRow . Но так как это таблица - это ещё и HTML-элемент, table реализует интерфейс Element , описанный в разделе . Наконец, так как HTML-элемент (в смысле DOM) - это узел (node) в дереве, которое составляет объектную модель для HTML- или XML-страницы, табличный элемент также реализует более общий интерфейс Node , из которого происходит Element .

Var table = document.getElementById("table"); var tableAttrs = table.attributes; // Node/Element interface for (var i = 0; i < tableAttrs.length; i++) { // HTMLTableElement interface: border attribute if(tableAttrs[i].nodeName.toLowerCase() == "border") table.border = "1"; } // HTMLTableElement interface: summary attribute table.summary = "note: increased border";

Основные интерфейсы в DOM (Core interfaces in the DOM)

Этот раздел перечисляет несколько самых распространенных интерфейсов в DOM. Идея не в том чтобы описать, что делают эти методы API, но в том чтобы дать вам несколько мыслей насчет видов методов и свойств, которые вы будете часто видеть, используя DOM. Эти распространенные части API использованы в большинстве примеров раздела в конце этой справки.

Document, window - это объекты, чьи интерфейсы вы, как правило, очень часто используете в программировании DOM. Говоря простыми словами, объект window представляет что-то вроде браузера, а объект document - корень самого документа. Element наследуется от общего интерфейса Node , и эти интерфейсы вместе предоставляют много методов и свойств, которые можно применять у отдельных элементов. Эти элементы также могут иметь отдельные интерфейсы для работы с типами данных, которые эти элементы содержат, как в примере с объектом table в предыдущем случае.

Ниже представлен краткий список распространненых членов API, используемых в программировании веб- и XML-страниц с использованием DOM:

  • parentNode.appendChild (node)
Тестирование DOM API

Этот документ содержит примеры для каждого интерфейса, который вы можете использовать в своей разработке. В некоторых случаях примеры - полноценные веб-страницы с доступом к DOM в элементе , также перечислены элементы, необходимые чтобы запустить скрипт в форме, и HTML-элементы, над которыми будут производиться операции DOM. Когда встречается такой случай, можно просто копировать и вставить пример в новый HTML-документ, сохранить и запустить его в браузере.

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

Вы можете использовать эту тестовую страницу или похожую для проверки интерфейсов DOM, которые вас интересуют и просмотра того, как они работают в браузерах. Вы можете обновить содержмое функции test() при необходимости, создать больше кнопок или добавить элементы при необходимости.

< html > < head > < title > DOM Tests < script type = " application/javascript" > function setBodyAttr (attr, value) { if (document. body) eval ("document.body." + attr+ "="" + value+ """ ) ; else notSupported () ; } < body > < div style =" margin : .5 in; height : 400 ; " > < p > < b > < tt > text < form > < select onChange = " setBodyAttr(" text" , this.options.value);" > < option value = " black" > black < option value = " darkblue" > darkblue < p > < b > < tt > bgColor < select onChange = " setBodyAttr(" bgColor" , this.options.value);" > < option value = " white" > white < option value = " lightgrey" > gray < p > < b > < tt > link < select onChange = " setBodyAttr(" link" , this.options.value);" > < option value = " blue" > blue < option value = " green" > green < small > < a href = " http://www.brownhen.com/dom_api_top.html" id = " sample" > (sample link) < br > < form > < input type = " button" value = " version" onclick = " ver()" />

Чтобы протестировать много интерфейсов на одной странице, набор свойств, которые изменяют цвета веб-страницы, можно создать похожую веб-страницу с целой "консолью" кнопок, текстовых полей и других элементов. Следующий скриншот даёт идею, как интерфейсы могут быть сгруппированы вместе для тестирования

В этом примере выпадающее меню динамически обновляет доступные из DOM части веб-страницы (например, фоновый цвет, цвет ссылок и цвет текста). Однако при разработке тестовых страниц, тестирование интерфейсов, как вы об этом прочитали, важная часть изучения эффективной работы с DOM.

На этом уроке мы рассмотрим, что такое объектная модель документа, и какую роль она играет при написании сценариев на языке JavaScript.

Что такое объектная модель документа (DOM)?

Объектная модель документа (DOM - Document Object Model) - это множество объектов, которые браузер создаёт в памяти компьютера (браузера) на основе кода HTML. В JavaScript она используется для создания динамических веб-страниц, т.е. таких страниц которые мгновенно изменяются, если изменяется объектная модель этого документа. Т.е. веб-разработчик при создании таких страниц работает не с HTML кодом данной страницы, а с объектами (DOM), которые браузер создаёт на основе этого кода.

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

Структура HTML кода

Но перед тем как перейти к изучению объектной модели документа необходимо вспомнить что из себя представляет исходный код веб-страницы (HTML документа).

Исходный код веб-страницы (HTML документа) состоит из HTML тегов и текста. Теги - это базовая синтаксическая конструкция кода HTML. Большинство тегов являются парными, т.е. один из них открывающий, а другой закрывающий. Одна такая пара тегов образует элемент HTML. В документе HTML элементы вкладываются друг в друга. В итоге веб-страница (HTML документ) представляет собой множество вложенных друг в друга элементов HTML .

В качестве примера рассмотрим следующий код HTML: Заголовок страницы Название статьи Раздел статьи

Содержимое статьи

Корневым элементом данного документа является элемент html , в него вложены элементы head и body . Элемент head содержит элемент title , а элемент body содержит элементы h1 и div . Элемент div в свою очередь содержит элемент h2 и p .

Дерево DOM

Браузер, получая код HTML из интернета, сначала строит дерево узлов (объектов) в своей памяти. А уже потом на основе этого дерева "рисует" картинку этой страницы в своём окне или вкладке.

Дерево DOM - это множество узлов (объектов, элементов) связанных друг с другом. При этом каждый элемент HTML образует узел в этом дереве.

Связи между узлами (объектами, элементами) определяются на основе того что каждый элемент в HTML документе вложен в какой-то другой элемент. Элемент, который содержит другие элементы, по отношению к ним является родителем . У любого элемента в HTML коде есть свой родитель и притом только один. Если элемент содержит другие элементы, то для него они являются дочерними (детьми, прямыми потомками) . Один элемент может содержать сколько угодно много дочерних элементов. Если все элементы HTML кода "выстроить" в зависимости от их отношения друг к другу, то у нас в результате получится дерево. Строится это дерево браузером сверху вниз.

В самом верху этого дерева находится узел document . Данный узел имеет один дочерний узел html , который образован элементом html ( ... ). Узлы head ( ... ) и body ( ... ) вложены в узел html и по отношению к нему являются его дочерними узлами. Узел head содержит один дочерний узел title ( ... ). Узлы h1 и div вложены в узел body , который для них является родительским. Узел div содержит узлы h2 ( ... ) и p (), которые по отношению к нему являются его дочерними узлами.

Браузер построив на основе кода HTML дерево узлов всегда может осуществить обратную операцию на основе этого дерева создать код HTML.

Основная работа с HTML страницей в JavaScript состоит в том, чтобы работать с этими узлами. Как только вы изменяется свойство этих узлов в дереве, добавляете новые или удаляете существующие, то автоматически происходят изменения на экране пользователя и автоматически изменяется код HTML. Т.е. происходят обратные действия, на основе дерева строится код HTML.

Задание

На основе дерева DOM, представленного на рисунке, создать код HTML.

Когда вы открываете веб-страницу в браузере, он получает исходный текст HTML и разбирает (парсит) его примерно так, как наш парсер из главы 11 разбирал программу. Браузер строит модель структуры документа и использует её, чтобы нарисовать страницу на экране.

Это представление документа и есть одна из игрушек, доступных в песочнице JavaScript. Вы можете читать её и изменять. Она изменяется в реальном времени – как только вы её подправляете, страница на экране обновляется, отражая изменения.

Структура документа

Можно представить HTML как набор вложенных коробок. Теги вроде и включают в себя другие теги, которые в свою очередь включают теги, или текст. Вот вам пример документа из предыдущей главы:

Моя домашняя страничка Моя домашняя страничка

Привет, я Марийн и это моя домашняя страничка.

А ещё я книжку написал! Читайте её здесь.

У этой страницы следующая структура:

Структура данных, использующаяся браузером для представления документа, отражает его форму. Для каждой коробки есть объект, с которым мы можем взаимодействовать и узнавать про него разные данные – какой тег он представляет, какие коробки и текст содержит. Это представление называется Document Object Model (объектная модель документа), или сокращённо DOM.

Мы можем получить доступ к этим объектам через глобальную переменную document. Её свойство documentElement ссылается на объект, представляющий тег. Он также предоставляет свойства head и body, в которых содержатся объекты для соответствующих элементов.

Деревья Вспомните синтаксические деревья из главы 11. Их структура удивительно похожа на структуру документа браузера. Каждый узел может ссылаться на другие узлы, у каждого из ответвлений может быть своё ответвление. Эта структура – типичный пример вложенных структур, где элементы содержат подэлементы, похожие на них самих.

Мы зовём структуру данных деревом, когда она разветвляется, не имеет циклов (узел не может содержать сам себя), и имеет единственный ярко выраженный «корень». В случае DOM в качестве корня выступает document.documentElement.

Деревья часто встречаются в вычислительной науке. В дополнение к представлению рекурсивных структур вроде документа HTML или программ, они часто используются для работы с сортированными наборами данных, потому что элементы обычно проще найти или вставлять в отсортированное дерево, чем в отсортированный одномерный массив.

У типичного дерева есть разные узлы. У синтаксического дерева языка Egg были переменные, значения и приложения. У приложений всегда были дочерние ветви, а переменные и значения были «листьями», то есть узлами без дочерних ответвлений.

То же и у DOM. Узлы для обычных элементов, представляющих теги HTML, определяют структуру документа. У них могут быть дочерние узлы. Пример такого узла - document.body. Некоторые из этих дочерних узлов могут оказаться листьями – например, текст или комментарии (в HTML комментарии записываются между символами).

У каждого узлового объекта DOM есть свойство nodeType, содержащее цифровой код, определяющий тип узла. У обычных элементов он равен 1, что также определено в виде свойства-константы document.ELEMENT_NODE. У текстовых узлов, представляющих отрывки текста, он равен 3 (document.TEXT_NODE). У комментариев - 8 (document.COMMENT_NODE).

То есть, вот ещё один способ графически представить дерево документа:

Листья – текстовые узлы, а стрелки показывают взаимоотношения отец-ребёнок между узлами.

Стандарт Использовать загадочные цифры для представления типа узла – это подход не в стиле JavaScript. Позже мы встретимся с другими частями интерфейса DOM, которые тоже кажутся чуждыми и нескладными. Причина в том, что DOM разрабатывался не только для JavaScript. Он пытается определить интерфейс, не зависящий от языка, который можно использовать и в других системах – не только в HTML, но и в XML, который представляет из себя формат данных общего назначения с синтаксисом, напоминающим HTML.

Получается неудобно. Хотя стандарты – и весьма полезная штука, в нашем случае преимущество независимости от языка не такое уж и полезное. Лучше иметь интерфейс, хорошо приспособленный к языку, который вы используете, чем интерфейс, который будет знаком при использовании разных языков.

Чтобы показать неудобную интеграцию с языком, рассмотрим свойство childNodes, которое есть у узлов DOM. В нём содержится объект, похожий на массив, со свойством length, и пронумерованные свойства для доступа к дочерним узлам. Но это – экземпляр типа NodeList, не настоящий массив, поэтому у него нет методов вроде forEach.

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

Но эти проблемы не фатальные. JavaScript позволяет создавать абстракции. Легко написать вспомогательные функции, позволяющие выражать операции более понятно и коротко. Вообще, такого рода инструменты предоставляют много библиотек, направленных на программирование для браузера.

Обход дерева Узлы DOM содержат много ссылок на соседние. Это показано на диаграмме:

Хотя тут показано только по одной ссылке каждого типа, у каждого узла есть свойство parentNode, указывающего на его родительский узел. Также у каждого узла-элемента (тип 1) есть свойство childNodes, указывающее на массивоподобный объект, содержащий его дочерние узлы.

В теории можно пройти в любую часть дерева, используя только эти ссылки. Но JavaScript предоставляет нам много дополнительных вспомогательных ссылок. Свойства firstChild и lastChild показывают на первый и последний дочерний элементы, или содержат null у тех узлов, у которых нет дочерних. previousSibling и nextSibling указывают на соседние узлы – узлы того же родителя, что и текущего узла, но находящиеся в списке сразу до или после текущей. У первого узла свойство previousSibling будет null, а у последнего nextSibling будет null.

При работе с такими вложенными структурами пригождаются рекурсивные функции. Следующая ищет в документе текстовые узлы, содержащие заданную строку, и возвращает true, когда находит:

Function talksAbout(node, string) { if (node.nodeType == document.ELEMENT_NODE) { for (var i = 0; i < node.childNodes.length; i++) { if (talksAbout(node.childNodes[i], string)) return true; } return false; } else if (node.nodeType == document.TEXT_NODE) { return node.nodeValue.indexOf(string) > -1; } } console.log(talksAbout(document.body, "книг")); // → true

Свойства текстового узла nodeValue содержит строчку текста.

Поиск элементов Часто бывает полезным ориентироваться по этим ссылкам между родителями, детьми и родственными узлами и проходить по всему документу. Однако если нам нужен конкретный узел в документе, очень неудобно идти по нему, начиная с document.body и тупо перебирая жёстко заданный в коде путь. Поступая так, мы вносим в программу допущения о точной структуре документа – а её мы позже можем захотеть поменять. Другой усложняющий фактор – текстовые узлы создаются даже для пробелов между узлами. В документе из примера у тега body не три дочерних (h1 и два p), а целых семь: эти три плюс пробелы до, после и между ними.

Var link = document.body.getElementsByTagName("a"); console.log(link.href);

У всех узлов-элементов есть метод getElementsByTagName, собирающий все элементы с данным тэгом, которые происходят (прямые или не прямые потомки) от этого узла, и возвращает его в виде массивоподобного объекта.

Чтобы найти конкретный узел, можно задать ему атрибут id и использовать метод document.getElementById.

Мой страус Гертруда:

var ostrich = document.getElementById("gertrude"); console.log(ostrich.src);

Третий метод – getElementsByClassName, который, как и getElementsByTagName, ищет в содержимом узла-элемента и возвращает все элементы, содержащие в своём классе заданную строчку.

Меняем документ Почти всё в структуре DOM можно менять. У узлов-элементов есть набор методов, которые используются для их изменения. Метод removeChild удаляет заданную дочерний узел. Для добавления узла можно использовать appendChild, который добавляет узел в конец списка, либо insertBefore, добавляющий узел, переданную первым аргументом, перед узлом, переданным вторым аргументом.

var paragraphs = document.body.getElementsByTagName("p"); document.body.insertBefore(paragraphs, paragraphs);

Узел может существовать в документе только в одном месте. Поэтому вставляя параграф «Три» перед параграфом «Один» мы фактически удаляем его из конца списка и вставляем в начало, и получаем «Три/Один/Два». Все операции по вставке узла приведут к его исчезновению с текущей позиции (если у него таковая была).

Метод replaceChild используется для замены одного дочернего узла другим. Он принимает два узла: новый, и тот, который надо заменить. Заменяемый узел должен быть дочерним узлом того элемента, чей метод мы вызываем. Как replaceChild, так и insertBefore в качестве первого аргумента ожидают получить новый узел.

Создание узлов В следующем примере нам надо сделать скрипт, заменяющий все картинки (тег) в документе текстом, содержащимся в их атрибуте “alt”, который задаёт альтернативное текстовое представление картинки.

Для этого надо не только удалить картинки, но и добавить новые текстовые узлы им на замену. Для этого мы используем метод document.createTextNode.

Это в .

Заменить

function replaceImages() { var images = document.body.getElementsByTagName("img"); for (var i = images.length - 1; i >= 0; i--) { var image = images[i]; if (image.alt) { var text = document.createTextNode(image.alt); image.parentNode.replaceChild(text, image); } } }

Получая строку, createTextNode даёт нам тип 3 узла DOM (текстовый), который мы можем вставить в документ, чтобы он был показан на экране.

Цикл по картинкам начинается в конце списка узлов. Это сделано потому, что список узлов, возвращаемый методом getElementsByTagName (или свойством childNodes) постоянно обновляется при изменениях документа. Если б мы начали с начала, удаление первой картинки привело бы к потере списком первого элемента, и во время второго прохода цикла, когда i равно 1, он бы остановился, потому что длина списка стала бы также равняться 1.

Если вам нужно работать с фиксированным списком узлов вместо «живого», можно преобразовать его в настоящий массив при помощи метода slice.

Var arrayish = {0: "один", 1: "два", length: 2}; var real = Array.prototype.slice.call(arrayish, 0); real.forEach(function(elt) { console.log(elt); }); // → один // два

Для создания узлов-элементов (тип 1) можно использовать document.createElement. Метод принимает имя тега и возвращает новый пустой узел заданного типа. Следующий пример определяет инструмент elt, создающий узел-элемент и использующий остальные аргументы в качестве его детей. Эта функция потом используется для добавления дополнительной информации к цитате.

Никакая книга не может быть закончена. Во время работы над ней мы узнаём достаточно для того, чтобы найти её незрелой сразу же после того, как мы отвлеклись от неё. function elt(type) { var node = document.createElement(type); for (var i = 1; i < arguments.length; i++) { var child = arguments[i]; if (typeof child == "string") child = document.createTextNode(child); node.appendChild(child); } return node; } document.getElementById("quote").appendChild(elt("footer", "-", elt("strong", "Карл Поппер"), ", предисловие ко второму изданию ", elt("em", "Открытое общество и его враги "), ", 1950"));

Атрибуты К некоторым элементам атрибутов, типа href у ссылок, можно получить доступ через одноимённое свойство объекта. Это возможно для ограниченного числа часто используемых стандартных атрибутов.

Но HTML позволяет назначать узлам любые атрибуты. Это полезно, т.к. позволяет вам хранить дополнительную информацию в документе. Если вы придумаете свои названия атрибутов, их не будет среди свойств узла-элемента. Вместо этого вам надо будет использовать методы getAttribute и setAttribute для работы с ними.

Код запуска 00000000.

У кошки четыре ноги.

var paras = document.body.getElementsByTagName("p"); Array.prototype.forEach.call(paras, function(para) { if (para.getAttribute("data-classified") == "secret") para.parentNode.removeChild(para); });

Рекомендую перед именами придуманных атрибутов ставить “data-“, чтобы быть уверенным, что они не конфликтуют с любыми другими. В качестве простого примера мы напишем подсветку синтаксиса, который ищет теги (“preformatted”, предварительно отформатированный – используется для кода и простого текста) с атрибутом data-language (язык) и довольно грубо пытается подсветить ключевые слова в языке.

Function highlightCode(node, keywords) { var text = node.textContent; node.textContent = ""; // Очистим узел var match, pos = 0; while (match = keywords.exec(text)) { var before = text.slice(pos, match.index); node.appendChild(document.createTextNode(before)); var strong = document.createElement("strong"); strong.appendChild(document.createTextNode(match)); node.appendChild(strong); pos = keywords.lastIndex; } var after = text.slice(pos); node.appendChild(document.createTextNode(after)); }

Функция highlightCode принимает узел И регулярку (с включённой настройкой global), совпадающую с ключевым словом языка программирования, которое содержит элемент.

Свойство textContent используется для получения всего текста узла, а затем устанавливается в пустую строку, что приводит к очищению узла. Мы в цикле проходим по всем вхождениям выражения keyword, добавляем между ними текст в виде простых текстовых узлов, а совпавший текст (ключевые слова) добавляем, заключая их в элементы (жирный шрифт).

Мы можем автоматически подсветить весь код страницы, перебирая в цикле все элементы У которых есть атрибут data-language, и вызывая на каждом highlightCodeс правильной регуляркой.

Var languages = { javascript: /\b(function|return|var)\b/g /* … etc */ }; function highlightAllCode() { var pres = document.body.getElementsByTagName("pre"); for (var i = 0; i < pres.length; i++) { var pre = pres[i]; var lang = pre.getAttribute("data-language"); if (languages.hasOwnProperty(lang)) highlightCode(pre, languages); } }

Вот пример:

А вот и она, функция идентификации:

Function id(x) { return x; } highlightAllCode();

Есть один часто используемый атрибут, class, имя которого является ключевым словом в JavaScript. По историческим причинам, когда старые реализации JavaScript не умели обращаться с именами свойств, совпадавшими с ключевыми словами, этот атрибут доступен через свойство под названием className. Вы также можете получить к нему доступ по его настоящему имени “class” через методы getAttribute и setAttribute.

Расположение элементов (layout) Вы могли заметить, что разные типы элементов располагаются по-разному. Некоторые, типа параграфов

И заголовков растягиваются на всю ширину документа и появляются на отдельных строках. Такие элементы называют блочными. Другие, как ссылки или жирный текст появляются на одной строчке с окружающим их текстом. Они называются встроенными (inline).

Для любого документа браузеры могут построить расположение элементов, расклад, в котором у каждого будет размер и положение на основе его типа и содержимого. Затем этот расклад используется для создания внешнего вида документа.

Размер и положение элемента можно узнать через JavaScript. Свойства offsetWidth и offsetHeight выдают размер в пикселях, занимаемый элементом. Пиксель – основная единица измерений в браузерах, и обычно соответствует размеру минимальной точки экрана. Сходным образом, clientWidth и clientHeight дают размер внутренней части элемента, не считая бордюра (или, как говорят некоторые, поребрика).

Я в коробочке

var para = document.body.getElementsByTagName("p"); console.log("clientHeight:", para.clientHeight); console.log("offsetHeight:", para.offsetHeight);

Самый эффективный способ узнать точное расположение элемента на экране – метод getBoundingClientRect. Он возвращает объект со свойствами top, bottom, left, и right (сверху, снизу, слева и справа), которые содержат положение элемента относительно левого верхнего угла экрана в пикселях. Если вам надо получить эти данные относительно всего документа, вам надо прибавить текущую позицию прокрутки, которая содержится в глобальных переменных pageXOffset и pageYOffset.

Разбор документа – задача сложная. В целях быстродействия браузерные движки не перестраивают документ каждый раз после его изменения, а ждут так долго. как это возможно. Когда программа JavaScript, изменившая документ, заканчивает работу, браузеру надо будет просчитать новую раскладку страницы, чтобы вывести изменённый документ на экран. Когда программа запрашивает позицию или размер чего-либо, читая свойства типа offsetHeight или вызывая getBoundingClientRect, для предоставления корректной информации тоже необходимо рассчитывать раскладку.

Программа, которая периодически считывает раскладку DOM и изменяет DOM, заставляет браузер много раз пересчитывать раскладку, и в связи с этим будет работать медленно. В следующем примере есть две разные программы, которые строят линию из символов X шириной в 2000 пикс, и измеряют время работы.

function time(name, action) { var start = Date.now(); // Текущее время в миллисекундах action(); console.log(name, "заняло", Date.now() - start, "ms"); } time("тупо", function() { var target = document.getElementById("one"); while (target.offsetWidth < 2000) target.appendChild(document.createTextNode("X")); }); // → тупо заняло 32 ms time("умно", function() { var target = document.getElementById("two"); target.appendChild(document.createTextNode("XXXXX")); var total = Math.ceil(2000 / (target.offsetWidth / 5)); for (var i = 5; i < total; i++) target.appendChild(document.createTextNode("X")); }); // → умно заняло 1 ms

Стили Мы видели, что разные элементы HTML ведут себя по-разному. Некоторые показываются в виде блоков, другие встроенные. Некоторые добавляют визуальный стиль – например, делает жирным текст и делает текст подчёркнутым и синим.

Внешний вид картинки в теге или то, что ссылка в теге при клике открывает новую страницу, связано с типом элемента. Но основные стили, связанные с элементом, вроде цвета текста или подчёркивания, могут быть нами изменены. Вот пример использования свойства style (стиль):

Зелёная ссылка

Атрибут style может содержать одно или несколько объявлений свойств (color), за которым следует двоеточие и значение. В случае нескольких объявлений они разделяются точкой с запятой: “color: red; border: none”.

Много всякого можно изменить при помощи стилей. Например, свойство display контролирует, показывается ли элемент в блочном или встроенном виде.

Текст показан встроенным, в виде блока, и вообще не виден.

Блочный элемент выводится отдельным блоком, а последний вообще не виден – display: none отключает показ элементов. Таким образом можно прятать элементы. Обычно это предпочтительно полному удалению их из документа, потому что их легче потом при необходимости снова показать.

Код JavaScript может напрямую действовать на стиль элемента через свойство узла style. В нём содержится объект, имеющий свойства для всех свойств стилей. Их значения – строки, в которые мы можем писать для смены какого-то аспекта стиля элемента.

Красотень

var para = document.getElementById("para"); console.log(para.style.color); para.style.color = "magenta";

Некоторые имена свойств стилей содержат дефисы, например font-family. Так как с ними неудобно было бы работать в JavaScript (пришлось бы писать style["font-family"]), названия свойств в объекте стилей пишутся без дефиса, а вместо этого в них появляются прописные буквы: style.fontFamily

Каскадные стили Система стилей в HTML называется CSS (Cascading Style Sheets, каскадные таблицы стилей). Таблица стилей – набор стилей в документе. Его можно писать внутри тега:
strong { font-style: italic; color: gray; }

Теперь текст тега strong наклонный и серый.

«Каскадные» означает, что несколько правил комбинируются для получения окончательного стиля документа. В примере на стиль по умолчанию для, который делает текст жирным, накладывается правило из тега, по которому добавляется font-style и цвет.

Когда значение свойства определяется несколькими правилами, приоритет остаётся у более поздних. Если бы стиль текста в включал правило font-weight: normal, конфликтующее со стилем по умолчанию, то текст был бы обычный, а не жирный. Стили, которые применяются к узлу через атрибут style, имеют наивысший приоритет.

В CSS возможно задавать не только название тегов. Правило для.abc применяется ко всем элементам, у которых указан класс “abc”. Правило для #xyz применяется к элементу с атрибутом id равным “xyz” (атрибуты id необходимо делать уникальными для документа).

Subtle { color: gray; font-size: 80%; } #header { background: blue; color: white; } /* Элементы p, у которых указаны классы a и b, а id задан как main */ p.a.b#main { margin-bottom: 20px; }

Приоритет самых поздних правил работает, когда у правил одинаковая детализация. Это мера того, насколько точно оно описывает подходящие элементы, определяемая числом и видом необходимых аспектов элементов. К примеру, правило для p.a более детально, чем правила для p или просто.a, и будет иметь приоритет.

Запись p > a {…} применима ко всем тегам, находящимся внутри тега и являющимся его прямыми потомками.
p a {…} применимо также ко всем тегам внутри, при этом неважно, является ли прямым потомком или нет.

Селекторы запросов В этой книге мы не будем часто использовать таблицы стилей. Понимание их работы критично для программирования в браузере, но подробное разъяснение всех их свойств заняло бы 2-3 книги. Главная причина знакомства с ними и с синтаксисом селекторов (записей, определяющих, к каким элементам относятся правила) – мы можем использовать тот же эффективный мини-язык для поиска элементов DOM.

Метод querySelectorAll, существующий и у объекта document, и у элементов-узлов, принимает строку селектора и возвращает массивоподобный объект, содержащий все элементы, подходящие под него.

Люблю грозу в начале мая

Когда весенний первый гром

Как бы резвяся и играя

Грохочет в небе голубом.

function count(selector) { return document.querySelectorAll(selector).length; } console.log(count("p")); // Все элементы

// → 4 console.log(count(".animal")); // Класс animal // → 2 console.log(count("p .animal")); // Класс animal внутри

// → 2 console.log(count("p > .animal")); // Прямой потомок

// → 1

В отличие от методов вроде getElementsByTagName, возвращаемый querySelectorAll объект не интерактивный. Он не изменится, если вы измените документ.

Метод querySelector (без All) работает сходным образом. Он нужен, если вам необходим один конкретный элемент. Он вернёт только первое совпадение, или null, если совпадений нет.

Расположение и анимация Свойство стилей position сильно влияет на расположение элементов. По умолчанию оно равно static, что означает, что элемент находится на своём обычном месте в документе. Когда оно равно relative, элемент всё ещё занимает место, но теперь свойства top и left можно использовать для сдвига относительно его обычного расположения. Когда оно равно absolute, элемент удаляется из нормального «потока» документа – то есть, он не занимает место и может накладываться на другие. Кроме того, его свойства left и top можно использовать для абсолютного позиционирования относительно левого верхнего угла ближайшего включающего его элемента, у которого position не равно static. А если такого элемента нет, тогда он позиционируется относительно документа.

Мы можем использовать это для создания анимации. Следующий документ показывает картинку с котом, которая двигается по эллипсу.

var cat = document.querySelector("img"); var angle = 0, lastTime = null; function animate(time) { if (lastTime != null) angle += (time - lastTime) * 0.001; lastTime = time; cat.style.top = (Math.sin(angle) * 20) + "px"; cat.style.left = (Math.cos(angle) * 200) + "px"; requestAnimationFrame(animate); } requestAnimationFrame(animate);

Картинка отцентрирована на странице и ей задана position: relative. Мы постоянно обновляем свойства top и left картинки, чтобы она двигалась.

Скрипт использует requestAnimationFrame для вызова функции animate каждый раз, когда браузер готов перерисовывать экран. Функция animate сама опять вызывает requestAnimationFrame, чтобы запланировать следующее обновление. Когда окно браузера (или закладка) активна, это приведёт к обновлениям со скорость примерно 60 раз в секунду, что позволяет добиться хорошо выглядящей анимации.

Если бы мы просто обновляли DOM в цикле, страница бы зависла и ничего не было бы видно. Браузеры не обновляют страницу во время работы JavaScript, и не допускают в это время работы со страницей. Поэтому нам нужна requestAnimationFrame – она сообщает браузеру, что мы пока закончили, и он может заниматься своими браузерными вещами, например обновлять экран и отвечать на запросы пользователя.

Наша функция анимации передаётся текущее время через аргументы, которое оно сравнивает с предыдущим (переменная lastTime), чтобы движение кота было однородным, и анимация работала плавно. Если бы мы просто передвигали её на заданный промежуток на каждом шаге, движение бы запиналось если бы, например, другая задача загрузила бы компьютер.

Движение по кругу осуществляется с применением тригонометрических функций Math.cos и Math.sin. Я кратко опишу их для тех, кто с ними незнаком, так как они понадобятся нам в дальнейшем.

Math.cos и Math.sin полезны тогда, когда надо найти точки на круге с центром в точке (0, 0) и радиусом в единицу. Обе функции интерпретируют свой аргумент как позицию на круге, где 0 обозначает точку с правого края круга, затем нужно против часовой стрелки, пока путь диной в 2π (около 6.28) не проведёт нас по кругу. Math.cos считает координату по оси x той точки, которая является нашей текущей позицией на круге, а Math.sin выдаёт координату y. Позиции (или углы) больше, чем 2π или меньше чем 0, тоже допустимы – повороты повторяются так, что a+2π означает тот же самый угол, что и a.

Использование синуса и косинуса для вычисления координат Анимация кота хранит счётчик angle для текущего угла поворота анимации, и увеличивает его пропорционально прошедшему времени каждый раз при вызове функции animation. Этот угол используется для подсчёта текущей позиции элемента image. Стиль top подсчитывается через Math.sin и умножается на 20 – это вертикальный радиус нашего эллипса. Стиль left считается через Math.cos и умножается на 200, так что ширина эллипса сильно больше высоты.

Стилям обычно требуются единицы измерения. В нашем случае приходится добавлять px к числу, чтобы объяснить браузеру, что мы считаем в пикселях (а не в сантиметрах, ems или других единицах). Это легко забыть. Использование чисел без единиц измерения приведёт к игнорированию стиля – если только число не равно 0, что не зависит от единиц измерения.

Итог Программы JavaScript могут изучать и изменять текущий отображаемый браузером документ через структуру под названием DOM. Эта структура данных представляет модель документа браузера, а программа JavaScript может изменять её для изменения видимого документа. DOM организован в виде дерева, в котором элементы расположены иерархически в соответствии со структурой документа. У объектов элементов есть свойства типа parentNode и childNodes, которы используются для ориентирования на дереве.

Внешний вид документа можно изменять через стили, либо добавляя стили к узлам напрямую, либо определяя правила для каких-либо узлов. У стилей есть очень много свойств, таких, как color или display. JavaScript может влиять на стиль элемента напрямую через его свойство style.

УпражненияСтроим таблицу Мы строили таблицы из простого текста в главе 6. HTML упрощает построение таблиц. Таблица в HTML строится при помощи следующих тегов:

name height country
Kilimanjaro 5895 Tanzania

Для каждой строки в теге содержится тег. Внутри него мы можем размещать ячейки: либо ячейки заголовков, либо обычные ячейки.

Те же данные, что мы использовали в главе 6, снова доступны в переменной MOUNTAINS.

Напишите функцию buildTable, которая, принимая массив объектов с одинаковыми свойствами, строит структуру DOM, представляющую таблицу. У таблицы должна быть строка с заголовками, где имена свойств обёрнуты в элементы, и должно быть по одной строчке на объект из массива, где его свойства обёрнуты в элементы. Здесь пригодится функция Object.keys, возвращающая массив, содержащий имена свойств объекта.

Когда вы разберётесь с основами, выровняйте ячейки с числами по правому краю, изменив их свойство style.textAlign на "right".

/* Определяет стили для красивых таблиц */ table { border-collapse: collapse; } td, th { border: 1px solid black; padding: 3px 8px; } th { text-align: left; } function buildTable(data) { // Ваш код } document.body.appendChild(buildTable(MOUNTAINS));

Элементы по имени тегов Метод getElementsByTagName возвращает все дочерние элементы с заданным именем тега. Сделайте свою версию этого метода в виде обычной функции, которая принимает узел и строчку (имя тега) и возвращает массив, содержащий все нисходящие узлы с заданным именем тега.

Чтобы выяснить имя тега элемента, используйте свойство tagName. Заметьте, что оно возвратит имя тега в верхнем регистре. Используйте методы строк toLowerCase или toUpperCase.

Заголовок с элементом span внутри.

Параграф с раз, два элементами spans.

function byTagName(node, tagName) { // Ваш код } console.log(byTagName(document.body, "h1").length); // → 1 console.log(byTagName(document.body, "span").length); // → 3 var para = document.querySelector("p"); console.log(byTagName(para, "span").length); // → 2

Шляпа кота Расширьте анимацию кота, чтобы и кот и его шляпа летали по противоположным сторонам эллипса.

Или пусть шляпа летает вокруг кота. Или ещё что-нибудь интересное придумайте.

Чтобы упростить расположение множества объектов, неплохо будет переключиться на абсолютное позиционирование. Тогда top и left будут считаться относительно левого верхнего угла документа. Чтобы не использовать отрицательные координаты, вы можете добавить заданное число пикселей к значениям position.

var cat = document.querySelector("#cat"); var hat = document.querySelector("#hat"); // Your code here.