Как стать автором
Обновить

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

Время на прочтение8 мин
Количество просмотров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: перенес в тематический блог, спасибо за карму :-)
Теги:
Хабы:
+22
Комментарии17

Публикации

Изменить настройки темы

Истории

Работа

Ближайшие события