Главная > CSS, HTML, JavaScript, Программирование > Ускоряем селекторы в jQuery

Ускоряем селекторы в jQuery

7 Февраль 2009

Когда я вчера начинал писать эту статью я хотел написать что-то типа "селекторы для продвинутых" небольшое руководство по сложным выборкам, но как то так получилось, что я отклонился от темы в сторону объяснения внутренних механизмов jQuery и получилось что-то средние между "селекторами для продвинутых" и "перфомансом селекторов", что тоже не плохо. Объяснять как работают селекторы я буду на простейшем примере, который лучше смотреть в FireFox 3.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

Конечно есть механизмы оптимизации, но есть и много другого кода поэтому от него отстранимся и будем решать нашу задачу. Задача очень простая надо посчитать общее количество циклов и трудозатраты на каждом цикле.

Итак, считаем:

  • первый цикл обойдет все 10 элементов и трудазатраты у него возрастают пропорционально величине DOM дерева (дадим ему модификатор 3)
  • второй менее трудозатратен и его величина пропорциональна количеству классов у элемента (модификатор 2)
  • и наконец третий цикл совсем простой с минимальным количеством трудозатрат (модификатор 1).
Посчитаем общую сложность 10*3+10*2+3*1=53. Попробуем поменять местами поставив самые простые на перед - 10*1+3*2+1*3=19, получается в 2,5 раза меньше.

Отсюда вывод, что в примере селекторы лучше всего поменять местами в противоположном порядке - :disabled.myClass:visible.

Конечно пример про :visible натянут тут они все видимы но в реальной ситуации когда выбираются все абзацы текста, все может быть по другому.

В общем это я все клоню к тому, что для того чтобы селекторы быстро работали надо вначале писать самые простые, отсекающие как можно больше элементов, а потом сложные чтобы они фильтровали как можно меньше элементов.

21.02 UPD: Несколько часов назад вышла jquery-1.3.2.js . В release note сказано что отремонтированы селекторы :visible/:hidden и действительно сейчас они полностью переписаны и не зависят от величины дом дерева.


vae element = document.getElementById("id")
if(!element.offsetWidth && !elem.offsetHeight)
	// hidden
else
	// visible

А это значит про предыдущие расчеты нужно немного подкорректировать и теперь самым 'тяжелым' является селектор класса.

  1. 7 Февраль 2009 в 23:18 | #1
    вначале писать самые простые, отсекающие как можно больше элементов, а потом сложные чтобы они фильтровали как можно меньше элементов.
    Да, полезное замечание, касаемо не только jQuery или JavaScript, но и других языков и технологий!
  2. 7 Февраль 2009 в 23:28 | #2
    Весь каммент склеил... nl2br плачет :)
  3. 14 Февраль 2009 в 11:04 | #3
    поравил
  4. 18 Февраль 2009 в 15:49 | #4
    @adw0rd тут и по статье нл2бр плачет))
  5. 18 Февраль 2009 в 15:51 | #5
    сори букаф так много что глаза болят ... я проснулся тока что (
  6. 18 Февраль 2009 в 16:38 | #6
    Да, следует разбивать статью на логические абзацы... )
  7. 18 Февраль 2009 в 16:40 | #7
    там бить нечего, ваще два слова в три строчки...
  8. 18 Февраль 2009 в 17:17 | #8
    Глаз болит от такого монолитного текста, вот смотри разберем: >>> Конечно есть механизмы оптимизации, но есть и много другого кода поэтому от него отстранимся и будем решать нашу задачу. Задача очень простая надо посчитать общее количество циклов и трудозатраты на каждом цИкле. Итак, считаем: * первый цикл обойдет все 10 элементов и трудазатраты у него возрастают пропорционально величине DOM дерева (дадим ему модификатор 3) * второй менее трудозатратен и его величина пропорциональна количеству классов у элемента (модификатор 2) * и наконец третий цикл совсем простой с минимальным количеством трудозатрат (модификатор 1). Посчитаем общую сложность 10*3+10*2+3*1=53. Попробуем поменять местами поставив самые простые на перед - 10*1+3*2+1*3=19, получается в 2,5 раза меньше. Отсюда вывод, что в примере селекторы лучше всего поменять местами в противоположном порядке - :disabled.myClass:visible. Конечно пример про :visible натянут тут они все видимы но в реальной ситуации когда выбираются все абзацы текста, все может быть по другомую. В общем это я все клоню к тому, что для того чтобы селекторы быстро работали надо вначале писать самые простые, отсекающие как можно больше элементов, а потом сложные чтобы они фильтровали как можно меньше элементов.
  9. 18 Февраль 2009 в 17:19 | #9
    бляяяяя где nl2br для камментов :D
  10. 18 Февраль 2009 в 17:22 | #10
    ну вот у меня так и написано)))
  11. 18 Февраль 2009 в 17:40 | #11
    Получил две ссылки мои?
  12. 18 Февраль 2009 в 17:43 | #12
    нет они в спам улетели, но я текст поправил
  13. 18 Февраль 2009 в 17:58 | #13
    О, а когда цитируешь - камменты нормальные О_о
  14. 18 Февраль 2009 в 17:59 | #14
    это я вчера руками правил
  15. 18 Февраль 2009 в 18:08 | #15
    Ты хочешь попасть в индекс по албанским запросам?) У тебя ошибок туча в тексте :)
  16. 18 Февраль 2009 в 22:41 | #16
    Ну собственно Америки вы не открыли.. в любом руководстве по SQL сказано что в запросах где в предложении Where много OR лучше всего ставить первым предикатом то что чаще всего встречается (у вас в статье как раз наоборот - но сути дела это не меняет), т.к. все остальные условия просто не будут проверяться и запись попадёт в выборку. И ещё я не очень понял - "дадим ему модификатор 3" - почему? почему не 5 или 7? - если за этим кроются какие-то изыскания - ссылочку пожалуйста - лично мне будет очень интересно почитать. Вобщем нового немного - но за напоминание спасибо.
  17. 18 Февраль 2009 в 22:51 | #17
    Можно перепечатать на webo.in со ссылкой на первоисточник?
  18. 18 Февраль 2009 в 23:12 | #18
    @Steward Конечно я ничего не придумал (а жаль), но многие люди и этого не понимают, даже не новички, а все потому что нет нормальной документации на русском (да и на англиском). В статье я пытался объяснить механизмы почему так происходит, и думаю у меня получилось.
    По поводу модификаторов могу сказать что они были взяты просто от количества составляющих. Потому что для того чтобы там стояло более менее значащее число надо было оговорить изначальный размер DOM дерева и количество классов у элементов, и все равно бы это ничего не дало потому что на практике все совсем иначе.
  19. 18 Февраль 2009 в 23:13 | #19
    @sunnybear да, конечно, я кстати на вас подписан, интересный ресурс ;)
  20. taliban
    18 Февраль 2009 в 23:32 | #20
    
    var cls = tmp1[i].className.split('/\s+/')
    	for (var c in cls)
    		if (cls[c] == "myClass")
    
    
    if( -1 !== (" " + tmp1[i].className + " ").indexOf( " " + "myClass" + " ") )
    
  21. 19 Февраль 2009 в 10:11 | #21
    дело в том что я этот код не придумал а взял из jquery поэтому намек проигнорирован
  22. 19 Февраль 2009 в 11:20 | #22
    CTAPbIu_MABP : @Steward Конечно я ничего не придумал (а жаль), но многие люди и этого не понимают, даже не новички, а все потому что нет нормальной документации на русском (да и на англиском). В статье я пытался объяснить механизмы почему так происходит, и думаю у меня получилось. По поводу модификаторов могу сказать что они были взяты просто от количества составляющих. Потому что для того чтобы там стояло более менее значащее число надо было оговорить изначальный размер DOM дерева и количество классов у элементов, и все равно бы это ничего не дало потому что на практике все совсем иначе.
    Есть такая штука - оценка сложности алгоритмов. А вообще, чтобы решить описанную задачу оптимально, jQuery должен только один раз обойти выборку. Странно что они выполняют для этого три прохода.
  23. 19 Февраль 2009 в 13:15 | #23
    да, я тоже думал что все делается в один проход, но к сожалению... чтобы подтвердить это проведем эксперимент. Выберем все четные и нечетные option из примера, по идеи их должно быть 10
    
    alert($("option:even:odd").length); // 2
    
    но на самом деле их только два потому что сначала были выбраны нечетные (5 штук) а потом из них были выбраны четные (2 штуки)
Комментирование отключено.