Когда я вчера начинал писать эту статью я хотел написать что-то типа «селекторы для продвинутых» небольшое руководство по сложным выборкам, но как то так получилось, что я отклонился от темы в сторону обьяснения внутрених механизмов jQuery и получилось что-то средние между «селекторами для продвинутых» и «перфомансом селекторов», что тоже не плохо. Объяснять как работают селекторы я буду на простейшем примере, который лучше смотреть в FF3.1 или IE8:
<style>
.myClass{
color:red;
}
</style>
<select name="myName">
<option value="101">101</option>
<option value="102">102</option>
<option value="103">103</option>
<option value="104">104</option>
<option value="105">105</option>
<option value="106" class="myClass">106</option>
<option value="107" class="myClass">107</option>
<option value="108" disabled="disabled">108</option>
<option value="109" disabled="disabled">109</option>
<option value="110" disabled="disabled" class="myClass">110</option>
</select>Теперь напишем селектор который выберет все элементы которые нам видны, которые имеют класс myClass и которые неактивны.
jQuery().ready(function($){
// выбираем все option
var o = $("select[name=myName] option");
// фильтруем
o.filter(":visible.myClass:disabled");
});Работает, но это не оптимальный селектор, даже если он занимает минимум символов при написании, он работает медленно. Надо понимать как это все работает и какая операция быстрее. Итак попробуем разобрать: если бы все было прекрасно и браузер поддерживал функцию document.querySelectorAll, jQuery бы передал ей селектор, но у нас не та ситуация, я спецально создал такие условия чтобы querySelectorAll ничего не ускорил, ну или мы используем какойто старый браузер который не поддерживает этот метод. Итак у нас три части селесктора :visible + .myClass + :disabled , (и хотя querySelectorAll понимает второй селектор, jQuery всеравно придеться обрабатывать первый и третий) примерный код выглядел бы так, только намного сложнее.
// Этот код работает идентично тому как работает jQuery
// выбираем все option без jQuery
var o = document.querySelectorAll("select[name=myName] option"),
tmp1 = [], tmp2 = [], result = [];
function isVisible(obj){
if (obj == document)
return true;
if(!obj)
return false;
if(obj.style.display !== "none" && obj.style.visibility !== "hidden")
return isVisible(obj.parentNode);
}
// фильтруем
for (var i=0; i<o.length; i++){
if(isVisible(o[i]))
tmp1.push(o[i]);
} // 10
for (var i=0; i<tmp1.length; i++){
var cls = tmp1[i].className.split('/\s+/')
for (var c in cls)
if (cls[c] == "myClass")
tmp2.push(tmp1[i]);
} // 3
for (var i=0; i<tmp2.length; i++){
if(tmp2[i].getAttribute("disabled"))
result.push(tmp2[i]);
} // 1
alert(result.length); // 1Конечно есть механизмы оптимизации, но есть и много другого кода поэтому от него отстранимся и будем решать нашу задачу. Задача очень простая надо посчитать общее количество циклов и трудозатраты на каждом цыкле.
Итак, считаем:
Отсюда вывод, что в примере селекторы лучше всего поменять местами в противоположном порядке — :disabled.myClass:visible.
Конечно пример про :visible натянут тут они все видимы но в реальной ситуации когда выбираются все абзацы текста, все может быть по другомую.
В общем это я все клоню к тому, что для того чтобы селекторы быстро работали надо вначале писать самые простые, отсекающие как можно больше элементов, а потом сложные чтобы они фильтровали как можно меньше элементов.