JavaScript с нуля — страница 20 из 30

Как мы видели в предыдущей главе, DOM — это не более чем древовидная структура (рис. 25.1), состоящая из всех элементов нашего HTML-документа.

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

Не буду принуждать вас к подобной пытке. Пока что. В текущей главе вы научитесь использовать встроенные функции querySelector и querySelectorall, которые удовлетворят большую часть ваших поисковых нужд в DOM.

Поехали!

Рис. 25.1. Действительно выглядит как древовидная структура

Знакомьтесь с семейством querySelector

Чтобы лучше понять всю прелесть возможностей, предоставляемых querySelector и querySelectorAll, взгляните на следующий HTML-код:

В этом примере у вас есть один div с id main, а также четыре элемента div и img, каждый из которых имеет значение класса pictureContainer и theImage соответственно. В нескольких следующих разделах мы задействуем функции querySelector и querySelectorAll в этом HTML-документе и посмотрим, что это даст.

querySelector

На базовом уровне функция querySelector работает так:

let element = document.querySelector("CSS selector");

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

Например, если мы захотим обратиться к div с id main из недавнего примера, то напишем следующее:

let element = document.querySelector("#main");

Так как main является id, синтаксис селектора для нацеливания на него будет #main. Аналогичным образом давайте определим селектор для класса pictureContainer:

let element = document.querySelector(".pictureContainer");

Вернется при этом первый div, чье значение класса будет pictureContainer. Остальные элементы div с тем же значением класса pictureContainer будут просто проигнорированы.

Синтаксис селектора не изменяется и не становится особенным из-за того, что вы работаете в JavaScript. Вы можете использовать для селекторов в точности тот синтаксис, который используете в таблице стилей или в области стилей.

querySelectorAll

Функция querySelectorAll возвращает все найденные элементы, которые совпадают с предоставленным вами селектором:

let elements = document.querySelectorAll("CSS selector");

Все описанное мной относительно querySelector также относится и к querySelectorAll, за исключением числа возвращаемых элементов. Эта важная деталь определяет то, что вы в итоге получаете, используя querySelectorAll. При этом возвращается не единичный элемент, а целый массивоподобный контейнер элементов.

Продолжая недавний пример HTML, то представим, как выглядел бы наш код JavaScript, если бы мы хотели использовать querySelector для помощи в отображении атрибута src среди элементов img, содержащих значение класса theImage:

let images = document.querySelectorAll(".theImage");


for (let i = 0; i < images.length; i++) {

let image = images[i];

console.log(image.getAttribute("src"));

}

Видите? Здесь все достаточно просто. Главное — это запомнить, как работать с массивами, чему к данному моменту вы уже должны были научиться. Еще одна (немного странная) особенность — это загадочная функция getAttribute. Если вы с ней не знакомы и не знаете, как считывать значения элементов, то волноваться не стоит. Мы все это вскоре подробно рассмотрим. Пока просто знайте, что она позволяет вам считывать значение любого HTML-атрибута, который может иметь рассматриваемый HTML-элемент.

Таков синтаксис селектора CSS

Когда я впервые использовал querySelector и querySelectorAll, то меня удивило, что в качестве аргумента они, по сути, получают всевозможные вариации синтаксиса селектора CSS. При этом вам не обязательно делать их простыми, как я показывал до сих пор.

Если вам понадобится нацелиться на все элементы img без необходимости указывать значение класса, то вот как может выглядеть вызов querySelectorAll:

let images = document.querySelectorAll("img");

Если вы захотите нацелиться только на изображение, чей атрибут src установлен как meh.png, то можете сделать следующее:

let images = document.querySelectorAll("img[src='meh.png']");

Обратите внимание, что я просто указал селектор атрибута[3] в качестве аргумента для querySelectorAll. Практически любые сложные выражения, которые вы можете определить для селектора в CSS-документе, также могут быть определены в качестве аргумента для querySelector или querySelectorAll.

Однако есть и некоторые подвохи, о которых стоит знать.

Не все селекторы псевдоклассов допустимы. Селекторы, состоящие из: visited,link,:before и::after, будут проигнорированы, и элементы не будут найдены.

Допустимый диапазон возможностей предоставления вами селекторов зависит от поддержки CSS браузером. Internet Explorer 8 поддерживает querySelector и querySelectorAll, но не поддерживает CSS3. Это значит использование всего, что новее селекторов, определенных в CSS2, не будет работать с querySelector и querySelectorAll в IE8. Скорее всего, это вас не коснется, так как вы наверняка используете более новые версии браузеров, в которых эта проблема с IE8 абсолютно не актуальна.

Селектор, который вы указываете, применяется только к наследникам стартового элемента, с которого начинается поиск. При этом сам этот стартовый элемент в поиск не включается. Не все вызовы querySelector и querySelectorAll должны производиться из document.

КОРОТКО О ГЛАВНОМ

Функции querySelector и querySelectorAll чрезвычайно полезны в сложных документах, где нацелиться на конкретный элемент зачастую не так просто. Полагаясь на грамотно организованный синтаксис селектора CSS, мы можем охватывать как малое, так и большое количество нужных нам элементов. Если мне требуются все элементы изображений, я просто могу написать querySelectorAll("img"). Если мне нужен только непосредственно элемент img, содержащийся внутри его родителя div, то я могу написать querySelector("div + img"). Это все очень круто.

Прежде чем завершить тему: есть еще кое-что важное, о чем хочется сказать. Во всем этом захватывающем процессе поиска элементов недостает функций getElementById, getElementsByTagName и getElementsByClassName. В свое время именно они использовались для поиска элементов в DOM. Функции querySelector и querySelectorAll — это настоящее и будущее решение этой задачи, поэтому не стоит беспокоиться о перечисленных функциях getElement*. На данный момент единственным их преимуществом перед querySelector и querySelectorAll является производительность. getElementById весьма быстра, и вы можете своими глазами увидеть ее в сравнении здесь: https://jsperf.com/getelementbyid-vs-queryselector/11.

Однако как сказал один мудрый человек: «Жизнь слишком коротка, чтобы тратить ее на изучение старых функций JavaScript, даже если они немного быстрее!»

Глава 26. Модифицирование элементов DOM

На данном этапе вы уже знаете, что такое DOM. Вы также видели, как осуществляется поиск элементов с помощью querySelector и querySelectorAll. Теперь мы изучим, как изменять эти найденные элементы.

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

Поехали!

Элементы DOM — они как объекты

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

Чтобы наглядно изобразить все сказанное, предположим, что у нас есть элемент изображения, определенный в разметке:

Sneezing Panda!

height="100"/>

Когда браузер считывает документ и доходит до этого элемента, он создает узел в DOM, который представляет его, как это показано на рис. 26.1.

Это представление в DOM дает нам возможность делать все, что мы могли делать в разметке. На деле оказывается, что представление DOM позволяет производить даже больше действий в отношении HTML-элементов, чем при использовании самой разметки. Этого мы в некоторой степени коснемся в текущей главе и существенно серьезнее рассмотрим в дальнейшем. Причина такой гибкости HTML-элементов при их рассмотрении через DOM кроется в том, что они имеют много общего с обычными объектами JavaScript. Наши элементы DOM содержат свойства, позволяющие получать и устанавливать значения и вызывать методы. Они имеют форму наследования, рассмотренную нами несколько ранее, при которой функциональность, предоставляемая каждым элементом DOM, распространяется по базовым типам Node, Element и HTMLElement (рис. 26.2).

Рис. 26.1. Все наши HTML-элементы в итоге будут иметь представление в DOM

Рис. 26.2. Иерархия элементов представления, с которыми мы обычно сталкиваемся в HTML

Наверняка элементы DOM даже пахнут, как Object, когда забегают в дом после прогулки под дождем.

Несмотря на все схожести, в связи с требованием закона и в целях сохранения ментального здоровья я должен предоставить следующий дисклеймер: DOM разрабатывалась не для того, чтобы имитировать принцип работы объекта. Многое из того, что мы можем делать с объектами, мы определенно можем делать и с DOM, но это лишь потому, что поставщики браузеров обеспечили такую возможность. Спецификации W3C не предписывают DOM вести себя в точности так, как компоненты ведут себя при работе с обычными объектами. Хоть я бы и не стал об этом особо волноваться, но если вы когда-нибудь решите расширить элементы DOM или произвести более продвинутые действия, связанные с объектами, то обязательно выполните тесты во всех браузерах, просто чтобы убедиться, что все работает, как вам надо.

Теперь, когда это нескладное обсуждение позади, давайте уже начнем именно модифицировать DOM.

Пора модифицировать элементы DOM

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


Hello…





What's happening?



Просто поместите весь этот код в HTML-документ и следуйте дальнейшим указаниям. Если просмотреть этот HTML в браузере, то вы увидите изображение, аналогичное рис. 26.3.

Рис. 26.3. Что происходит?

На самом деле происходит не так уж много. Основная часть содержимого — это тег h1, отображающий текст What’s happening?:

What's happening?

Теперь если переключиться на обзор с позиции DOM, то на рис. 26.4 можно увидеть, как выглядит текущий пример при отображении всех HTML-элементов и узлов вроде document и window.

Рис. 26.4. Так выглядит структура DOM нашего примера

В ближайших разделах мы рассмотрим кое-какие возможности модифицирования элементов DOM.

Изменение значения текста элемента

Начнем с самого простого. Многие HTML-элементы могут отображать текст. Примером таких элементов являются заголовки, абзацы, разделы, вводные данные, кнопки и многое другое. У всех них есть одна общая деталь, а именно то, что изменение значения текста вы производите, устанавливая свойство textContent.

Предположим, нужно изменить текст, появляющийся в элементе h1 нашего примера. Следующий фрагмент кода показывает, как это будет выглядеть:

What's happening?


Если вы внесете эти изменения и снова запустите код, то в браузере вы увидите то, что показано на рис. 26.5.

Рис. 26.5. Изменение значения текста заголовка

Теперь давайте посмотрим, что же конкретно мы сделали, чтобы внести эти изменения. Первым шагом модифицирования любого HTML-элемента в JavaScript будет получение ссылки на него:

let headingElement = document.querySelector("#bigMessage");

Именно здесь появляются наши старые знакомые querySelector и querySelectorAll. Как мы увидим позже, есть и второстепенные способы сослаться на элемент. Тем не менее прямой подход, приведенный здесь, мы будем использовать, когда у вас будет очень конкретное понимание того, на какой элемент или элементы вы хотите нацелиться.

Как только у нас появилась ссылка на элемент, мы можем просто установить в нем свойство textContent:

headingElement.textContent = "Oppa Gangnam Style!";

Чтобы увидеть текущее значение свойства textContent, вы можете считать его, как и любую переменную. Мы также можем установить свойство для изменения текущего значения, как мы сделали это здесь. После выполнения этой строки кода наше оригинальное значение What’s happening? в разметке будет заменено в DOM на то, что установлено в JavaScript.

Значения атрибутов

Один из основных способов отличия HTML-элементов заключается в их атрибутах и хранимых в этих атрибутах значениях. Например, следующие три элемента изображений отличаются по атрибутам src и alt:

Sneezing Panda!

Cat sliding into box!

Dog chasing its tail!

К каждому HTML-атрибуту (включая пользовательские data-*) можно обратиться через свойства, предоставляемые DOM. Чтобы облегчить нам работу с атрибутами, элементы предлагают самоочевидные методы aetAttribute и aetAttribute.

Метод getAttribute позволяет указать имя атрибута в элементе, где он существует. Если атрибут найден, этот метод вернет значение, ассоциированное с этим атрибутом. Вот пример:

What's happening?


В этом фрагменте кода стоит обратить внимание на то, что мы получаем значение атрибута id в элементе h1. Если мы укажем имя несуществующего атрибута, то получим значение null. В противоположность получению значения существует его установка. Чтобы установить значение, мы используем соответственно названный метод setAttribute. Делается это вызовом setAttribute для элемента, на который нужно воздействовать, и указанием как имени атрибута, так и значения, которое он должен в итоге хранить.

Вот пример использования setAttribute:

What's happening?


Мы устанавливаем (на самом деле перезаписываем) атрибут class в элементе h1 на bar foo. Функция setAttribute не производит никакой проверки, чтобы убедиться, что мы устанавливаем в элемент допустимый для него атрибут. Поэтому ничто не мешает нам сделать какую-нибудь глупость вроде этой:

What's happening?


Элемент h1 не содержит атрибут src, но мы можем его указать. Когда код будет запущен, элемент h1 даже примерит на себя этот атрибут src, но ему наверняка будет неудобно.

Прежде чем мы продолжим, хочу кое-что прояснить. В этих примерах использования setAttribute и getAttribute я выбрал id и class. Для этих двух атрибутов у нас есть и другой способ установки. В связи с тем что процесс установки атрибутов id и class очень распространен, наши HTML-элементы открыто выражают свойства id и className:

What's happening?


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

СОВЕТ

Есть существенно лучший способ указания значений класса, чем использование className. Этот способ реализуется через куда более интересное свойство classList, о котором вы подробно узнаете в следующей главе.

КОРОТКО О ГЛАВНОМ

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

Основные выводы из текущей темы в том, что производимые вами изменения DOM практически всегда будут принимать одну из двух форм:

• установка свойства;

• вызов метода.

Методы textContent, setAttribute и getAttribute, рассмотренные нами, покрывают оба этих подхода, и вскоре вы часто будете встречать не только их самих, но и их друзей.

Это весьма увесистая тема! Если у вас есть вопросы, не откладывайте и скорее обращайтесь на форум https://forum.kirupa.com.

Глава 27. Cтиль контента