ЧАСТЬ III. DOM, милый DOM

Глава 23. Все о JSON (объектная нотация JavaScript)

Когда дело доходит до хранения, извлечения или передачи данных, то в нашем распоряжении оказывается множество форматов файлов и структур данных. Вы наверняка уже использовали текстовые файлы, документы Word, электронные таблицы Excel и т. д. Что же касается фронтенд-разработки, то здесь лидирует один формат — JSON, JavaScript Object Notation.

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

Поехали!

Что такое JSON?

В JavaScript для определения объектов вы используете специальный синтаксис объектного литерала:

let funnyGuy = {

firstName: "Conan",

lastName: "O'Brien",


getName: function () {

return "Name is: " + this.firstName + " " + this.lastName;

}

};


let theDude = {

firstName: "Jeffrey",

lastName: "Lebowski",


getName: function () {

return "Name is: " + this.firstName + " " + this.lastName;

}

};


let detective = {

firstName: "Adrian",

lastName: "Monk",


getName: function () {

return "Name is: " + this.firstName + " " + this.lastName;

}

};

Если вы не понимаете этот синтаксис, то настоятельно рекомендую почитать раздел «Об объектах подробней». Это существенно упростит понимание объектов JSON и работу с ними.

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

Формат JSON очень многое заимствует от синтаксиса объектного литерала. Ниже приведен пример реальных данных JSON, возвращенных API WeatherUnderground для отображения погоды в моем родном городе Сиэтле:

{

"response": {

"version": "0.1",

"termsofService":

"http://www.wunderground.com/weather/api/d/terms.html",

"features": {

"conditions": 1

}

},

"current_observation": {

"image": {

"url": "http://icons.wxug.com/graphics/wu2/logo_130x80.png",

"title": "Weather Underground",

"link": "http://www.wunderground.com"

},

"display_location": {

"full": "Seattle, WA",

"city": "Seattle",

"state": "WA",

"state_name": "Washington",

"country": "US",

"country_iso3166": "US",

"zip": "98101",

"magic": "1",

"wmo": "99999",

"latitude": "47.61167908",

"longitude": "-122.33325958",

"elevation": "63.00000000"

},


"observation_location": {

"full": "Herrera, Inc., Seattle, Washington",

"city": "Herrera, Inc., Seattle",

"state": "Washington",

"country": "US",

"country_iso3166": "US",

"latitude": "47.616558",

"longitude": "-122.341240",

"elevation": "121 ft"

},

"estimated": {},

"station_id": "KWASEATT187",

"observation_time": "Last Updated on August 28, 9:28 PM PDT",

"observation_time_rfc822": "Fri, 28 Aug 2015 21:28:12 -0700",

"observation_epoch": "1440822492",

"local_time_rfc822": "Fri, 28 Aug 2015 21:28:45 -0700",

"local_epoch": "1440822525",

"local_tz_short": "PDT",

"local_tz_long": "America/Los_Angeles",

"local_tz_offset": "-0700",

"weather": "Overcast",

"temperature_string": "68.0 F (20.0 C)",

"temp_f": 68.0,

"temp_c": 20.0,

"relative_humidity": "71 %",

"wind_string": "Calm",

"wind_dir": "NNW",

"wind_degrees": 331,

"wind_mph": 0.0,

"wind_gust_mph": "10.0",

"wind_kph": 0,

"wind_gust_kph": "16.1",

"pressure_mb": "1008",

"pressure_in": "29.78",

"pressure_trend": "-",

"dewpoint_string": "58 F (15 C)",

"dewpoint_f": 58,

"dewpoint_c": 15,

"heat_index_string": "NA",

"heat_index_f": "NA",

"heat_index_c": "NA",

"windchill_string": "NA",

"windchill_f": "NA",

"windchill_c": "NA",

"feelslike_string": "68.0 F (20.0 C)",

"feelslike_f": "68.0",

"feelslike_c": "20.0",

"visibility_mi": "10.0",

"visibility_km": "16.1",

"solarradiation": "-",

"UV": "0",

"precip_1hr_string": "0.00 in (0 mm)",

"precip_1hr_in": "0.00",

"precip_1hr_metric": " 0",

"precip_today_string": "0.00 in (0 mm)",

"precip_today_in": "0.00",

"precip_today_metric": "0",

"icon": "cloudy",

"icon_url": "http://icons.wxug.com/i/c/k/nt_cloudy.gif",

"nowcast": ""

}

}

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

Объект JSON изнутри

Объект JSON — не более чем комбинация имен свойств и их значений. Звучит очень просто, но есть в этом важные детали, которые мы рассмотрим в текущем разделе.

Имена свойств

Имена свойств являются идентификаторами, которые вы будете использовать для обращения к значению. Располагаются они слева от двоеточия:

{

"firstName": "Kirupa",

"lastName": "Chinnathambi",

"special": {

"admin": true,

"userID": 203

},

"devices": [

{

"type": "laptop",

"model": "Macbook Pro 2015"

},

{

"type": "phone",

"model": "iPhone 6"

}

]

}

В этом фрагменте JSON имена свойств — это firstName, lastName, special, admin, userID, devices, type и model. Обратите внимание на то, как они определены, то есть представлены в виде строчных значений, заключенных в кавычки. Кавычки — это важная деталь, которую вам не требуется использовать для имен свойств в случае с объектными литералами. Поэтому имейте их в виду, когда будете работать в мире JSON.

Значения

Каждое имя свойства отображается в значение. Сами же значения могут иметь следующие типы:

• числа;

• строки;

• логические типы (true или false);

• объекты;

• массивы;

• Null.

Давайте сопоставим эти типы с примером, который видели недавно.

Строки

Ниже выделены именно строчные значения:

{

"firstName": "Kirupa",

"lastName": "Chinnathambi",

"special": {

"admin": true,

"userID": 203

},

"devices": [

{

"type": "laptop",

"model": "Macbook Pro"

{

},

"type": "phone",

"model": "iPhone XS"

}

]

}

Двойные кавычки являются убедительным признаком того, что представленные значения — это строки. Помимо привычных букв, чисел и символов можно включать в них экранирующие символы вроде \', \", \\, \/ и т. д., чтобы определять те знаки строки, которые в противном случае будут считаны как операция JSON.

Числа

В нашем примере представлен единственный член семейства чисел — это значение свойства userID:

{

"firstName": "Kirupa",

"lastName": "Chinnathambi",

"special": {

"admin": true,

"userID": 203

},

"devices": [

{

"type": "laptop",

"model": "Macbook Pro"

},

{

"type": "phone",

"model": "iPhone XS"

}

]

}

Вы можете указывать как десятичные значения (например 0.204, 1200.23, 45), так и экспоненциальные (2e16, 3e+4, 1.5e-2). При этом нужно помнить о том, что нельзя использовать префикс, начинающийся с нуля, сопровождаемого числом. Например, значение 03.14 недопустимо.

Логический тип

Логические значения весьма просты:

{

"firstName": "Kirupa",

"lastName": "Chinnathambi",

"special": {

"admin": true,

"userID": 203

},

"devices": [


{

"type": "laptop",

"model": "Macbook Pro"

},

{

"type": "phone",

"model": "iPhone XS"

}

]

}

Эти значения могут быть либо true, либо false. Здесь важно помнить, что регистр важен. Оба упомянутых значения должны использоваться в нижнем регистре. Печать в режиме предложения (True или False) либо использование только верхнего регистра (TRUE или FALSE) запрещено.

Объекты

А вот тут ситуация немного интереснее:

{

"firstName": "Kirupa",

"lastName": "Chinnathambi",

"special": {

"admin": true,

"userID": 203

},

"devices": [

{

"type": "laptop",

"model": "Macbook Pro"

},

{

"type": "phone",

"model": "iPhone XS"

}

]

}

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

Массивы

Наше свойство devices представляет массив:

{

"firstName": "Kirupa",

"lastName": "Chinnathambi",

"special": {

"admin": true,

"userID": 203

},

"devices": [

{

"type": "laptop",

"model": "Macbook Pro"

},

{

"type": "phone",

"model": "iPhone XS"

}

]

}

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

Null

Последний тип данных самый унылый:

{

"foo": null

}

Ваши значения JSON могут быть null, что означает пустое значение.

Чтение данных JSON

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

Как бы то ни было, практически всегда ваше взаимодействие с JSON будет связано с чтением данных. Когда дело доходит до чтения данных JSON, главное помнить, что это очень похоже на чтение значений, хранящихся в типичном объекте JavaScript. Вы можете либо обратиться к необходимому значению через точку (property.propertyFoo), либо использовать для этого подход массива (property["propertyFoo"]).

Следующий пример продемонстрирует это:

let exampleJSON = {

"firstName": "Kirupa",

"lastName": "Chinnathambi",

"special": {

"admin": true,

"userID": 203

},

"devices": [

{

"type": "laptop",

"model": "Macbook Pro"

},

{

"type": "phone",

"model": "iPhone XS"

}

]

};

Чтобы считать значение, хранящееся в firstName, вы можете сделать одно из следующего:

exampleJSON.firstName;

exampleJSON["firstName"];

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

Аналогично тому, что вы видели ранее, для обращения к значению, хранящемуся в lastName, вы можете сделать следующее:

exampleJSON.lastName;

В случае простых свойств, хранящих простые значения, все достаточно просто. Есть, правда, очень и очень маленькая трудность, с которой вы можете столкнуться, и связана она с более сложными значениями, состоящими из объектов и массивов. Чтобы считать значение, содержащееся в объекте, просто продолжайте через точку обращаться к каждому свойству, пока не достигните того, которое и хранит нужное вам значение.

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

exampleJSON.special.userID;

Массивы ничем не отличаются, но вам придется переключиться на нотацию массива, как только вы достигните свойства, которое хранит значения массива. Если бы мы хотели обратиться к значению model первого устройства в массиве devices, то могли бы написать, например, следующее:

exampleJSON.devices[0].model;

Так как свойство devices относится к массиву, вы также можете производить стандартные присущие массивам операции вроде такой:

let devicesArray = exampleJSON.devices;


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

let type = devicesArray[i].type;

let model = devicesArray[i].model;


// Делает что-нибудь интересное с этими данными.

}

Напоминая пройденное в предыдущем разделе, скажу, что ваши значения JSON могут быть строками, числами, объектами, массивами, логическими типами или null. Все, что JavaScript поддерживает для заданного типа данных, встреченного вами в объекте JSON, вы можете использовать в своих интересах.

Парсинг JSON-подобных данных в действительный JSON

В нашем примере данные JSON были точно определены внутри переменной exampleJSON. Ни у кого не возникнет сомнений, что мы имеем дело именно с реальным объектом JS, представленным посредством семантики JSON.

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

Для таких случаев существует метод JSON.parse, который получает ваши «недействительные» данные JSON в виде аргумента:

function processRequest(e) {

if (xhr.readyState == 4 && xhr.status == 200) {

let response = JSON.parse(xhr.responseText);

selectInitialState(response.region);

}

}

Как видно по выделенной строке, этот метод получает любые JSON-подобные конечные данные и преобразует их в реальный объект JSON, с которым уже гораздо легче работать. Лично я при работе с внешними JSON-данными всегда использую JSON.parse просто для безопасности.

Запись данных JSON?

Текущий раздел был целиком посвящен чтению значений из данных JSON. Было бы логичным посвятить аналогичный раздел их записи. Но как выясняется, запись данных JSON вовсе не так популярна, если только вы не сохраняете их в файл или не работаете с веб-службами. Если вы выполняете какую-либо из этих задач, то очевидно, что это либо разработка на Node, либо написание кода на не JavaScript.

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

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

Раньше эта глава была бы посвящена XML. Даже сегодня XML по-прежнему широко используется как формат файлов для хранения информации и ее обмена. JSON же чрезвычайно популярен только в том мире, где браузер является королем (то есть в мире, где живем мы с вами). Вне веб-сайтов, веб-приложений и веб-сервисов, основанных на REST, работа с данными в формате JSON не приветствуется. Вам следует иметь это в виду, если вы столкнетесь с более старыми, менее веб-ориентированными ситуациями.

Если у вас появились какие-либо связанные с JSON или чем-то другим вопросы, отправляйтесь за ответами на форум https://forum.kirupa.com.

Глава 24. JS, браузер и DOM

До сих пор мы изучали JavaScript обособленно. Мы многое узнали о его базовой функциональности, но все это было мало связано с реальным положением вещей в реальном мире — мире, представленном браузером и наполненном множеством мелких HTML-тегов и CSS-стилей. Эта глава послужит введением в этот мир, а последующие главы помогут погрузиться в него уже глубже.

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

Поехали!

Что делают HTML, CSS и JavaScript

Прежде чем перейти к делу и начать обсуждать смысл жизни… гм, то есть DOM, давайте быстренько взглянем на кое-что, возможно, вам известное. Для начала скажу, что весь материал, помещаемый в HTML-документы, касается HTML, CSS и JavaScript. Мы рассматриваем эти три компонента как равных партнеров в создании того, что вы знаете как браузер (рис. 24.1).

Рис. 24.1. Типичная веб-страница состоит из HTML, CSS и JavaScript

Каждый из этих партнеров играет свою уникальную важную роль.

HTML определяет структуру

HTML определяет структуру страницы и по большей части содержит материал, который вы видите:


name="description">

Example



What This Sea Otter Did to This Little Kid Will Make You

LOL!


Nulla tristique, justo eget semper viverra,

massa arcu congue tortor, ut vehicula urna mi

in lorem. Quisque aliquam molestie dui, at tempor

turpis porttitor nec. Aenean id interdum urna.

Curabitur mi ligula, hendrerit at semper sed,

feugiat a nisi.


more


HTML — это кто-то вроде Мег Гриффин, и он весьма скучен. Если вы не знакомы с Мег Гриффин и вам лень гуглить, кто это, то рис. 24.2 приблизительно показывает, на что она похожа.

Рис. 24.2. Творческая интерпретация Мег Гриффин

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

Приукрась мой мир, CSS!

CSS — это ваш главный язык стиля, который позволяет придавать HTML-элементам столь необходимую эстетическую и схематичную привлекательность:

body {

font-family: "Arial";

background-color: #CCCFFF;

}

#container {

margin-left: 30 %;

}

#container img {

padding: 20px;

}

#container h1 {

font-size: 56px;

font-weight: 500;

}

#container p.bodyText {

font-size: 16px;

line-height: 24px;

}

submitButton {

display: inline-block;

border: 5px solid #669900;

background-color: #7BB700;

padding: 10px;

width: 150px;

font-weight: 800;

}

На протяжении долгого времени HTML и CSS предоставляли все необходимое для создания прекрасных и функциональных страниц. Имелись и структура, и макет страницы, были навигация и даже простые взаимодействия вроде наведения мыши. Жизнь была прекрасна.

Настало время JavaScript!

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

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

Чтобы заполнить пробел между тем, что предлагали HTML/CSS, и тем, что требовалось пользователям, применялись сторонние компоненты вроде Java и Flash, которые благополучно развивались на протяжении многих лет. Так продолжалось до недавних пор. Это было следствием многих технических и политических причин, но одна из таких причин была в том, что JavaScript просто был не готов в течение долгих лет. Для эффективности ему не хватало либо того, что предоставляли базовые языки, либо того, что поддерживали браузеры.

Сегодня это уже не важно. Теперь JavaScript является крайне способным языком, который позволяет добавлять нужные людям виды интерактивности. Доступ же ко всем этим возможностям обеспечивает именно DOM.

Знакомьтесь с объектной моделью документа

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

Эта структура известна как объектная модель документа. Друзья зовут ее просто DOM. На рис. 24.3 показан упрощенный вид DOM для нашего предыдущего примера.

Рис. 24.3. DOM для всего HTML, приведенного в начале главы, выглядит примерно так

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

Эти узлы могут быть элементами (что не должно вас удивлять), атрибутами, текстом, комментариями, документным материалом и чем-то еще, о чем вы даже и не задумываетесь. Эта деталь может быть для кого-то и важна, но не для нас с вами. Практически всегда единственным интересующим нас узлом будет элемент, так как именно с ними мы будем иметь дело в 99 % случаев. Хотя на скучном техническом уровне узлы по-прежнему играют определенную роль в нашем сконцентрированном на элементе представлении.

Каждый HTML-элемент, к которому вы хотите обратиться, имеет конкретный, ассоциированным с ним тип. Все эти типы расширяют основу Node (узел), которая представляет их все, как показано на рис. 24.4.

Рис. 24.4. Структурное расположение элементов, которые мы обычно видим

Ваши HTML-элементы находятся в конце цепочки, которая начинается с узла и продолжается Element и HTMLElement, заканчиваясь типом (HTMLDivElement, HTMLHeadingElement и т. д.), совпадающим с самим элементом. Свойства и методы, используемые для управления HTML-элементами, представлены в одной из частей этой цепочки.

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

Объект window

В браузере основой всей иерархии выступает объект window, который содержит много свойств и методов, помогающих в работе (рис. 24.5).

Рис. 24.5. Window — это весьма значимый компонент процесса

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

Объект document

Теперь мы переходим к объекту document, выделенному на рис. 24.6. Здесь уже будет поинтереснее, и именно на нем мы будем фокусироваться.

Рис. 24.6. Объект document — тоже по-своему важный компонент

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

Любые изменения, производимые вами в DOM через JavaScript, отражаются на том, что показывается в браузере. Это значит, что вы можете динамически добавлять элементы, удалять их, перемещать, изменять их атрибуты, устанавливать встроенные стили CSS и выполнять различные другие выкрутасы. За исключением самого основного HTML-кода, необходимого в виде тега script для запуска JavaScript-кода в HTML-документе, вы можете построить полноценную страницу, используя исключительно JavaScript, если вам того захочется. При правильном использовании это очень мощная возможность.

Другой аспект импорта объекта — document связан с событиями. Я поясню детали вкратце, но если вы хотите использовать реакцию на наведение или клик мыши, отметку в графе для галочки, обнаружение, когда была нажата клавиша, и т. д., то будете полагаться на функциональность, предоставляемую объектом document для прослушивания событий и реагирования на них.

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

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

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

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

Если у вас есть вопросы по этой или другой теме, задавайте их на форуме https://forum.kirupa.com.

Глава 25. Поиск элементов в DOM

Как мы видели в предыдущей главе, 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тиль контента

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

batman {

width: 100px;

height: 100px;

background-color: #333;

}

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

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

Без учета встроенных стилей другой подход для CSS-стилизации элементов заключается в использовании JavaScript. Мы можем использовать JS, чтобы устанавливать стиль элемента напрямую, а также чтобы добавлять или удалять значения класса элементов, которые будут определять применение тех или иных правил стиля.

В этой главе мы изучим оба этих подхода.

Поехали!

Зачем устанавливать стили с помощью JavaScript?

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

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

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

Два подхода стилизации

Из начала главы мы знаем, что есть два способа изменять стиль элемента с помощью JavaScript. Первый — это установка свойства CSS непосредственно в элементе. Второй — добавление или удаление значений класса из элемента, что может приводить к применению или игнорированию определенных правил стиля. Рассмотрим оба случая подробнее.

Установка стиля напрямую

Каждый HTML-элемент, к которому вы обращаетесь через JavaScript, имеет объект style. Этот объект позволяет определять свойство CSS и устанавливать его значение. Например, вот как будет выглядеть установка фонового цвета HTML-элемента со значением id — superman:

let myElement = document.querySelector("#superman");

myElement.style.backgroundColor = "#D93600";

Чтобы воздействовать на несколько элементов, вы можете сделать следующее:

let myElements = document.querySelectorAll(".bar");


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

myElements[i].style.opacity = 0;

}

Коротко говоря, для прямой стилизации элементов с помощью JavaScript первым шагом будет обращение к элементу. При этом будет очень удобно использовать знакомый нам метод querySelector. Вторым шагом будет нахождение интересующего вас свойства CSS и указание для него значения. Помните, что многие значения в CSS являются строками. Также помните, что многим значениям для распознания требуются единицы измерения вроде px, em или аналогичные.

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

myElement.style.transform = "translate3d(" + xPos +", " + yPos + "px, 0)";

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

myElement.style.transform = `translate3d(${xPos}px, ${yPos}px, 0)`;

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

Вынужденное изменение имен некоторых свойств CSS

JavaScript очень требователен к содержанию допустимого имени свойства. Большинство имен в CSS будут одобрены JS, поэтому вы можете использовать их в изначальном виде. Тем не менее кое-что стоит помнить.

Чтобы указать в JavaScript свойство CSS, содержащее тире, просто удалите это тире. Например, background-color станет backgroundColor, а border-radius будет borderRadius и т. д.

Кроме того, некоторые слова в JavaScript зарезервированы и не могут быть использованы как есть. Один из примеров свойств CSS, подпадающих под эту категорию, является float. В CSS это свойство макета. В JavaScript же оно обозначает нечто иное. Чтобы использовать свойство, чье имя зарезервировано, используйте для него префикс css, то есть напишите не float, а cssfloat.

Добавление и удаление классов с помощью JavaScript

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

disableMenu

{ display: none;

}

В HTML у нас есть меню, чей id — dropDown:

Теперь если мы захотим применить стиль. disableMenu к этому элементу, то нам всего лишь понадобится добавить disableMenu в качестве значения class к элементу dropDown:

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

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

• add;

• remove;

• toggle;

• contains.

Назначение этих методов угадывается из их имен, но все же давайте их изучим внимательнее.

Добавление значений классов

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

let divElement = document.querySelector("#myDiv");

divElement.classList.add("bar");

divElement.classList.add("foo");

divElement.classList.add("zorb");

divElement.classList.add("baz");


console.log(divElement.classList);

После выполнения этого кода элемент div будет иметь следующие значения класса: bar, foo, zorb, baz. API classList берет на себя добавление пробелов между значениями классов. Если же мы укажем недопустимое значение класса, API classList будет ругаться и добавлять его не станет. Если мы прикажем методу add добавить класс, уже существующий в элементе, то код по-прежнему будет выполняться, но повторяющееся значение класса добавлено не будет.

Удаление значений классов

Для удаления значения класса мы можем вызвать метод remove также через classList:

let divElement = document.querySelector("#myDiv");

divElement.classList.remove("foo");


console.log(divElement.classList);

После выполнения этого кода значение класса foo будет удалено и останутся только bar, baz и zorb. Несложно, правда?

Переключение значений класса

Для многих сценариев стилизации применяется стандартный рабочий процесс. Во-первых, мы проверяем, существует ли значение класса в элементе. Если да, мы удаляем его из элемента. Если же нет, то мы, наоборот, его добавляем. Чтобы упростить этот тривиальный шаблон переключения, API classList предоставляет метод toggle:

let divElement = document.querySelector("#myDiv");

divElement.classList.toggle("foo"); // удаляет foo

divElement.classList.toggle("foo"); // добавляет foo

divElement.classList.toggle("foo"); // удаляет foo


console.log(divElement.classList);

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

Проверка наличия значения класса

Последним рассмотрим метод contains:

let divElement = document.querySelector("#myDiv");


if (divElement.classList.contains("bar") == true) {

// делает что-нибудь

}

Этот метод проверяет, существует ли указанное значение класса в элементе. Если да, то возвращается true, если нет — false.

Углубление

Как вы видите, API classList предоставляет почти все, что нужно для удобного добавления, удаления или проверки значений классов в элементе. Ключевое слово «почти». О том немногом, что этот API все же не предлагает по умолчанию, можно почитать в моей статьеhttp://bit.ly/kClassList, рассказывающей о многих других возможностях classList.

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

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

Остались вопросы? Дружелюбные форумчане ответят вам. Обращайтесь на https://forum.kirupa.com!

Глава 28. Перемещение по DOM

Как вы уже поняли, DOM — это огромное дерево с ветвями, на которых висит множество элементов. Если выразиться более точно, элементы в DOM упорядочены иерархически (рис. 28.1), определяя то, что мы в итоге видим в браузере.

Рис. 28.1. DOM и браузер неразрывно связаны

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

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

Поехали!

Поиск пути

Прежде чем найти элементы и работать с ними, важно понять, где они находятся. Самый простой способ решить эту проблему — начать сверху и постепенно спускаться, что мы и сделаем.

Вид вершины нашей DOM представлен элементами window, document и html (рис. 28.2).

Рис. 28.2. Вид вершины этого дерева всегда одинаков

В связи с повышенной важностью этих трех элементов DOM предоставляет к ним легкий доступ посредством window, document и document.documentElement:

let windowObject = window; // хм-м….

let documentObject = document; // Это наверняка необязательно

let htmlElement = document.documentElement;

Здесь стоит отметить, что и window, и document являются глобальными свойствами. Нам не обязательно явно объявлять их подобно тому, как сделал я. Используйте их сразу.

Как только мы спускаемся ниже уровня HTML-элемента, наша DOM начинает ветвление и становится гораздо интереснее. С этого места можно использовать несколько способов навигации. Один из них мы уже видели многократно, и связан он с использованием querySelector и querySelectorAll для получения в точности тех элементов, которые нам нужны. На практике зачастую эти два метода слишком ограничены.

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

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

Элементы div и script являются братьями, так как имеют одного родителя — элемент body. Элемент script не имеет потомков, но у div, напротив, они есть. Img, h1, p и div являются потомками элемента div, при этом все потомки одного родителя между собой являются братьями. Как и в реальной жизни, положение родителя, потомка и брата зависит от того, на какой части дерева мы фокусируемся. То есть практически каждый элемент в зависимости от угла обзора может выступать в различных ролях.

Для упрощения работы с этим всем у нас есть несколько свойств, на которые мы и будем полагаться. Ими являются firstChild, lastChild, parentNode, children, previousSibling и nextSibling. Просто глядя на их названия, вы можете догадаться, какую именно роль они играют. Дьявол в деталях, поэтому рассмотрим все подробно.

Рис. 28.3. Пример нашего дерева с родителями, братьями и потомками

Работа с братьями и родителями

Из всех свойств легче всего работать с относящимися к родителям и братьям, а именно parentNode, previousSibling и nextSibling. Схема на рис. 28.4 дает представление о том, как эти свойства работают.

Рис. 28.4. Связь между братьями и родителями с позиции DOM

Схема несколько перегружена, но понять, что на ней происходит, можно. Свойство parentNode указывает на родителя элемента. Свойства previousSibling и nextSibling позволяют элементу найти его предыдущего или следующего брата — если мы будем следовать по стрелкам на рисунке, то увидим это. В нижней строке nextSibling нашего img это div. previousSibling для div — это img. Обращение к parentNode в любом из этих элементов приведет вас к родителю div во втором ряду. Здесь все достаточно понятно.

Давай заведем детей!

Менее понятно, как во все это вписываются потомки. Поэтому давайте взглянем на свойства firstChild, lastChild и children, показанные на рис. 28.5.

Рис. 28.5. Представление потомков и их потомков

Свойства firstChild и lastChild относятся к первому и последнему дочерним элементам родителя. Если у родителя есть всего один потомок, как в случае с элементом body из нашего примера, тогда и firstChild, и lastChild будут указывать на одно и то же. Если у элемента нет потомков, то эти свойства будут возвращать null.

Самое хитрое из всех этих свойств — свойство children. Когда вы обращаетесь к свойству children в родителе, то по умолчанию получаете коллекцию дочерних элементов, которые у него есть. Эта коллекция не является Array, но при этом имеет некоторые присущие массиву возможности. Как и в случае с массивом, вы можете перебирать эту коллекцию или обращаться к ее потомкам по отдельности. У этой коллекции есть свойство length, которое сообщает, с каким количеством потомков взаимодействует родитель. Если у вас уже голова пошла кругом, не переживайте. Код из следующего раздела прояснят сказанное.

Складываем все воедино

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

Проверка наличия потомка

Чтобы проверить, есть ли у элемента потомок, мы можем сделать следующее:

let bodyElement = document.querySelector("body");


if (bodyElement.firstChild) {

// Делает что-нибудь интересное

}

Эта инструкция if вернет null, если потомков не существует. Мы могли бы также использовать bodyElement.lastChild или bodyElement.children.count, если бы любили много печатать, но я предпочитаю простые варианты.

Обращение ко всем потомкам

Если нужно обратиться ко всем потомкам родителя, всегда можно прибегнуть к старому доброму циклу for:

let bodyElement = document.body;


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

let childElement = bodyElement.children[i];


document.writeln(childElement.tagName);

}

Обратите внимание, что мы используем свойства children и length так же, как делали бы это в случае с Array. Стоит отметить, что эта коллекция по сути не является Array. Практически все методы Array, которые мы можем захотеть использовать, не будут доступны для этой коллекции, возвращенной свойством children.

Прогулка по DOM

Последний фрагмент затрагивает все, что мы видели до сих пор. Он рекурсивно проходится по DOM и касается каждого HTML-элемента, который находит:

function theDOMElementWalker(node) {

if (node.nodeType == Node.ELEMENT_NODE) {


console.log(node.tagName);

node = node.firstChild;


while (node) {

theDOMElementWalker(node);

node = node.nextSibling;

}

}

}

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

let texasRanger = document.querySelector("#texas");

theDOMElementWalker(texasRanger);

В этом примере мы вызываем функцию theDOMElementWalker для элемента, на который ссылается переменная texasRanger. Если вы хотите выполнить некий код для элемента, найденного этим скриптом, замените закомментированную строку на то, что хотите сделать.

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

Нахождения пути внутри DOM — это один из тех навыков, которым должен обладать каждый JavaScript-разработчик. Этот урок предоставляет обзор доступных техник. Применение же всего этого материала на практике уже полностью ложится на вас… или хорошего друга, который поможет со всем этим разобраться. В последующих уроках мы углубимся в эту тему еще больше. Разве это не звучит захватывающе?

Появился вопрос? Обращайтесь на https://forum.kirupa.com за ответом от дружественных разработчиков, таких же, как мы с вами.

Глава 29. Создание и удаление элементов DOM

Здесь реально может заштормить. Поэтому держитесь крепче во время чтения следующих разделов:

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

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

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

Поехали!

Создание элементов

Для интерактивных сайтов и приложений динамическое создание HTML-элементов и помещение их в DOM — вполне обыденная процедура. Если вы впервые слышите о том, что такое возможно, то этот раздел точно вам понравится!

Создавать элементы можно с помощью метода createElement, который работает достаточно просто. Он вызывается через объект document, и ему передается имя HTML-тега элемента, который нужно создать. В следующем фрагменте кода создается элемент абзаца, представленный буквой p:

let myElement = document.createElement("p");

Переменная myElement содержит ссылку на только что созданный элемент.

Если мы запустим приложение, добавив эту строку, то при своем выполнении она создаст элемент p. Создание элемента — это простая часть. Усилия придется приложить, чтобы сделать его веселым и ответственным членом нашей DOM. Требуется поместить этот элемент в определенное место DOM, и пока что наш динамически созданный элемент p где-то совершенно бесцельно блуждает:

Причина в том, что DOM не знает о существовании этого элемента, и, чтобы он стал полноценной частью DOM, нужно сделать две вещи:

1. Найти элемент, который выступит в качестве его родителя.

2. Использовать appendChild и присоединить элемент к этому родителю.

Легче всего это будет понять на примере, который все объединяет. Если вы хотите проработать это самостоятельно, то создайте HTML-документ и добавьте в него следующий код HTML, CSS и JS:


Creating Elements



Am I real?


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

Отступим на шаг назад и разберем этот пример подробней. Все, что нам нужно для создания элемента и его добавления в DOM, находится между тегами script:

let newElement = document.createElement("p");

let bodyElement = document.querySelector("body");


newElement.textContent = "Or do I exist entirely in your

imagination?";


bodyElement.appendChild(newElement);

newElement хранит ссылку на наш созданный тег p. В bodyElement хранится ссылка на элемент body. В только что созданном элементе (newElement) мы устанавливаем свойство textContent, указав, что в итоге нужно отобразить.

В конце мы берем бесцельно блуждающий newElement и присоединяем его к элементу-родителю body с помощью функции appendChild.

На рис. 29.1 показано, как выглядит DOM для нашего простого примера.

Рис. 29.1. Как выглядит DOM после выполнения приведенного кода

Следует учитывать, что функция appendChild всегда добавляет элемент после всех потомков, которые могут быть у родителя. В нашем случае элемент body уже имеет в качестве потомков элементы h1 и script. Элемент p присоединяется после них как самый новый. В итоге появлятся контроль над позицией, в которой будет размещен конкретный элемент под родителем.

Если мы хотим вставить newElement сразу после тега h1, то можем сделать это, вызвав функцию insertBefore для родителя. Функция insertBefore получает два аргумента. Первый из них является элементом-вставкой, а второй — ссылкой на брата (то есть другого потомка родителя), которому этот элемент должен предшествовать. Далее приведен измененный пример, где наш newElement помещен после элемента h1 (и перед элементом script):

let newElement = document.createElement("p");

let bodyElement = document.querySelector("body");

let scriptElement = document.querySelector("script");


newElement.textContent = "I exist entirely in your imagination.";


bodyElement.insertBefore(newElement, scriptElement);

Обратите внимание, что мы вызываем insertBefore для bodyElement и указываем, что newElement должен быть вставлен перед элементом script. Наша DOM в этом случае будет выглядеть так, как показано рис. 29.2.

Рис. 29.2. Вставленный элемент находится между элементами h1 и script

Вы можете подумать, что если есть метод insertBefore, то должен быть и метод insertAfter. Но на самом деле это не так. Здесь нет встроенного способа для вставки элемента после, а не до другого элемента. Мы можем только перехитрить функцию insertBefore, сказав ей вставить элемент перед элементом, следующим за нужным. Но будет ли в этом смысл? Сначала я покажу пример и затем все объясню:

let newElement = document.createElement("p");

let bodyElement = document.querySelector("body");

let h1Element = document.querySelector("h1");


newElement.textContent = "I exist entirely in your imagination.";


bodyElement.insertBefore(newElement, h1Element.nextSibling);

Обратите внимание на выделенные строки, а затем взгляните на рис. 29.3, где видно положение до и после выполнения кода.

Рис. 29.3. Трюк для имитирования действия insertAfter

Вызов hiElement.nextSibling находит элемент script. Вставка newElement перед элементом script удовлетворяет нашу цель вставить элемент после h1. А что, если нет элемента-брата, на который можно указать? В таком случае функция insertBefore достаточно умна и просто автоматически вставляет элемент в конец.

УДОБНАЯ ФУНКЦИЯ

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

function insertAfter(target, newElement) {

target.parentNode.insertBefore(newElement,

target.nextSibling);

}

Да, я понимаю, что это окольный путь, но он работает, и весьма прилично. Вот пример этой функции в действии:

let newElement = document.createElement("p");

let bodyElement = document.querySelector("body");

let h1Element = document.querySelector("h1");


newElement.textContent = "I exist entirely in your imagination.";


function insertAfter(target, element) {

target.parentNode.insertBefore(element, target.nextSibling);

}


insertAfter(bodyElement, newElement);

Можно пойти еще дальше и расширить этой функцией HTML-элемент, чтобы предоставить ее функциональность для всех HTML-элементов. В главе 19 «Расширение встроенных объектов» рассматривается, как сделать нечто подобное. Имейте в виду, что некоторые разработчики не одобряют расширение DOM, поэтому заготовьте какие-нибудь остроумные отговорки на случай, если эти некоторые начнут вам досаждать.

Более обобщенный подход к добавлению потомков родителю основан на понимании, что элементы-родители рассматривают потомков как точки входа в массив. Чтобы обратиться к этому массиву потомков, у нас есть свойства children и childNodes. Свойство children возвращает только HTML-элементы, а childNodes возвращает более обобщенные узлы, представляющие много того, до чего нам нет дела. Да, я осознаю, что повторяюсь, и вы можете пересмотреть главу 28 «Перемещение по DOM», чтобы лучше разобраться в способах точного указания на элемент.

Удаление элементов

Мне кажется, что следующая фраза принадлежит какому-то умному человеку: «То, что имеет силу создавать, имеет силу и удалять». В предыдущем разделе мы видели, как можно использовать метод createElement для создания элементов. В текущем разделе мы рассмотрим метод removeChild, который, несмотря на пугающее имя, занимается именно удалением элементов.

Взгляните на следующий фрагмент кода, который можно создать для работы со знакомым нам примером:

let newElement = document.createElement("p");

let bodyElement = document.querySelector("body");

let h1Element = document.querySelector("h1");


newElement.textContent = "I exist entirely in your imagination.";


bodyElement.appendChild(newElement);


bodyElement.removeChild(newElement);

Элемент p, хранящийся в newElement, добавляется к элементу body с помощью метода appendChild. Это мы уже видели раньше. Чтобы удалить этот элемент, вызывается removeChild для элемента body и передается указатель на элемент, который нужно удалить. Конечно же, это элемент newElement. Как только будет выполнен метод removeChild, все станет так, как будто DOM никогда не знала о существовании newElement.

Главное, обратите внимание, что нужно вызывать removeChild из родителя потомка, которого мы хотим удалить. Этот метод не будет отыскивать элемент для удаления по всей DOM. А теперь предположим, что у нас нет прямого доступа к родителю элемента и тратить время на его поиск мы не хотим. При этом все равно с легкостью можно удалить его с помощью свойства parentNode:

let newElement = document.createElement("p");

let bodyElement = document.querySelector("body");

let h1Element = document.querySelector("h1");


newElement.textContent = "I exist entirely in your imagination.";


bodyElement.appendChild(newElement);


newElement.parentNode.removeChild(newElement);

В этом варианте мы удаляем newElement, вызывая removeChild для его родителя, указав newElement.parentNode. Выглядит замысловато, но работает.

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

let newElement = document.createElement("p");

let bodyElement = document.querySelector("body");

let h1Element = document.querySelector("h1");


newElement.textContent = "I exist entirely in your imagination.";


bodyElement.appendChild(newElement);


newElement.remove();

Я не собираюсь заканчивать тему удаления элементов на этом методе remove. Почему? Все дело в поддержке браузера. Этот подход все еще нов, поэтому более старые версии браузеров вроде Internet Explorer его не поддерживают. Если для вас принципиальна поддержка IE, то подойдут другие рассмотренные подходы.

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

Клонирование элементов

По мере продвижения эта глава становится все запутаннее, но, к счастью, мы уже дошли до последнего раздела. Оставшаяся техника управления DOM, о которой стоит знать, связана с клонированием элементов, а именно с созданием их идентичных копий:

Клонирование производится с помощью вызова функции cloneNode для нужного элемента с аргументом true или false — это определяет, хотим мы клонировать только сам элемент или еще и всех его потомков. Вот как будет выглядеть код для клонирования элемента (и добавления его в DOM):

let bodyElement = document.querySelector("body");

let item = document.querySelector("h1");


let clonedItem = item.cloneNode(false);


// добавление клонированного элемента в DOM


bodyElement.appendChild(clonedItem);

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


Cloning Elements



Am I real?

I exist entirely in your imagination.



Если вы поместите весь этот код в HTML-документ и просмотрите его в браузере, то увидите нечто напоминающее недавний пример:

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

Секрет происходящего кроется в самом коде. Давайте вернемся назад, взглянем на код внутри тега script и попытаемся разобраться в происходящем:

let bodyElement = document.querySelector("body");

let textElement = document.querySelector(".message");

На самом верху есть переменная bodyElement, которая ссылается на элемент body в нашем HTML. Также есть переменная textElement, ссылающаяся на элемент p со значением класса message. Здесь нет ничего необычного.

А дальше уже интереснее. Есть функция-таймер setInterval, вызывающая функцию sayWhat каждые 1000 миллисекунд (1 секунду):

setInterval(sayWhat, 1000);

Сам процесс клонирования происходит внутри функции sayWhat:

function sayWhat() {

let clonedText = textElement.cloneNode(true);

bodyElement.appendChild(clonedText);

}

Мы вызываем cloneNode для textElement. В результате этого создается копия textElement, которая хранится как часть переменной clonedText. Последним шагом добавляем созданный элемент в DOM, чтобы он начал отображаться. Благодаря функции setTimer весь код после sayWhat повторяется и продолжает добавлять клонированный элемент на страницу.

Вы могли заметить, что мы клонируем следующий элемент абзаца:

I exist entirely in your imagination.

В коде же мы указали следующее:

let clonedText = textElement.cloneNode(true);

Мы вызываем cloneNode с флагом true, обозначая, что хотим клонировать и всех потомков. Зачем? Ведь у нашего элемента абзаца, кажется, нет потомков. Что ж, именно здесь и проявляется различие между elements и nodes. Тег абзаца не имеет дочерних elements, но текст, обернутый тегом p, является дочерним node. Этот нюанс важно учитывать, когда вы клонируете что-либо и в итоге получаете не то, что хотели, указав, что потомков клонировать не нужно.

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

Подытожим: DOM можно не только использовать, но и всячески изменять. Мы уже мимоходом обсуждали, как все в DOM может быть изменено, но именно здесь впервые увидели глубину и ширину доступных изменений, которые можно производить с помощью таких методов, как createElement, removeElement, remove и cloneNode.

Изучив весь этот материал, вы сможете начать с абсолютно чистой страницы и с помощью всего нескольких строк JavaScript-кода заполнить ее всем необходимым:


Look what I did, ma!



Глава 30. Браузерные инструменты разработчика

Серьезные браузеры — Google Chrome, Apple Safari, Mozilla Firefox и Microsoft Edge (бывший Internet Explorer) — не только отображают страницы. Они дают разработчикам доступ к богатой и интересной функциональности, позволяющей разобраться в том, что именно происходит на отображаемой странице. Эта возможность реализована в так называемых инструментах разработчика, которые встроены в браузер и позволяют работать с HTML, CSS и JavaScript всяческими изощренными способами.

Рассмотрим здесь эти инструменты и узнаем, как с их помощью облегчить себе жизнь.

Поехали!

Я ИСПОЛЬЗУЮ GOOGLE CHROME

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

Знакомство с инструментами разработчика

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

Выглядит знакомо, так как эта часть функционала особо не менялась с момента выпуска первого браузера в 1800-х или когда его там выпустили. Если используете Chrome, нажмите Cmd-Opt-I для Mac или F12 (или Ctrl + Shift + I) для Windows.

Нажмите нужные клавиши и посмотрите, что произойдет. Тревожная музыка, сопровождаемая землетрясением и лазерными выстрелами, скорее всего, не зазвучит, но совершенно точно изменится макет страницы браузера. Он покажет нечто загадочное. Обычно это изменение происходит в нижней или правой части экрана, как показано на рис. 30.1.

Рис. 30.1. Браузер с активированными инструментами разработчика внизу экрана

Браузер разделится на две части. Одна из них отвечает за отображение веб-страниц. Мы с ней уже знакомы, и она нам очень нравится. Вторая же — новая часть — предоставляет доступ к информации о странице, отображаемой в данный момент. Ее уже может оценить только разработчик вроде нас с вами. Эта часть и называется инструментами разработчика.

Инструменты разработчика предоставляют следующие возможности:

• Просмотр DOM.

• Отладка JavaScript.

• Проверка объектов и просмотр сообщений через консоль.

• Определение производительности и обнаружение проблем с памятью.

• Просмотр сетевого трафика

…и многое другое!

В целях экономии времени (скоро начнется очередная серия «Игры престолов», и я верю, что в этом эпизоде Нед Старк вернется в виде лютоволка) я сконцентрируюсь на первых трех пунктах, так как они непосредственно относятся к тому, что вы изучаете в этой книге.

Просмотр DOM

Первый инструмент разработчика, который мы рассмотрим, позволяет просматривать и даже манипулировать содержимым DOM. Запустив Chrome, пройдите по ссылке http://bit.ly/kirupaDevTool.

НЕТ БРАУЗЕРА? БЕЗ ПРОБЛЕМ!

Если у вас нет браузера под рукой или вы просто не можете получить доступ к этой ссылке, не волнуйтесь. Я буду объяснять, что происходит на каждом этапе пути, чтобы вы не остались в стороне.

При загрузке этой страницы вы увидите цветной фон и отображенный на нем текст:

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

Первое, что мы сделаем с этим примером, — это проверим DOM и посмотрим, что здесь происходит. Убедитесь, что инструменты разработчика видимы и при этом выбрана вкладка Elements:

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

Результат команды View source (просмотр исходного кода) в нашем примере будет выглядеть примерно так:


Random Color Generator!


Random


Color


Generator


Команда View source просто выводит представление разметки, хранящейся в HTML-странице. Иначе говоря, View source предоставляет вам (устаревшую) версию разметки, существующую на сервере, но не версию DOM.

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


Random Color Generator!

Random


Color


Generator



Если присмотреться, то можно заметить едва уловимые различия в представлении некоторых элементов. Самое серьезное различие — это выделенный стиль background-color в элементе body, который есть только в представлении DOM, но не традиционном представлении исходного кода (View source). Причина в том, что есть JavaScript, который динамически устанавливает встроенный стиль в элементе body. Следующая врезка поясняет, почему так происходит.

Отличие представления DOM от View Source

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

View Source — это просто статическое представление документа в том виде, в котором он находится на сервере (или на вашем компьютере). Оно не отображает текущих изменений выполняемой страницы, которые отражены в представлении DOM. Если вы взглянете на наш JavaScript код, то увидите, что я установил динамическое получение backgroundColor элементом body:

let bodyElement = document.querySelector("body");

bodyElement.style.backgroundColor = getRandomColor();

Когда этот код запускается, он изменяет DOM, чтобы установить свойство backgroundColor в элементе body. Используя View Source, вы бы никогда не увидели этого. Вообще никогда. Вот почему представление DOM из инструментов разработчика — ваш самый лучший друг.

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

Отладка JavaScript

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

В панели инструментов разработчика перейдите на вкладку Sources:

Sources дает доступ ко всем файлам вашего документа. Вы видите сырое содержимое этих файлов, а не сгенерированную версию DOM, которая, как вы помните, ваш лучший друг.

Убедитесь, что в представлении дерева слева выбран файл randomColorGenerator.htm. Это гарантия того, что содержимое этого файла будет отображено справа и вы сможете его просмотреть. Промотайте вниз до тега script, содержащего две строки кода, которые вы видели ранее. Судя по нумерации строк, указанной в столбце слева, наши строки идут под номерами 20 и 21.

Нужно проверить, что происходит, когда код в строке 21 начинает выполнение. Чтобы это сделать, нужно сообщить браузеру о необходимости остановиться перед началом выполнения кода этой строки. Делается это с помощью установки так называемой точки останова (прерывания). Щелкните на ярлык строки 21 в столбце слева, чтобы установить точку останова.

Так вы выделите строку 21:

Теперь точка останова установлена. В следующем шаге браузер должен наткнуться на эту точку или, говоря более мягко, достиг точки прерывания. В нашем случае достаточно нажать F5, чтобы обновить страницу.

Если все сработало ожидаемым образом, то вы увидите загрузку страницы, которая внезапно остановится, выделив строку 21:

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

Находясь в этом режиме, вернитесь к строке 21 и наведите курсор на переменную bodyElement. При наведении вы увидите подсказку, содержащую различные свойства и значения, существующие в этом конкретном объекте:

Можно взаимодействовать с этой справкой, делать прокрутку по объектам и даже углубиться в сложные объекты, содержащие в себе другие объекты. Так как bodyElement по сути является JavaScript/DOM-представлением элемента body, то вы увидите множество свойств, с которыми косвенно столкнулись при рассмотрении HTMLElement несколько глав назад.

Справа от представления исходного кода видно, с каких еще ракурсов его можно изучить:

Не стану объяснять возможности всех этих категорий, но просто скажу, что с их помощью можно изучать текущее состояние всех переменных и объектов JavaScript.

Помимо этого, точка останова предоставляет очень полезную возможность проследовать по коду так же, как это делает браузер. Мы остановились на строке 21. Чтобы проследовать далее, щелкните по кнопке Step into function call справа:

Помните, что вы сейчас находитесь на этой строке:

bodyElement.style.backgroundColor = getRandomColor();

Щелкните по указанной кнопке и посмотрите, что произойдет. Вы окажетесь внутри randomColor.js, где была определена функция getRandomColor. Нажмайте на Step into function call, чтобы двигаться по коду и проходить каждую строку функции getRandomColor. Обратите внимание, что теперь вы видите, как объекты в памяти браузера обновляются по мере выполнения кода при вашем продвижении от строки к строке. Если вы устали от этого, сделайте шаг назад, щелкнув на кнопку Step out of current function (она расположена справа от предыдущей кнопки), которая выведет вас из этой функции. В нашем случае выход произойдет обратно на строку 21 в randomColorGenerator.htm.

Если вы просто хотите запустить приложение, не продолжая прогулку по коду, то щелкните на кнопке Play, расположенной несколькими пикселями левее кнопки Step into:

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

Чтобы удалить точку останова, еще раз щелкните на номер строки, для которой ее определяли. В данном случае если вы нажмете на ярлык строки 21, то точка останова будет отключена и можно выполнять приложение, не попадая в режим отладки.

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

Знакомство с консолью

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

Чтобы вызвать ее, просто перейдите на вкладку Console:

Не пугайтесь пустоты, которую видите, лучше просто насладитесь этой свободой и свежим воздухом.

Инспектирование объектов

Там, где находится ваш курсор, напечатайте window и нажмите Enter:

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

Это ни в коем случае не песочница «только для чтения». Здесь легко можно учинить всяческие беспорядки. Например, если набрать document.body.remove() и нажать Ввод, то весь ваш документ просто исчезнет. Если вы случайно удалили body, тогда просто обновите страницу, чтобы вернуться к предыдущему состоянию. Инструменты разработчика работают напрямую с представлением страницы в оперативной памяти и не производят запись в исходный код. Поэтому ваши эксперименты без ущерба для всех останутся в промежуточной реальности.

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

Журналирование сообщений

Мы уже почти закончили с темой инструментов разработчика. В финале рассмотрим возможность консоли журналировать сообщения из кода. Помните, как мы уже делали нечто подобное?

function doesThisWork() {

console.log("It works!!!");

}

Это «нечто» мы делали, когда использовали инструкцию alert для вывода значения или подтверждения выполнения кода. Что ж, теперь можем перестать это делать. Консоль предоставляет нам гораздо менее раздражающий способ вывода сообщений, без прерывания всего процесса всплывающими диалоговыми окнами. Вы можете использовать функцию console.log для вывода нужной информации в консоль:

function doesThisWork() {

console.log("It works!!!")

}

еще раз об области видимости и состоянии

Я уже несколько раз упоминал о том, что консоль позволяет проверять текущую область видимости. По сути, все то, что мы обсуждали в главе 8 «Область видимости переменных», также относится и к поведению консоли.

Предположим, вы установили точку останова на следующей выделенной строке:

let oddNumber = false;


function calculateOdd(num) {

if (num % 2 == 0) {

oddNumber = false;

} else {

oddNumber = true;

}

}

calculateOdd(3);

Когда код при выполнении достигает этой точки, значение oddNumber по-прежнему остается false. Строка, на которой произошло прерывание, еще не была выполнена, и вы можете проверить это, протестировав значение oddNumber в консоли. А теперь предположим, что вы выполняете код, достигаете точки останова и совершаете шаг к следующей строке.

В этот момент значение oddNumber уже установлено как true. Теперь консоль будет отражать уже новое значение, так как именно оно определено для переменной oddNumber согласно представлению в оперативной памяти. Главный вывод здесь в том, что представление в консоли напрямую связано с той частью кода, на которой вы в данный момент фокусируетесь. Это становится особенно заметно, когда вы шагаете по коду и области видимости сменяются часто.

При выполнении этого кода вы увидите указанные вами данные в консоли:

Далее вы увидите, что в некоторых случаях я начну использовать console.log вместо alert.

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

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

Чтобы узнать больше об инструментах разработчика (или Dev Tools, как их называют крутые ребята), обратитесь к следующим ресурсам:

• Обзор Chrome Dev Tools: http://bit.ly/kirupaChromeDevTools

• Обзор IE/Edge F12 Dev Tools: http://bit.ly/kirupaIEDevTools

• Обзор Firefox Dev Tools: http://bit.ly/kirupaFFDevTools

• Обзор Safari Web Inspector: http://bit.ly/kirupaSafariDevTools

Загрузка...