Pull to refresh

Реализация интерфейса ElementTraversal

Reading time 8 min
Views 2.2K
Достаточно много браузеров (Opera 9.6, Google Chrome 2, Safari 4, Firefox 3.5) обзавелись поддержкой весьма удобного интерфейса ElementTraversal, который позволяет перемещаться по DOM-дереву, игнорируя текстовые узлы. В этих браузерах для каждого элемента стал доступен следующий набор новых getter'ов:
  • firstElementChild — первый дочерний элемент;
  • lastElementChild — последний дочерний элемент;
  • nextElementSibling — следующий соседний элемент;
  • previousElementSibling — предыдущий соседний элемент;
  • childElementCount — количество дочерних элементов.
Не вдаваясь в подробности реализации этих getter'ов и опираясь на слова из спецификации:
The ElementTraversal interface is a set of read-only attributes
будем называть их далее атрибутами.

Уточним: элементами называются узлы DOM-дерева, имеющие свойство nodeType равное 1; элементам соответствуют теги.

Использование этих атрибутов должно повысить производительность JavaScript-приложений при перемещении по DOM-дереву, т. к. отпадает необходимость проверять в цикле nodeType узлов и использовать дополнительные функции-обертки. А атрибут childElementCount позволяет узнать, есть ли вообще дочерние элементы у текущего узла, в отличие от практически бесполезного метода hasChildNodes. Конечно, проверить наличие дочерних элементов можно следующим образом:
node.getElementsByTagName("*").length
Но такой способ слишком расточителен, встроенный атрибут childElementCount теоретически должен работать гораздо быстрее.

Все это конечно хорошо и интересно, но что же делать со старыми браузерами, которые не поддерживают интерфейс ElementTraversal? Очевидно, придется написать дополнительные функции для каждого атрибута. Их реализацию вы можете посмотреть в статье «Быстрый поиск DOM-элементов», хотя, думаю, для многих не составит труда написать такие функции самому, ничего сложного в этом нет.

А теперь самое интересное, в Internet Explorer 8 появилась возможность создавать эти самые getter'ы и setter'ы. Странно только, почему ни в одном обзоре о нововведениях IE8 не прозвучало об этом мощном и полезном механизме. В JScript создать getter или setter теперь можно с помощью метода defineProperty встроенного объекта Object. Вспоминаем, что подобная возможность, посредством методов __defineGetter__ и __defineSetter__, изначально была в браузерах на основе Gecko, как оказалось уже и в Safari 3, и в Opera 9.6 внедрили этот механизм.

Итак, имея возможность создавать getter'ы, мы можем внедрить поддержку интерфейса ElementTraversal в Internet Explorer 8, Mozilla Firefox 2+ и Safari 3+, ну а Opera 9.6 и так его поддерживает.

Осталось написать код:
// Создаем новый элемент для дальнейших проверок<br>var element = document.createElement("div");<br><br>// Проверяем, что браузер не поддерживает ElementTraversal<br>if(typeof element.firstElementChild == "undefined") {<br><br>  // Создаем объект с набором методов<br>  var ElementTravrsal = {<br><br>    // Поиск первого дочернего элемента<br>    firstElementChild: function() {<br>      // Получаем первый дочерний узел<br>      var node = this.firstChild;<br>      // Находим следующий соседний узел пока не встретили элемент<br>      // или не получили значение null<br>      while(node && node.nodeType != 1) node = node.nextSibling;<br>      // Возвращаем найденный элемент или null<br>      return node;<br>    },<br><br>    // Поиск последнего дочернего элемента<br>    lastElementChild: function() {<br>      // Получаем последний дочерний узел<br>      var node = this.lastChild;<br>      // Находим предыдущий соседний узел пока не встретили элемент<br>      // или не получили значение null<br>      while(node && node.nodeType != 1) node = node.previousSibling;<br>      // Возвращаем найденный элемент или null<br>      return node;<br>    },<br><br>    // Поиск следующего соседнего элемента<br>    nextElementSibling: function() {<br>      // Объявляем переменную и инициализируем<br>      // ее ссылкой на текущий элемент<br>      var node = this;<br>      // Находим следующий соседний узел пока не встретили элемент<br>      // или не получили значение null<br>      do node = node.nextSibling<br>      while(node && node.nodeType != 1);<br>      // Возвращаем найденный элемент или null<br>      return node;<br>    },<br><br>    // Поиск предыдущего соседнего элемента<br>    previousElementSibling: function() {<br>      // Объявляем переменную и инициализируем<br>      // ее ссылкой на текущий элемент<br>      var node = this;<br>      // Находим предыдущий соседний узел пока не встретили элемент<br>      // или не получили значение null<br>      do node = node.previousSibling;<br>      while(node && node.nodeType != 1);<br>      // Возвращаем найденный элемент или null<br>      return node;<br>    },<br><br>    // Определение количества дочерних элементов<br>    // Проверяем, что браузер не поддерживает геттер children<br>    childElementCount: typeof element.children == "undefined" ? function() {<br>      // Браузер не поддерживает children,<br>      // поэтому получаем список всех дочерних узлов<br>      var list = this.childNodes,<br>      // определяем их количество<br>      i = list.length,<br>      // заводим счетчик элементов<br>      j = 0;<br>      // Проходя в цикле по всем дочерним узлам,<br>      while(i--)<br>          // если встретился элемент,<br>        if(list[i].nodeType == 1)<br>          // увеличиваем счетчик<br>          j++;<br>      // Возвращаем количество дочерних узлов или 0<br>      return j;<br>    } : function() {<br>      // Браузер поддерживает children,<br>      // поэтому получаем список всех дочерних элементов<br>      // и возвращаем их количество<br>      return this.children.length;<br>    }<br>  };<br><br>  // Создаем геттеры для IE8<br>  if(Object.defineProperty)<br>    for(var property in ElementTravrsal)<br>      if(ElementTravrsal.hasOwnProperty(property))<br>        Object.defineProperty(Element.prototype, property, {<br>          get: ElementTravrsal[property]<br>        });<br><br>  // для Firefox 2+ и Safari 3+<br>  if(Object.__defineGetter__)<br>    for(var property in ElementTravrsal)<br>      if(ElementTravrsal.hasOwnProperty(property))<br>        HTMLElement.prototype.__defineGetter__(property, ElementTravrsal[property]);<br><br>}
Включив этот код в проект, уже сейчас можно пользоваться ElementTraversal в большинстве браузеров:
  • Internet Explorer 8;
  • Mozilla Firefox 2+ и другие браузеры на основе Gecko 1.8+;
  • Opera 9.6+ (может быть и 9.5+);
  • Safari 3+ (может даже и 2? К сожалению, нет возможности проверить);
  • Google Chrome 2+.
Для разработки некоторых сервисов или административных интерфейсов этого списка вполне может быть достаточно. Ну а если нужна поддержка всех браузеров, без дополнительных функций, как в приведеной статье по ссылке выше, не обойтись. Или все же можно расширить список поддерживаемых браузеров? Если есть идеи, как это сделать в других браузерах, пишите в комментариях, пусть даже это будут нерациональные решения, интересна сама возможность реализации.

Используются атрибуты нового интерфейса точно так же, как и старые firstChild, lastChild, nextSibling и т. д. Возьмем, к примеру, такой XHTML-код:
<div id="test"><br>  TextNode<br>  <div>TextNode</div><br>  TextNode<br>  <p>TextNode</p><br>  TextNode<br></div><br>TextNode<br><p>TextNode</p>
И выполним несколько перемещений по DOM-дереву в JavaScript:
// Получим ссылку на элемент<br>// с идентифкатором "test"<br>var node = document.getElementById("test");<br><br>// Узнаем tagName следующего элемента<br>alert(node.nextElementSibling.tagName); // → "P"<br><br>// Найдем последний дочерний элемент<br>node = node.lastElementChild;<br><br>// И узнаем, есть у него дочерние элементы?<br>alert(node.childElementCount); // → "0"<br><br>// А вообще дочерние узлы?<br>alert(node.hasChildNodes()); // → "true"
Если было интересно, могу написать подробнее о применении getter'ов и setter'ов в JScript и JavaScript в следующей статье.

Архив с кодом из статьи: ElementTraversal.zip

Update: перенес в тематический блог, спасибо за карму :-)
Tags:
Hubs:
+22
Comments 17
Comments Comments 17

Articles