IE8 has no getElementsByClassName, and jQuery pays for it

Sizzle hands a selector to querySelectorAll whenever the browser has it, and IE8 does — for CSS 2.1 selectors only. Anything from CSS3 or invented by jQuery makes IE8 throw, Sizzle catches it and falls back to its own engine, and that engine has no getElementsByClassName to work with on IE8. It walks getElementsByTagName('*') and tests the class name of every node in the document.

// CSS 2.1: querySelectorAll handles it, even on IE8
$('.product-row');

// CSS3 or jQuery-only: QSA throws, Sizzle walks every element in the page
$('.product-row:nth-child(odd)');
$('.product-row:visible');
$('#grid .product-row:not(.sold, .hidden)');

// give it a subtree, and split the part QSA can do from the part it cannot
var rows = $('#grid').find('.product-row');   // one QSA call, scoped

rows.filter(':visible').addClass('active');   // filters 40 nodes, not 4000

The fix is two habits rather than a workaround. Scope the query — $('#grid').find('.row') resolves the id natively and gives Sizzle a subtree of forty nodes instead of the document — and split a compound selector so the standards-compliant half goes through querySelectorAll and the jQuery-only pseudo-class filters the small result. Then cache it, because the same selector run inside a loop repeats the whole walk each time. :visible deserves particular suspicion: it is not a CSS selector at all, it reads offsetWidth and offsetHeight on every candidate, and on IE8 that means a layout calculation per node. All of this evaporates when IE8 does, since IE9 has getElementsByClassName and full CSS3 selector support — which is not an argument you can make to anyone still looking at the traffic figures.