Переосмысление работы с DOM
- Details
- 22 Январь 2015 21:58
В веб-разработке, как в жизни, иногда мы создаем шаблоны в которых решаем общие задачи. Это необходимо, иначе можно потратить впустую много умственных сил на тривиальные проблемы, которые мы уже когда-то решили. Однако от этих шаблонов бывает трудно отказаться, даже когда, возможно, они уже не являются оптимальным решением.
Одна из наиболее распространенных задач, с которыми может встречаться фронтенд-разработчик, работа с DOM (Объектная Модель Документа). Обычно она заключается в определении расположения элемента DOM и затем управление им и/или его содержимым. Многие из нас использовали jQuery для этой задачи в течение последних семи или восьми лет, поскольку это - одна из основных задач, с которыми jQuery отлично справляется.
Для интернета семь лет это очень много. Вернемся в 2006 год, когда появился jQuery, Flash был очень популярен и интернет-приложения, разрабатывались на Flex или Silverlight, или даже Laszlo казалось, что они могли бы быть будущим веба. Кому нужен DOM, когда плагины были будущим веб-разработки.
С того момента, много изменилось. Браузер теперь поддерживает работу с DOM способами, которые не были возможны, когда jQuery только появился. Плюс, появились новые библиотеки, которые предлагают абсолютно другой подход к работе с DOM, чем тот, к которому мы привыкли. Итак, настало время спросить, нужен ли нам все еще jQuery для работы с DOM?
Как мы обычно работаем с DOM
Прежде чем мы рассмотрим доступные нативные методы или альтернативные библиотеки. Полезно сначала вспомнить то, каковы общие методы для работы с DOM предоставляет jQuery, чтобы мы знали, что мы должны будем заменить.
Селекторы
Конечно, первая вещь, в которой мы нуждаемся, это селекторы. jQuery предлагает CSS подобный синтаксис для выбора элементов DOM. Вот некоторые простые селекторы:
$("#item") // выбирает элемент с id равным "item" $(".fancybutton") // выбирает элементы с классом "fancybutton" $("li:even") // выбирает четные элементы списка $("ul li:nth-child(3)") // выбирает 3-ий элемент списка
Большинство читателей, вероятно, уже знакомы с основными селекторами в jQuery. После того как вы выбрали DOM элемент, вы можете начать поиск с этой точки - так что давайте посмотрим на типичные методы поиска в DOM.
Методы поиска в DOM
Прежде чем мы начнем рассматривать различные методы для поиска в DOM, будет полезно иметь простой DOM для работы, таким образом, мы сможем наглядно проиллюстрировать, что каждый метод будет делать.
<html> <head> <title>DOM Traversal Sample</title> <style> .cell { height: 50px; width: 50px; border: 1px solid #000000; } </style> </head> <body> <table class="board"> <tr id="row1" class="row"> <td id="cell11" class="cell"></td> <td id="cell12" class="cell"></td> <td id="cell13" class="cell"></td> </tr> <tr id="row2" class="row"> <td id="cell21" class="cell"></td> <td id="cell22" class="cell"></td> <td id="cell23" class="cell"></td> </tr> <tr id="row3" class="row"> <td id="cell31" class="cell"></td> <td id="cell32" class="cell"></td> <td id="cell33" class="cell"></td> </tr> </table> </body> </html>
Наш пример тривиален, но эта простая таблица из трех столбцов и трех строк позволяет легко показать то, что мы выбираем. Ниже приведены наиболее часто используемые методы поиска в DOM вместе с иллюстрациями, показывая, что они будут выбирать.
Примечание: На иллюстрациях, выделенный элемент - DOM узел, который выбирается с помощью селектора, а крестики показывают, узлы выбранные методом поиска.
siblings()
Метод siblings()
получает братские элементы (т.е. DOM узлы с тем же родительским элементом) выбранного узла. В случае ячейки таблицы, он вернет другие смежные ячейки.
$("#cell11").siblings();
В случае выбора строки, метод вернет другие строки.
$("#row1").siblings();
parent() и parents()
Метод parent()
возвращает непосредственный родительский узел выбранного DOM узла. Таким образом, для ячейки он вернет строку-родителя.
$("#cell23").parent();
Для строки, метод вернет таблицу-родителя.
$("#row3").parent();
Метод parents()
возвращает все родителей для выбранного узла вверх по DOM дереву. Таким образом, для ячейки он вернет строку и таблицу (строго говоря, он также вернет body и html узлы, но для иллюстрации, я их не беру во внимание).
$("#cell33").parents();
next() и nextAll()
Метод next()
возвращает братский узел, который следует непосредственно за выбранным. Например, если мы выбираем клетку, метод вернет только следующую ячейку в той же строке.
$("#cell21").next();
Метод nextAll()
будет возвращать все последующие ячейки, за выбранной. Он отличается от siblings()
тем, что он не возвращает братские узлы, которые предшествуют выбранному узлу.
$("#cell21").nextAll();
prev() and prevAll()
Методы prev()
и prevAll()
работают аналогично методам next()
и nextAll()
, но возвращают предшествующие узлы.
$("#cell23").prev();
$("#cell23"). prevAll();
closest() и find()
Некоторые методы обхода DOM требуют указать селектор. Например, метод closest()
вернет DOM элемент, который соответствует указанному селектору и является ближайшим родителем выбранному узлу.
$("#cell32").closest(".row");
В отличие от closest()
, который ищет вверх по DOM дереву, find()
выполняет поиск вниз, среди потомков. Он вернет все дочерние элементы, которые соответствуют указанному селектору (в отличие от closest()
, который возвращает только один).
$("#row2").find(".cell");
Нам еще нужен jQuery?
За годы, прошедшие с появления jQuery, в браузерах значительно улучшилась поддержка поиска по DOМ. Фактически, по мере возможности, jQuery просто использует многие из этих методов, значит, что в большинстве случаев мы заменяем одну строку JavaScript одной строкой jQuery в нашем коде, но при этом подключается вся библиотека jQuery.
Учитывая эти усовершенствования, справедливо задать вопрос, нужен ли нам еще JQuery для поиска в DOM? Давайте посмотрим, как мы можем заменить весь код, указанный выше, используя чистый JavaScript.
Селекторы
Используя document.querySelector()
и document.querySelectorAll()
, мы можем легко заменить селекторы, которые я перечислил выше. Вы даже можете описать эти функции в $
и вы не заметите отсутствия jQuery. Давайте посмотрим, как это сделать.
Вот замены для четырех селекторов, перечисленных в начале.
document.querySelector("#item") document.querySelectorAll(".fancybutton") //…pass! document.querySelectorAll("ul")[0].children[2]
Как вы можете заметить, одна строка чистого JavaScript заменяет одну строку jQuery.
ОК. я вас обманул!
...но только совсем чуть-чуть.
Большинство селекторов jQuery - это CSS селекторы. document.querySelector()
также использует CSS селекторы. Но некоторые селекторы (например :even
) специфичны для jQuery. Следующая функция основана на реализации :even
в JQuery и выполняет то же самое.
function selectEven(elems) { var i = 0, length = elems.length, matches = []; for ( ; i < length; i += 2 ) { matches.push(elems[i]); } return matches; } selectEven(document.querySelectorAll("li"));
Метод не сложный, но это не означает, что вы должны вызвать selectEven()
, вместо фильтрации селектором :even
.
Выше метод будет работать для любого элемента, но в случаях, когда все элементы являются дочерними определенного элемента (чаще всего), замените его строкой:
document.querySelectorAll("ul li:nth-child(even)");
siblings()
Как и для многих методов поиска в DOM, нет родной замены и для siblings()
, так что мы должны будем заменить его с на свой метод.
function getSiblings(elem) { var i = 0, n = elem.parentNode.firstChild, matches = []; for ( ; n; n = n.nextSibling ) if ( n.nodeType == 1 && n != elem) matches.push( n ); return matches; }
После того, как этот метод написан, мы можем легко заменить примеры с jQuery.
getSiblings(document.querySelector("#cell11")) getSiblings(document.querySelector("#row1"))
Возвращаемые элементы будут теми же.
parent() и parents()
Существует простая замена одной строкой для JQuery метода parent()
. Примеры выше могут быть заменены:
document.querySelector("#cell21").parentNode document.querySelector("#row3").parentNode
Метод вернет те же DOM элементы. Однако, для метода parents()
нет простой замены, так что нам придется ее написать.
function getParents(elem) { var n = elem.parentNode; matches = []; for ( ; n; n = n.parentNode ) { if ( n.nodeType == 9) { return matches; } else if (n.nodeType == 1) matches.push( n ); } }
Используя этот метод, мы можем заменить parents()
из JQuery на наш метод getParents()
.
getParents(document.querySelector("#cell33"))
next() и nextAll()
Есть множество потенциальных замен для этих методов. У DOM узла есть свойство nextSibling
. Это свойство используется внутри jQuery в методе next()
. Тем не менее, в некоторых браузерах, это свойство содержит пробел, так что при замене нужно учесть эту особенность.
Для простоты, я решил заменить оба метода одним, он основан на реализации используемой внутри JQuery.
function getNext(elem,all) { var n = elem.nextSibling, matches = []; for ( ; n; n = n.nextSibling ) { if ( n.nodeType == 1) { matches.push( n ); if (!all) return matches; } } return matches; }
Для замены next()
из jQuery, мы просто вызовем getNext()
и укажем DOM узел:
getNext(document.querySelector("#cell21"))
Для замены nextAll()
, мы просто передадим вторым параметром true:
getNext(document.querySelector("#cell21"),true)
Но, оказывается, что вы также можете получить следующий элемент, используя только специализированные селекторы. Вы можете выбрать следующий элемент используя селектор:
document.querySelector("#cell21+*")
Также реализуем nextAll()
с помощью менее строгого отбора.
document.querySelectorAll("#cell21~*")
Кроме того, childNode API имеет свойство nextElementSibling
, что позволит избежать случаев, когда возвращается пробел. Итак метод next()
может реализован следующим образом:
document.querySelector("#cell21").nextElementSibling
Я не делал кросс-браузерное тестирование этих методов, но, в соответствии с MDN, они отлично поддерживаются (хотя я не знаю, почему JQuery не использует их).
prev() и prevAll()
Как и следовало ожидать, замена этих методов похожа на замену next()
. Мы будем использовать previousSibling
(как сделано в JQuery), но нужно учитывать ситуацию, когда возвращается пробел.
function getPrev(elem,all) { var n = elem.previousSibling, matches = []; for ( ; n; n = n.previousSibling ) { if ( n.nodeType == 1) { matches.push( n ); if (!all) return matches; } } return matches; }
И снова, наша замена предыдущих примеров будет выглядеть очень просто. Для prev()
, мы передаем только DOM узел:
getPrev(document.querySelector("#cell23"))
Для prevAll()
, вторым аргументом передадим true:
getPrev(document.querySelector("#cell23"),true)
Как и в случае с next()
, childNode API имеет свойство, называемое previousElementSibling
, что позволит избежать ситуации, когда возвращается пробел. Так prev()
может быть переписан как:
document.querySelector("#cell23").previousElementSibling
Я также не делал кросс-браузерное тестирование этих методов, но, в соответствии с MDN, они отлично поддерживаются (хотя я опять не знаю, почему JQuery не использует их).
closest() и find()
Как вы, надеюсь, помните closest()
и find()
получают дополнительные селекторы в качестве аргумента. Для closest()
нет прямой замены, но мы можем легко ее написать, используя метод matchesSelector()
.
function getClosest(elem, selector) { var n = elem, el; for ( ; n; n = n.parentNode ) { el = n; if ( n.matchesSelector(selector)) return el; } }
Этот метод довольно прост и отлично работает. Просто передайте выбранный узел в качестве первого аргумента и селектор, в качестве второго. Теперь у нас есть замена для JQuery примера:
getClosest(document.querySelector("#cell32"),".row");
Проблема в том, что поддержка matchesSelector
не является универсальной и часто нужно указывать префикс (на самом деле, по текущей спецификации, метод называется matches
, а не matchesSelector
но он не был реализован в таком виде в большинстве браузеров). Так что, если вам нужна поддержка устаревших браузеров и нужно использовать closest()
, то это будет не просто.
К счастью заменить find()
очень просто
document.querySelector("#row2").querySelectorAll(".cell")
Вы можете объединять в цепочку querySelector()
и querySelectorAll()
, благодаря чему, мы можем легко заменить find()
из JQuery.
- Tags
- Author
- Андрей Рачков
Комментарии (2)
ну вот, это перевод, оказывается. я уж подумал, что какие то мысли около этой темы бродили в твоей голове и ты попробовал "выйти за границы обычного".
можно продолжить статью дальше так: "нужен ли нам DOM ? " - в этой шутке только доля шутки, некоторые "сумашедшие", например предлагают виртуальный DOM. а полные психи, на мой взгляд, пошли еще дальше...
читаю тут значит вечерком и наткнулся: http://code.tutsplus.com/tutorials/intro-to-shadow-dom--net-34966
а оттуда ссылки на вообще жесть http://www.w3.org/TR/2013/WD-components-intro-20130606/#decorator-section стало не до смеха , и на https://dvcs.w3.org/hg/webcomponents/raw-file/default/spec/templates/index.html
тут уже видно , что работы у них "непочатый край"
и вот они чем они думают, хочу я спросить ? представляете чего удумали ? теневое DOM придумали ! которое невидимо для JS! понимаетели, и с CSS тоже самое, о с одним то DOM было полно проблем, так они еще теневые ветки придумали. а АПИ к ним, и все это начиналось с безобидной идеи виртуального DOM от React, кому то понравилось, идею стали развивать до неузнаваемости.
с декораторами в CSS вообще - теперь CSS может не только описывать отображение, но и плодить для себя еще работу!
где то на хабре была статья о назвалась как-то "игра на CSS и HTML" или "программирование на HTML", в общем там чела осмеяли с ухмылкой : "ты серьезно? программирование на html ?! " и еще статья типа "вы сломали мой javascript" - шедеврально по моему
а теперь уж не знаю чего и думать. видимо жить станет еще смешнее и интереснее. мой главный вывод: - теперь я отношусь к любой нестандартной идее с большой осторожностью , в смысле, пытаюсь предвидеть "куда это может привести? "
Жизнь - боль
Авторизуйтесь, чтобы оставить комментарий