Выборка DOM-элементов по имени класса || getElementsByClassName

Понедельник, 18 января 2010 года, 20:00. Напечатано в категориях DOM, JavaScript, jvl6

Привет, привет, привет друзья!

Сегодня мы углубимся в тему получения DOM-элементов посредством выборки их из документа по нужному имени класса! :) Так что пристегните ремни, полёт будет увлекательным :)

1. Самый простейший логический вариант – это применение встроенной функции getElementsByClassName:

var bull = document.getElementsByClassName( 'bull' );

1000 итераций данной функции в FireFox занимают примерно 13 миллисекунд.
Но вот не задача, этот метод не входит в спецификацию DOM level 2 :(
Но на данный момент ряд браузеров поддерживают этот метод, значит мы можем его применять! :)

2. Второй старинный метод – перебор все DOM-елементов документа и проверка на нужный класс. Эта функция выглядит примерно так:

function getElementsByClassName( className, node ){
  // Если имя класса не передано - обрываем выполнение
  if( !className )
    return [];
 var
    // node - родитель - место поиска елементов
    // Если node не передан, то им становится document
    node = node || document,
    // Берём все DOM-элементы узла
    elements = node.getElementsByTagName( '*' ),
    list = [],
    expr = new RegExp( '(^|\\b)' + className + '(\\b|$)' );
  if( elements.length == 0 )
    return elements;
  for( var i = 0, length = elements.length; i < length; i++ )
    if( expr.test( elements[ i ].className ) )
      list[ list.length ] = elements[ i ];
  return list;
};

/*
** Пример использования
*/

var bull = getElementsByClassName( 'bull' );
alert( 'Буллитов используется в документе: ' + bull.length );

Конечно эта функция требует значительного времени исполнения.
1000 итераций данной функции в FireFox занимают примерно 400 миллисекунд.

3. Третий вариант – использование XPath.
Не буду сильно углубятся в теорию, небольшой обзор XPath вы можете увидеть здесь: http://www.quizful.net/post/xpath-in-javascript
Приведу пример моей функции выборки элементов с помощью XPath:

function getElementsByClassName( className, node ){
  if( !className )
    return [];
 var
    // node - родитель - место поиска елементов
    // Если node не передан, то им становится document
    node = node || document,
    // Берём все DOM-элементы узла с помощью XPath
    elements = document.evaluate(
      // XPath селектор
      "//*[contains(concat(' ',normalize-space(@class),' '),' " + className + " ')]",
      node,
      null,
      XPathResult.ANY_TYPE,
      null
    ),
    list = [],
    element = elements.iterateNext();
  while( element ){
    list[ list.length ] = element;
    element = elements.iterateNext();
  };
  return list;
};
// Пример использования
var bull = getElementsByClassName( 'bull' );
alert( 'Буллитов используется в документе: ' + bull.length );

1000 итераций данной функции в FireFox занимают примерно 600 миллисекунд.
Это слишком долгий метод для нас и мы вынуждены от него отказаться в пользу второго самописного метода.

4. querySelectorAll – метод W3C CSS API Selectors. На данный момент приличное число браузеров поддерживают его. Метод принимает в качестве аргумента строку – CSS селектор и производит по нему выборку элементов. Очень быстрый метод! :)
Вот пример моей функции:

function getElementsByClassName( className, node ){
  return !className && []
    || ( node || document ).querySelectorAll( '.' + className );
};

// Пример использования
var bull = getElementsByClassName( 'bull' );
alert( 'Буллитов используется в документе: ' + bull.length );

1000 итераций данной функции в FireFox занимают примерно 80 миллисекунд :)

Итак! На данный момент у нас есть 3 варианта получения элементов. Расположим их по скорости выполнения:

  1. Встроенный метод getElementsByClassName
  2. Метод W3C CSS API Selectors – querySelectorAll
  3. Самописная функция getElementsByClassName

Вариант с XPath мы отбрасываем, так как использование его не выгодно!

Такс, давайте теперь составим идеальную функцию, перебирающую возможные три варианта поиска и использующую оптимальный вариант:

/*
 * jvlGetClass - JavaScript-функция для выборки DOM-узлов по имени класса
 *   Версия 0.2
 *
 * Copyright (c) 2010 Александр Кузнецов aka Regent(http://vl.vg/)
 *   Подробности: http://vl.vg/18.01.2010/get-elements-by-class-name/
 * Дата создания: 18.01.2010
 */

var getElementsByClassName = (
  function(){
    var d = window.document;
    if( typeof d.getElementsByClassName == 'function' )
      return function( className, node ){
        return ( node || d ).getElementsByClassName( className );
      };
    else if( typeof d.querySelectorAll == 'function' )
      return function( className, node ){
        return ( node || d ).querySelectorAll( '.' + className );
      };
    else
      return function( className, node ){
        if( !className )
          return [];
        var
          // node - родитель - место поиска елементов
          // Если node не передан, то им становится document
          // Берём все DOM-элементы узла
          elements = ( node || document ).getElementsByTagName( '*' ),
          list = [],
          expr = new RegExp( '(^|\\b)' + className + '(\\b|$)' );
        if( elements.length == 0 )
          return elements;
        for( var i = 0, length = elements.length; i < length; i++ )
          if( expr.test( elements[ i ].className ) )
            list[ list.length ] = elements[ i ];
        return list;
      };
  }
)();

И в качестве подарка получите компактный минимизированный вариант :)

/*
 * jvlGetClass - JavaScript-функция для выборки DOM-узлов по имени класса
 *   Версия 0.2
 *
 * Copyright (c) 2010 Александр Кузнецов aka Regent(http://vl.vg/)
 *   Подробности: http://vl.vg/18.01.2010/get-elements-by-class-name/
 * Дата создания: 18.01.2010
 */

var getElementsByClassName=(function(){var a=window.document;if(typeof a.getElementsByClassName=="function"){return function(b,c){return(c||a).getElementsByClassName(b)}}else{if(typeof a.querySelectorAll=="function"){return function(b,c){return(c||a).querySelectorAll("."+b)}}else{return function(c,e){if(!c){return[]}var e=e||document,g=e.getElementsByTagName("*"),f=[],h=new RegExp("(^|\\b)"+c+"(\\b|$)");if(g.length==0){return g}for(var b=0,d=g.length;b<d;b++){if(h.test(g[b].className)){f[f.length]=g[b]}}return f}}}})();

Спасибо! С уважением, Regent!



Комментарии(6)

Oler пишет в понедельник, 18.01.2010 в 22:37 Ответить
Ох, прикольный туториал получился
Чистяков Денис пишет в пятницу, 26.02.2010 в 14:48 Ответить
Странно, что между getElementsByClassName и querySelectorAll есть разница в браузере который поддерживает оба из методов, ведь по идее при использовании querySelectorAll с класс селектором должен быть применен как раз getElementsByClassName и разница должна быть совсем не значительна.
Regent пишет в пятницу, 26.02.2010 в 17:49 Ответить
@Чистяков Денис, тут используются совершенно разные схемы поиска в этих двух методах.

getElementsByClassName использует самый минималистический поиск по запросам.

querySelectorAll - это уже CSS движок, можно сказать. Пока селектор разберётся, пока все фильтры пройдёт и т. д.
Чистяков Денис пишет в пятницу, 26.02.2010 в 17:57 Ответить
@Regent, я понимаю, что это разные вещи, но я про то, как должен работать querySelectorAll, если он видит что селектор это просто класс, а не вложенный элемент или любой другой, разве не должен он использовать getElementsByClassName для его поиска, так же как например getElementById, если в качестве селектора -- '#id', а для всех остальных случаев соответственно применять фильтры, учитывать вложенность и т.д.

Это как бы размышление на тему )

P.S. Теги BB-кодов всегда вставляются в конце текста, не зависимо от положения фокуса и зоне выделения (((
Regent пишет в пятницу, 26.02.2010 в 18:04 Ответить
@Чистяков Денис, ага, на счёт BB кодов знаю

В том то и дело, что querySelectorAll работает по своему алгоритму, если в него передать даже просто #id
Чистяков Денис пишет в пятницу, 26.02.2010 в 18:06 Ответить
@Regent, Вот и не факт что это хорошо )
Для простых селекторов я бы использовал то что уже есть, а для остальных то что они используют.
Уведомлять меня о новых комментариях по почте
Для корректной работы отправки комментариев необходимо включить JavaScript
Либо скачать новый нормальный браузер