15 малоизвестных свойств и методов объектов DOM

Автор оригинала: David Gilbertson
  • Перевод
При разработке современных веб-сайтов интенсивно используются возможности JavaScript по работе с DOM. Скрипты позволяют отображать и скрывать элементы, из которых строятся страницы, настраивать свойства этих элементов. У объектов DOM, с которыми взаимодействуют из программ, имеются свойства и методы. О некоторых из них, по мнению автора материала, перевод которого мы сегодня публикуем, знают практически все веб-программисты. А вот некоторые, о которых он и хочет здесь рассказать, пользуются куда меньшей известностью.



HTML-код и DOM


Для начала поговорим о разнице между HTML-кодом и DOM. Например, обычный элемент <table< — это, очевидно, HTML-код. Этот элемент можно использовать в html-файлах, у него есть набор атрибутов, который определяет внешний вид и поведение создаваемой с его помощью таблицы. Собственно говоря, сам по себе тег <table> не имеет никакого отношения к JavaScript. Связь между HTML-элементами, присутствующими в документе, и JavaScript-кодом, обеспечивает DOM (Document Object Model, объектная модель документа). DOM даёт возможность взаимодействовать с HTML-элементами из JavaScript-кода так, как будто они являются объектами.

У всех HTML-элементов есть собственные «DOM-интерфейсы», определяющие свойства (они обычно связаны с атрибутами HTML-элементов) и методы. Например, у элемента <table> есть интерфейс, который называется HTMLTableElement.

Получить ссылку на некий элемент можно, например, воспользовавшись такой конструкцией:

const searchBox = document.getElementById('search-box');

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

Пожалуй, на этом можно завершить наш «краткий курс по DOM» и перейти, собственно, к малоизвестным свойствам и методам DOM-интерфейсов HTML-элементов.

Если вы хотите читать и сразу же экспериментировать — откройте инструменты разработчика браузера. В частности, для того, чтобы получить ссылку на некий элемент страницы, можно выделить его в дереве элементов, а затем воспользоваться конструкцией $0 в консоли. Для того чтобы просмотреть элемент в виде объекта, введите в консоли команду dir($0). И, кстати, если вы наткнётесь на что-то для вас новое, попробуйте исследовать это с помощью консоли.

№1: методы таблиц


Скромный элемент <table> (который всё ещё держит первое место среди технологий, используемых в деле разработки макетов веб-страниц) обладает порядочным числом очень хороших методов, которые значительно упрощают процесс конструирования таблиц.

Вот некоторые из них.

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, то, после загрузки страницы, браузер автоматически прокрутит её к элементу с соответствующим ID? Приём это удобный, но, в том случае, если интересующий нас элемент рендерится после загрузки страницы, работать он не будет. Вот как можно самостоятельно воссоздать такую схему поведения:

document.querySelector(document.location.hash).scrollIntoView();

№3: свойство hidden


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

myElement.style.display = 'none'

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

myElement.hidden = true

№4: метод toggle()


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

myElement.classList.toggle('some-class')

Кстати, если вы когда-нибудь добавляли классы, пользуясь конструкцией if, знайте, что больше вам так делать не придётся, и об этой конструкции забудьте. Тот же механизм реализуется с помощью второго параметра метода toggle(). Если это — выражение, при вычислении которого получается true, то класс, переданный toggle(), будет добавлен к элементу.

el.classList.toggle('some-orange-class', theme === 'orange');

Вероятно, тут у вас могут появиться сомнения в адекватности этой конструкции. Ведь название метода, «toggle», которое, учитывая то, что в нём скрыта суть выполняемого им действия, можно перевести как «переключить», не содержит никаких упоминаний о том, что «переключение» подразумевает выполнение некоего условия. Однако вышеописанная конструкция существует именно в таком виде, хотя, разработчики Internet Explorer, вероятно, тоже считают её странной. В их реализации toggle() второй параметр не предусмотрен. Поэтому, хотя выше и говорилось о том, что тем, кто знает о toggle(), можно забыть о конструкции if, забывать о ней, всё же, не стоит.

№5: метод querySelector()


О существовании этого метода вы, определённо, уже знаете, но есть подозрение, что в точности 17% из вас не знает о том, что использовать его можно в применении к любому элементу.

Например, конструкция myElement.querySelector('.my-class') выберет лишь те элементы, которые имеют класс my-class и при этом являются потомками элемента myElement.

№6: метод closest()


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

myElement.closest('article').querySelector('h1');

Тут, в ходе поиска, сначала обнаруживается первый родительский элемент <article>, а потом — первый входящий в него элемент <h1>.

№7: метод getBoundingClientRect()


Метод 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) {
  // выполняем какие-то действия
}

Вот ещё один вариант, он лучше, но тоже далёк от идеала:

if (myElement.className.includes('some-class')) {
  // выполняем какие-то действия
}

А вот — самый лучший способ решить эту задачу:

if (myElement.matches('.some-class')) {
  // выполняем какие-то действия
}

№9: метод insertAdjacentElement()


Этот метод похож на appendChild(), но даёт немного больше власти над тем, куда именно будет добавлен элемент-потомок.

Так, команда parentEl.insertAdjacentElement('beforeend', newEl) аналогична команде parentEl.appendChild(newEl), но, используя метод insertAdjacentElement() можно, помимо аргумента beforeend, передавать ему аргументы beforebegin, afterbegin и afterend, указывающие на место, куда надо добавить элемент.

№10: метод contains()


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

const handleClick = e => {
  if (!modalEl.contains(e.target)) modalEl.hidden = true;
};

Здесь modalEl — ссылка на модальное окно, а e.target — это любой элемент, который щёлкнули мышью. Что интересно, когда я пользуюсь этим приёмом, то у меня никогда не получается с первого раза написать всё правильно, даже тогда, когда я вспоминаю, что постоянно тут ошибаюсь и пытаюсь заранее исправить возможные ошибки.

№11: метод getAttribute()


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

Помните, выше мы говорили о том, что свойства объектов DOM обычно связаны с атрибутами HTML-элементов?

Один из случаев, когда это не так, представлен атрибутом href, например, таким, как здесь: <a href="/animals/cat">Cat</a>.

Конструкция el.href не вернёт, как можно ожидать, /animals/cat. Происходит так из-за того, что элемент <a> реализует интерфейс HTMLHyperlinkElementUtils, у которого имеется множество вспомогательных свойств вроде protocol и hash, которые помогают выяснять подробности о ссылках.
Одним из таких вспомогательных свойств является свойство href, которое даёт полный URL, включающий в себя всё то, чего нет у относительного URL в атрибуте.

В результате, для того, чтобы получить именно то, что записано в атрибут href, нужно пользоваться конструкцией el.getAttribute('href').

№12: три метода элемента <dialog>


Сравнительно новый элемент <dialog> обладает двумя полезными, но вполне обычными методами, и одним методом, который можно назвать просто замечательным. Итак, методы show() и close() выполняют в точности то, чего от них можно ожидать, показывая и скрывая окно. Их мы и называем полезными, но обычными. А вот метод showModal() покажет элемент <dialog> поверх всего остального, выведя его по центру окна. Собственно говоря, именно такого поведения обычно и ожидают от модальных окон. При работе с такими элементами не нужно задумываться о свойстве z-index, вручную добавлять размытый фон, или прослушивать событие нажатия на клавишу Escape для того, чтобы закрыть соответствующее окно. Браузер знает, как должны работать модальные окна и позаботится о том, чтобы всё действовало как надо.

№13: метод forEach()


Иногда, когда вы получаете ссылку на список элементов, перебирать эти элементы можно с помощью метода forEach(). Циклы for() — это вчерашний день. Предположим, нам надо вывести в лог ссылки всех элементов <a> со страницы. Если сделать это так, как показано ниже, мы столкнёмся с сообщением об ошибке:

document.getElementsByTagName('a').forEach(el => {
    console.log(el.href);
});

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

document.querySelectorAll('a').forEach(el => {
    console.log(el.href);
});

Дело тут в том, что методы наподобие getElementsByTagName() возвращают объект типа HTMLCollection, а querySelectorAll — объект NodeList. Именно интерфейс объекта NodeList даёт нам доступ к методу forEach() (а также — к методам keys(), values() и entries()).

На самом деле, куда лучше было бы, если бы подобные методы просто возвращали бы обычные массивы, а не предлагали бы нам нечто, обладающее какими-то, вроде бы полезными, методами, не вполне похожее на массивы. Однако не стоит из-за этого расстраиваться, так как умные люди из 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: работа с формами


Вы, весьма вероятно, знаете о том, что у элемента <form> есть метод submit(). Однако, менее вероятно то, что вы знаете о наличии у форм метода reset(), и о том, что у них есть метод reportValidity(), который применим в тех случаях, когда используется проверка правильности заполнения элементов форм.

При работе с формами, кроме того, можно пользоваться их свойством elements, которое, через точку, позволяет обращаться к элементам формы, используя их атрибуты name. Например, конструкция myFormEl.elements.email вернёт элемент <input name="email" />, принадлежащий форме («принадлежащий» не обязательно означает «являющийся потомком»).

Тут надо отметить, что само свойство elements не возвращает список обычных элементов. Оно возвращает список элементов управления (и этот список, конечно, не является массивом).

Вот пример. Если на форме имеются три радиокнопки и все они имеют одно и то же имя (animal), то конструкция formEl.elements.animal даст ссылку на набор радиокнопок (1 элемент управления, 3 HTML-элемента). А если воспользоваться конструкцией formEl.elements.animal.value, то она выдаст значение выбранной пользователем радиокнопки.

Если над этим поразмыслить, то выглядит всё это довольно странно, поэтому разберёмся с предыдущим примером:

  • formEl — это элемент.
  • elements — это объект HTMLFormControlsCollection, напоминающий массив, но им не являющийся. Его элементы не обязательно являются HTML-элементами.
  • animal — это набор из нескольких радиокнопок, представленных в виде набора из-за того, что все они имеют один и тот же атрибут name (существует интерфейс RadioNodeList, предназначенный специально для работы с радиокнопками).
  • value используется для доступа к атрибуту value активной радиокнопки, находящейся в коллекции.

№15: метод select()


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

Итоги


В этом материале мы рассказали о малоизвестных методах и свойствах, которые можно использовать для работы с содержимым веб-страниц. Надеемся, что вы нашли здесь что-то новое для себя, а, возможно — не только новое, но ещё и полезное.

Уважаемые читатели! Пользуетесь ли вы какими-нибудь способами программного взаимодействия с содержимым веб-страниц, не обладающими широкой известностью?

RUVDS.com
926,49
RUVDS – хостинг VDS/VPS серверов
Поделиться публикацией

Комментарии 23

    +3
    Это прекрасно, в кои то веки чистый жс
      +2
      Если делать проверку на наличие класса, то в пункте №8 на самом деле самым лучшим способом было бы использование коллекции classList:
      if (myElement.classList.contains("some-class")) {...}
        +8
        Странный набор, если честно.

        №3: свойство hidden

        Стилем display можно управлять через цсс. Атрибутом hidden — нельзя. Его использование крайне сомнительно, за исключением особо извращенных случаев.

        №4: метод toggle()

        Это метод коллекции classList, и только её. Там вообще все методы полезны, выделять один только toggle() странно.

        №7: метод getBoundingClientRect()

        Нет, он не обязательно вызывает перерисовку страницы.

        №8: метод matches()

        Уже написали — «наиболее правильно» таки будет проверять через classList.

        №13: метод forEach()

        Не надо писать о том, что NodeList умеет в forEach(). Надо писать о том, что NodeList !== Array, и стоит конвертировать при любой необходимости. Потому что сегодня люди делают forEach() на NodeList, а завтра сделают .map() и обломаются.
          0
          Атрибут hidden не годится для управления видимостью: «The hidden attribute must not be used to hide content that could legitimately be shown in another presentation», отсюда.
            0
            Технически, это абсолютно то же самое, что и display: none. Во всех браузерах, которые у меня под рукой нашлись.
              0
              Я так думаю, речь идет о семантике документа.
                0

                Только вот любой CSS вида


                .my-flex { display: flex; }

                перебивает


                [hidden] { display: none; }

                И уже не получится написать


                <div class="my-flex" hidden>...</div>

                А значит это свойство бесполезно

              +2
              №8: метод matches()

              Уже написали — «наиболее правильно» таки будет проверять через classList.


              Здесь просто пример неудачный. Через classList правильно проверить наличие класса на элементе, а matches — проверка на соответствие css-селектору

              <div class="someParentClass">
                 <div id="myElem" class="someChildClass"></div>
              </div>
              

              matches «умеет», например, такое:
              myElem.matches('div.someParentClass .someChildClass') //true
              myElem.matches('span.someParentClass .someChildClass') //false
              
                +1

                Атрибутом hidden активно пользуюсь и очень советую, с css никаких проблем: [hidden] { display: none !important }
                Hidden значит спрятан, и это все что стоит добавить в css.


                И чтобы показать элемент: hidden = false vs style.display = '...block? flex? Пойду в css посмотрю'

                  0
                  Чтоб элемент показать — надо всего лишь убрать добавленный ранее display: none. А правильный display должен быть уже выставлен (в цсс ли, еще ли где-то).
                    0

                    тогда уж лучше removeAttribute('hidden'), чтобы с SVG работало

                  +14
                  Ок, а где малоизвестные свойства и методы?
                    0
                    без танцев с бубном поддерживается только хромом, сомнительная полезность.
                      0
                      Простите, я про dialog
                      –1
                      Забыли написать, что
                      var v = document.getElementById('MyId');
                      равносильно
                      var v = MyId;
                      Как же так? =)
                        0
                        Лучше про это не знать от греха подальше.
                          0
                          Ну да, было такое когда-то. А ещё был набор document.all
                          +2
                          Для меня пожалуй «открытием» было: scrollIntoView
                          А то постоянно офсеты считал :(
                            0

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

                            0
                            Циклы for() — это вчерашний день

                            Вчерашний день — это forEach. А for — это снова сегодняшний день:


                            for (el of document.querySelectorAll('a')) {
                                console.log(el.href);
                            }
                              +1
                              конструкция myElement.querySelector('.my-class') выберет лишь те элементы, которые имеют класс my-class и при этом являются потомками элемента myElement

                              Данная конструкция выберет лишь первый элемент, удовлетворяющий селектору
                                0
                                Именно, чтобы выбрать все надо делать myElement.querySelectorAll('.my-class');
                                Который уже вернёт злополучный NodeList
                                0
                                Производители браузеров как-то не спешат включать поддержку элемента dialog

                                Только полноправные пользователи могут оставлять комментарии. Войдите, пожалуйста.

                                Самое читаемое