Получение элементов по классу A или B в JavaScript

#javascript #documentfragment

#javascript #getelementsbyclassname

Вопрос:

Возможно ли получить элементы, имеющие один из указанных классов? Это не то же самое, что получение элементов, которые имеют все указанные классы.

Например, я хочу захватить все элементы, список классов которых содержит либо one , two , либо three .

Может быть, что-то вроде этого:

 var oneTwoThree = document.getElementsByClassName("one, two, three");
  

Я также не хочу использовать jQuery. Это единственный вариант getElementsByClassName для каждого класса и их объединения?

Ответ №1:

querySelector принимает практически любой CSS-селектор:

 var oneTwoThree = document.querySelectorAll('.one, .two, .three');
  

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

1. Опередите меня на 40 секунд;-) Обратите внимание, если вам нужна поддержка IE7 или ниже, вам понадобится какая-то библиотека полизаполнения / селектора.

2. Ого, я не знал об этой функции. Большое вам спасибо. Я узнал кое-что новое. 🙂 Кстати, мне нужен только IE10 и выше, так что это работает хорошо.

3. getElementsByClassName является IE9 , поэтому я сомневаюсь, что требуется поддержка старого браузера

4. Просто чтобы отметить для кого-то в будущем. Вот сравнение производительности между jQuery и querySelector jsperf.com/jquery-vs-queryselectorall/42

5. @Tibos Большое вам спасибо за то, что указали на это… Похоже, я сам голосую против. Я отредактировал свой комментарий.

Ответ №2:

Я также не хочу использовать jQuery. Это единственный вариант получить elementsbyclassname для каждого класса и объединить их?

Используя querySelectorAll (как указано в ответе), он будет искать и выполнять объединение для вас. IE9 {IE8 (только для селекторов CSS2}

В противном случае, getElementsByClassName это не единственный доступный вам метод, он даже недоступен в некоторых старых браузерах (IE9 , FF3 ).

Но практически любой другой метод, который вы могли бы использовать, потребовал бы объединения результатов. Итак, просто в качестве демонстрации я создал для вас несколько примеров.

Определите пробелы в соответствии с атрибутом класса HTML4.01

«Этот атрибут присваивает элементу имя класса или набор имен классов. Любому количеству элементов может быть присвоено одно и то же имя или имена класса. Несколько имен классов должны быть разделены пробелами. »

 var whiteSpaces = '[u0009u000Au000Bu000Cu000Du0020u00A0u1680u180Eu2000u2001u2002u2003‌​u2004u2005u2006u2007u2008u2009u200Au202Fu205Fu3000u2028u2029uFEFF]';
  

Определите символы пробела в соответствии с HTML5 3.2.5.7 в атрибуте класса

У каждого элемента HTML может быть указан атрибут class.

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

Классы, которые присвоил ему элемент HTML, состоят из всех классов, возвращаемых при разделении значения атрибута class на пробелы. (Дубликаты игнорируются.)

Назначение классов элементу влияет на сопоставление классов в селекторах в CSS, метод getElementsByClassName() в DOM и другие подобные функции.

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

Атрибуты className и classList IDL, определенные в спецификации DOM, отражают атрибут содержимого класса. [DOM]»

 var whiteSpaces = '[ nrtf]';
  

Определение начальных и конечных символов

 var starts = '(^|'   whiteSpaces   ')',
    ends = '('   whiteSpaces  '|$)';
  

Кроссбраузерный (DOM walker)

 function walkTheDOM(node, func) {
    func(node);
    node = node.firstChild;
    while (node) {
        walkTheDOM(node, func);
        node = node.nextSibling;
    }
}

function getElementsByClassName1(node, className) {
    var regex = new RegExp(starts   className   ends),
        results = [];

    walkTheDOM(node, function (currentNode) {
        if (regex.test(currentNode.className)) {
            results.push(currentNode);
        }
    });

    return results;
}
  

Кроссбраузерный (getElementsByClassName)

 function getElementsByClassName2(node, className) {
    var array = [],
        regex = new RegExp(starts   className   ends),
        elements = node.getElementsByTagName("*"),
        length = elements.length,
        i = 0,
        element;

    while (i < length) {
        element = elements[i];
        if (regex.test(element.className)) {
            array.push(element);
        }

        i  = 1;
    }

    return array;
}
  

Современный браузер (IE9 )

 function getElementsByClassName3(node, className) {
    var results = [],
        treeWalker = document.createTreeWalker(
        node,
        NodeFilter.SHOW_ELEMENT, {
            acceptNode: function (thisNode) {
                var accept = NodeFilter.FILTER_SKIP;

                if (thisNode.classList.contains(className)) {
                    accept = NodeFilter.FILTER_ACCEPT;
                }

                return accept;
            }
        }, false);

    while (treeWalker.nextNode()) {
        results.push(treeWalker.currentNode);
    }

    return results;
}
  

Современный браузер (IE9 )

 function getElementsByClassName4(node, className) {
    return Array.prototype.slice.call(document.getElementsByClassName(className));
}
  

Кроссбраузерный массив.indexOf

 function indexOf(array, searchElement, fromIndex) {
    var length = array.length,
        val = -1,
        index;

    if (length !== 0) {
        if (arguments.length > 2) {
            fromIndex = fromIndex >> 0;
        } else {
            fromIndex = 0;
        }

        if (fromIndex < length) {
            if (fromIndex < 0) {
                fromIndex = length - Math.abs(fromIndex);
            }

            if (fromIndex < 0) {
                fromIndex = 0;
            }

            for (index = fromIndex; index < length; index  = 1) {
                if (index in array amp;amp; searchElement === array[index]) {
                    val = index;
                    break;
                }
            }
        }
    }

    return val;
}
  

Кроссбраузерный массив.фильтр

 function filter(array, fn, thisArg) {
    var length = array.length,
        arr = [],
        index,
        element;

    for (index = 0; index < length; index  = 1) {
        if (index in array) {
            element = array[index];
            if (fn.call(thisArg, element, index, array)) {
                arr.push(element);
            }
        }
    }

    return arr;
};
  

Объединение результатов и поддержание порядка

 function getElements(node, classes, func) {
    if (typeof classes === 'string') {
        classes = classes.split(/s*,s*/);
    }

    var length = classes.length,
        results = [],
        index,
        name;

    for (index = 0; index < length; index  = 1) {
        name = classes[index];
        if (name.charAt(0) === '.') {
            name = name.slice(1);
        }

        results = results.concat(func(node, name));
    }

    return filter(results.reverse(), function (element, index, arr) {
        return index <= indexOf(arr, element);
    }).reverse();
}
  

Тест

 <div class='A'></div>
<div class='B'></div>
<div class='A B'></div>
<div class='C'></div>
  

 console.log(getElements(document, '.A, .B', getElementsByClassName1));
console.log(getElements(document, '.A, .B', getElementsByClassName2));
console.log(getElements(document, '.A, .B', getElementsByClassName3));
console.log(getElements(document, '.A, .B', getElementsByClassName4));
console.log(document.querySelectorAll('.A, .B'));
  

В jsFiddle

И, наконец, сравнение производительности для вас, на jsPerf

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

1. Просто для дальнейшего использования, вместо одного пробела в регулярном выражении, которое вы должны использовать [ nrtf] из-за того, как W3C определяет символы пробела . Или вы можете просто использовать s , чтобы упростить задачу.

2. Извините, я был недостаточно ясен. Имена классов в элементе могут быть разделены «пробелами», как определено в W3C. Таким образом, вместо пробела между ‘A’ и ‘B’ в вашем тестовом div у вас может быть разрыв строки, и это все равно будет допустимый HTML. Это означает, что ваше регулярное выражение, использующее только символ пробела, завершится ошибкой в некоторых ситуациях, когда в имени класса элемента используются другие пробелы.

3. Проверьте изменения, которые я внес в ваш jsFiddle .

4. Да, я уже прошел через все это, создавая hasClass() функцию. И да, технически все это символы пробела, но вам нужно учитывать только те, которые указаны в спецификации HTML5: w3.org/TR/html5/single-page.html#space-character (как я уже говорил ранее).

5. Хорошо, я вижу, что я просмотрел спецификацию HMT4.01 , в которой указаны все, а спецификация HTML5 сокращает этот список только до тех, которые вы упомянули. Или так кажется? Но обновили обе возможности.