Небольшое вступление
Давайте обсудим разницу между HTML и DOM.
Например, возьмем элемент table из HTML. Вы можете использовать его в файле с расширением .html. В нем мы указываем набор атрибутов, которые определяют внешний вид и «поведение» страницы.
И это все, здесь нет ничего общего с JavaScript.
DOM — это то, что позволяет объединить ваш код JavaScript с HTML-элементами в документе, так что вы сможете взаимодействовать ними как с объектами.
Это модель «документ-в-объект».
Любой тип элементов в HTML имеет собственный «интерфейс» DOM, который определяет свойства и методы. Например, у table есть интерфейс, который называется HTMLTableElement.
Вы можете можете получить ссылку на определенный элемент, написав что-то вроде этого:
У вас есть доступ ко всем свойствам и методам, которые доступны для конкретного типа элемента. Например, вы можете получить доступ к свойствам value, использовав searchBox.value, или установить курсор в определенную позицию, воспользовавшись searchBox.focus().
Теперь обсудим еще одну важную вещь: у большинства элементов нет интересных методов, поэтому легко пропустить что-то важное, если не погружаться в этот вопрос специально.
Но, к счастью, внимание к деталям и изучение спецификаций — моя сильная сторона. Поэтому все необходимое я сделал и о результатах пишу в этой статье.
Если вы хотите попробовать разобраться с DOM самостоятельно, стоит воспользоваться инструментами браузера для изучения некоторых элементов. Для этого нужно выбрать один из них в дереве элементов и набрать $0 в консоли. Это даст вам ссылку на элемент, который вы выбрали. Для того чтобы преобразовать его в объект, наберите dir($0).
Есть много вещей, которые вы можете делать в консоли.
Skillbox рекомендует: онлайн-курс Веб-разработчик
Напоминаем: Для всех читателей Хабра — скидка 10 000 рублей при записи на любой курс Skillbox по промокоду «Хабр».
№1. Методы таблиц
Существует достаточно много удобных методов, которые позволяют собирать таблицы подобно мебели в Ikea.
const tableEl = document.querySelector('table');
const headerRow = tableEl.createTHead().insertRow();
headerRow.insertCell().textContent = 'Make';
headerRow.insertCell().textContent = 'Model';
headerRow.insertCell().textContent = 'Color';
const newRow = tableEl.insertRow();
newRow.insertCell().textContent = 'Yes';
newRow.insertCell().textContent = 'No';
newRow.insertCell().textContent = 'Thank you';
Здесь вы видите не только document.createElement(). Например, метод .insertRow() встроит tbody, если вы организуете вызов этого элемента прямо в дереве. Это ли не классно?
№2. scrollIntoView()
Вы знаете, что если у вас есть #something в URL, то при загрузке страницы браузер проскроллит ее до элемента с этим ID?
Обычно это очень помогает, но не работает, если вы рендерите этот элемент после загрузки страницы. Для того чтобы воспользоваться возможностью, описанной выше, просто прописываете document.querySelector(document.location.hash).scrollIntoView();
№3. hidden
Ну да, это вроде как не метод, но раз есть setter (метод задания свойства), то можно считать методом.
Как бы там ни было, вы когда-либо использовали myElement.style.display = 'none' для того, чтобы спрятать элемент? Если да, то не стоит этого делать.
Куда лучше воспользоваться myElement.hidden = true.
№4. toggle()
Мы можем использовать этот метод для того, чтобы добавить или удалить класс элемента при помощи myElement.classList.toggle('some-class').
И если вы когда-либо добавляли класс с использованием if, то подумайте: может, стоит попробовать что-то еще?
Например, можно просто использовать второй параметр для метода toggle. Нужно передать его методу переключения. Если это произойдет, ваш класс будет добавлен в элемент:
el.classList.toggle('some-orange-class', theme === 'orange');
Да, я понимаю, о чем вы сейчас думаете: это не то, что означает само слово “toogle”. Те, кто стоит за Internet Explorer? с этим согласны и выражают свой протест, не используя второй параметр вообще.
Но давайте его вернем. Свободу параметрам!
№5. querySelector()
Ну хорошо, вы определенно должны это знать, но я подозреваю, что примерно 17% читателей не в курсе, как можно использовать этот метод в связке с элементами.
Пример: myElement.querySelector('.my-class') покажет соответствие элементов, у которых есть класс my-class и «потомков» myElement.
№6. closest
Это метод, доступный для всех элементов, которые просматривают дерево элементов. Это нечто вроде реверса для querySelector(). Поэтому мы можем использовать заголовок текущего раздела таким образом:
myElement.closest('article').querySelector('h1');
Начинаем с article и заканчиваем первым h1.
№7. getBoundingClientRect()
Метод возвращает объект с подробными сведениями об элементе, который мы указали.
{
x: 604.875,
y: 1312,
width: 701.625,
height: 31,
top: 1312,
right: 1306.5,
bottom: 1343,
left: 604.875
}
Но будьте осторожны: приведенный пример вызывает перерисовку. В зависимости от устройства и сложности вашей страницы это может занять несколько миллисекунд. Поэтому помните об этом, если вы вызываете его повторно, например в анимации.
Не все браузеры возвращают все эти значения.
№8. matches()
Я бы хотел проверить, принадлежит ли определенный элемент к определенному классу.
Максимальная сложность:
if (myElement.className.indexOf('some-class') > -1) {
// do something
}
Немного лучше, но все равно не то:
if (myElement.className.includes('some-class')) {
// do something
}
То, что нужно:
if (myElement.matches('.some-class')) {
// do something
}
№9. insertAdjacentElement()
Я обнаружил это сегодня! Это подобно appendChild(), но дает немного больше контроля в процессе добавления child.
parentEl.insertAdjacentElement('beforeend', newEl) делает примерно то же, что и parentEl.appendChild(newEl), но вы можете указать beforebegin, afterbegin или afterend для помещения его в то место, которое указывают эти имена.
Сколько контроля!
№10. contains()
Вы когда-нибудь хотели знать, есть ли элемент внутри другого? Я все время хочу это знать.
Например, если я обрабатываю клик мыши и хочу понять, происходил ли он внутри или снаружи (так что я могу закрыть его), я выполняю вот что:
const handleClick = e => {
if (!modalEl.contains(e.target)) modalEl.hidden = true;
};
Здесь modal El — отсылка к модальному окну, а e.target — элемент, по которому кликают.
Смешно, но я часто ошибаюсь в логике при первой же попытке использовать метод. И потом, когда я пытаюсь исправить ошибку, я снова неправ. А этот метод помогает сразу разбираться с ней.
№11. getAttribute()
Один из самых бесполезных методов элементов, но не в этом частном случае.
Вы помните, что обычно свойства относятся к атрибутам?
Иногда это не так, например, когда href — атрибут элемента, например a href="/animals/cat">Cat</a.
el.href не даст нам /animals/cat, как этого можно ожидать. Это потому, что элемент выполняет интерфейс HTMLHyperlinkElementUtils, который представляет собой связку вспомогательных свойств вроде protocol и hash, указывающих на объект ссылки.
Это одно из полезных свойств href, которое даст полный L, а не относительный URL в атрибуте.
Таким образом, вы должны использовать el.getAttribute('href'), если вам нужна литеральная строка внутри атрибута href.
№12. dialog
У относительно нового элемента dialog есть два хороших метода и один идеальный. show() и close() делают именно то, что от них ожидают. И это хорошо, я полагаю.
Но showModal() выведет dialog поверх любых других элементов, размещенных на странице. Нет нужды в z-index, или ручном добавлении неяркого фона, или отслеживании нажатия кнопки Escape. Браузер знает, как работают модальные окна, и все сделает для вас. И это отлично.
№13. forEach()
Иногда, когда вам нужна отсылка к списку элементов, вы можете использовать forEach().
Но что, если вам нужно записать в лог все URL для всех ссылок страницы? Вы можете выполнить вот это и увидеть ошибку.
document.getElementsByTagName('a').forEach(el => {
console.log(el.href);
});
Или выполнить вот что:
document.querySelectorAll('a').forEach(el => {
console.log(el.href);
});
Дело в том, что getElementsByTagName и другие методы get… возвращают HTMLCollection, но querySelectorAll возвращает NodeList.
И интерфейс NodeList дает нам метод forEach() (наряду с ключами(), значениями() и entry()).
Хорошие парни из ECMA дали нам Array.from(), который превратит все, что выглядит как массив, в сам массив.
Array.from(document.getElementsByTagName('a')).forEach(el => {
console.log(el.href);
});
Бонус! Создавая массив, вы можете использовать map(), и filter(), и reduce() или любой другой метод. Например, возвращение массива внешних ссылок:
Array.from(document.querySelectorAll('a'))
.map(el => el.origin)
.filter(origin => origin !== document.origin)
.filter(Boolean);
Мне очень нравится прописывать .filter(Boolean), поскольку без этого я в будущем вынужден был бы чесать затылок, пытаясь вспомнить, что это и как оно работает.
№14. Forms
У form, как вы, скорее всего, уже знаете, есть метод submit(). Несколько менее вероятно, что вы знаете, что формы имеют метод reset() и могут сообщить значение Validity(), если вы используете проверку на своих элементах формы.
Вы также можете использовать свойство элементов формы с точечной нотацией для ссылки на элемент по его атрибуту name. Например, myFormEl.elements.email вернет элемент input name = «email» /, который принадлежит form («принадлежит» не обязательно означает, что он его «потомок»).
А вот сейчас я соврал. Дело в том, что elements не возвращает список элементов. Он возвращает список элементов управления (и, конечно же, это не массив).
Пример: если у вас есть три переключателя, каждый с тем же именем animal, то formEl.elements.animal даст вам ссылку на этот набор переключателей (1 элемент управления, 3 элемента).
И formEl.elements.animal.value вернет значение выбранного переключателя.
Это странный синтаксис, если подумать. Разбивайте его, ребята: formEl — это элемент, элементы — это HTMLFormControlsCollection, не-совсем-массив, где каждый элемент не обязательно является элементом HTML. Animal имеет несколько переключателей, объединенных только потому, что они имеют один и тот же атрибут имени (для этого есть интерфейс RadioNodeList), а значение просматривает атрибут value любого переключателя в коллекции.
№15. select()
Метод .select() выберет весь текст в любом вводе, который вы вызываете.
Спасибо за то, что прочли, надеюсь, все это будет для вас полезным. Всегда проверяйте возможности своего браузера, чтобы потом не было мучительно больно.
Практические от Skillbox, которые помогут начинающему программисту стать востребованным специалистом:
Комментарии (17)
webviktor
17.07.2018 11:41+2Как бы там ни было, вы когда-либо использовали myElement.style.display = 'none' для того, чтобы спрятать элемент? Если да, то не стоит этого делать.
Куда лучше воспользоваться myElement.hidden = true.
С чего это вдруг лучше?vlreshet
17.07.2018 11:43+3Меньше кода, плюс понятнее. Так явно видно что «элемент.скрыт — да». А в первом случае надо ещё осознать что «так, ага, дисплей стоит в none, это css стиль, значит элемент невидим».
vndtta
17.07.2018 12:20+1на самом деле поведение неравнозначное, в одном случае элемент — пропадает, а в другом его не видно, но он есть, как с сусликом, и он может занимать место.
darkdaskin
19.07.2018 21:54Вы путаете свойство CSS
visibility: hidden
и атрибут / свойство DOMhidden
. Последний убирает элемент из разметки, как иdisplay: none
(и может быть перекрыт явным указаниемdisplay
).
Хотя есть разница в семантике:
The hidden attribute must not be used to hide content that could legitimately be shown in another presentation. For example, it is incorrect to use hidden to hide panels in a tabbed dialog, because the tabbed interface is merely a kind of overflow presentation — one could equally well just show all the form controls in one big page with a scrollbar. It is similarly incorrect to use this attribute to hide content just from one presentation — if something is marked hidden, it is hidden from all presentations, including, for instance, screen readers.
theWaR_13
17.07.2018 12:49А может не нужно ничего осознавать, а просто прочитать книжку по CSS? Ну серьезно, display же чуть ли не основное свойство, которое используется практически везде.
Не говоря уже о том, что у display и hidden совершенно разное поведение, как уже отметили сверху.vlreshet
17.07.2018 12:53А может не нужно ничего осознавать, а просто прочитать книжку по CSS? Ну серьезно, display же чуть ли не основное свойство, которое используется практически везде.
Осознать != узнать. Если не «читал книжку по CSS» то и не осознаешь что делает свойство display.theWaR_13
17.07.2018 12:59Судя по всему, статья рассчитана на разработчиков. Я подразумеваю, что любой разработчик, даже самый начинающий, знает, что есть такое свойство display и когда у элемента стоит dispaly: none; он понимает, что оно означает. Может я и не прав, конечно, не знаю.
Но в общем я хотел донести то, что мне подход «давайте использовать hidden вместо display: none, потому что оно читается легче» не кажется правильным.
peresada
17.07.2018 11:41А что, взять почти готовый перевод и незначительно его подправить это в рамках нормы?skillbox
17.07.2018 16:38Мы переводили с оригинала и, к сожалению, просто не увидели, что публикация выходила раньше)
JIenpukoH
17.07.2018 11:42+4Эм, но перевод этой статьи уже был habr.com/company/ruvds/blog/416539
Arty_Fact
18.07.2018 09:53Эм, но этот комментарий уже был:
habr.com/company/skillbox/blog/417375/#comment_18889163
vlreshet
То же самое, только 7 дней назад. 15 малоизвестных свойств и методов объектов DOM.
Zverik
О, а я гадал, почему нет плашки «перевод» — невозможно же сразу на русском таким странным языком писать. Спасибо за ссылку.
skillbox
Спасибо! :( Будем внимательнее.