📜 ⬆️ ⬇️

Yet Another cSS selector = YASS

After the note about Peppy, I was almost glad - here it is, happiness. Finally, the CSS selector engine appeared, which does the same thing as jQuery , only faster. Much faster.

It was not long to rejoice, as soon as I looked at the code, I was a little horrified: it did not correspond to my ideas of exceptional performance. Not at all. More precisely, I was a little satisfied, but not completely. Therefore, the question immediately arose: what if faster?

Why not make a quick mini-core for CSS selectors that provide basic functionality for working with DOM (well, quite basic - just a selection of elements, for example)? And, most importantly, it should not work more slowly (ideally, even faster) than native calls.

Performance Basics


Well, they thought and did. After two sleepless days Nights managed to sketch a simple engine for selecting elements by CSS1 selectors (XPATH was dropped by an order of magnitude slower technology and realized why jsForms also work somewhat slower than the built-in browser DOM methods).
')
Secondly, such code should cache samples (DOM calls are expensive, all normal JavaScript programmers already cache them - so simplify the task and improve performance).

Thirdly, there are statistics on the use of CSS selectors in projects - which means that we have a set of elementary operations that need to be carried out the fastest (maybe even to the detriment of more general performance).

Fourthly, the library must return the elements themselves so that they can “tie” any wrappers and increase the methods. All wrappers are resource intensive. If you just need to change the HTML 200 times on the page for the given elements, then they are not needed. Enough and checks from the browser on the validity of the operations performed.

Fifthly, naturally, the library should rely on all the fastest: fast iterators , fast substitutions , anonymous functions, a minimum of nested calls, etc.

Code


As the best illustration, I’ll give the code itself (it is also available on the YASS test page ), for it is a bit (the same mini-kernel, after all, sorry for the backlight, it’s not very easy to read)
/* */
( function (){
/* , window. _
-- $ */
var _ = function () {
/* document */
var doc = document ,
/* */
selector = arguments[0],
/* */
nocache = arguments[1];
/* , */
if (_.cache[selector] && !nocache) {
return _.cache[selector];
} else {
/* querySelectorAll, Safari */
if (doc[ 'querySelectorAll' ]) {
_.nodes = doc[ 'querySelectorAll' ](selector);
} else {
switch (selector) {
/* . , body */
case 'body' :
_.nodes = doc.body;
break ;
/* head */
case 'head' :
_.nodes = doc[ 'getElementsByTagName' ]( 'head' )[0];
break ;
case 'a' :
_.nodes = doc.links ? doc.links : doc[ 'getElementsByTagName' ]( 'a' );
break ;
case 'img' :
_.nodes = doc.images ? doc.images : doc[ 'getElementsByTagName' ]( 'img' );
break ;
/* , */
default :
/* , */
var groups = selector.replace(/, +/g, "," ).split( "," ),
groups_length = groups.length - 1;
j = -1;
/* while, for -- */
while (j++ < groups_length) {
/* , -id- */
var singles = groups[j].split( ' ' ),
singles_length = singles.length - 1,
i = -1,
level = 0;
/* */
_.nodes = doc;
while (i++ < singles_length) {
/*
(-id-), :
webo.in/articles/habrahabr/40-search-not-replace
*/
singles[i].replace(/([^\.#]+)?(#([^\.#]+))?(\.([^\.#]+))?/, function (a, tag, b, id, c, klass) {
/* , id ( ) */
if (tag == '' && klass == '' && !level) {
_.nodes = doc[ /*@cc_on !@*/ 0 ? 'all' : 'getElementById' ](id);
} else {
/* , */
if (klass == '' && id == '' && !level) {
_.nodes = doc[ 'getElementsByTagName' ](tag);
/* , id */
} else {
/* , */
var newNodes = {},
/* */
nodes_length = _.nodes.length,
J = -1,
/* , */
idx = 0;
/* 1 ,
*/
if (!nodes_length) {
_.nodes = [_.nodes[0]?_.nodes[0]:_.nodes];
nodes_length = 1;
}
/* while, for-in for */
while (J++ < nodes_length) {
var node = _.nodes[J];
if (node) {
/* */
var childs = node[ 'getElementsByTagName' ](tag ? tag : '*' ),
childs_length = childs.length,
h = -1;
while (h++ < childs_length) {
var child = childs[h];
/* id */
if (child && (!id || (id && child.id == id)) && (!klass || (klass && child.className.match(klass)))) {
/* */
newNodes[idx++] = child;
}
}
}
}
/* */
_.nodes = newNodes;
_.nodes.length = idx - 1;
}
}
/* "" "" --
"" id */
level++;
});
/* --
, */
if (groups_length) {
var nodes_length = _.nodes.length,
K = -1,
idx = _.sets ? _.sets.length : 0;
_.sets = _.sets ? _.sets : {};
while (K++ < nodes_length) {
_.sets[idx++] = _.nodes[K];
}
_.sets.length = idx;
/* */
} else {
_.sets = _.nodes;
}
}
}
break ;
}
}
/* , . ,
. */
_.sets = _.sets ? _.sets : _.nodes;
/* , , */
_.cache[selector] = _.sets.length>1||_.sets.nodeName ? _.sets : _.sets[0];
/* DOM
-- , IE */
_.sets = _.nodes = null ;
/* */
return _.cache[selector];
}
};
/* */
_.nodes = null ;
/* , , */
_.sets = null ;
/* */
_.cache = {};
/* : window._ */
window._ = _;
})();


Call examples


Everything to the disgrace is simple:
_('p') - returns all paragraphs on the page
_('p a') - or all links in them
_('p a.blog') - or all links with blog class
etc. Full CSS1 gamma.

Another bike?


Maybe this case may seem trivial to someone, but for me personally, the further development of the situation is seen as follows. Either the code can be used for educational purposes, to understand the logic of the finite state machines used by the browser to recognize CSS selectors (in the embedded engine), and to model some trivial or not so situations.

Or, this code can be refined and included in the basis of high-performance libraries, which will already turn on their methods. As is easy to see, the code is remarkably extensible or even modular. All he needs is a browser :)

As a conclusion


In the near future, I plan to conduct a series of studies on the performance of various methods and approaches in JavaScript — after all, the future of (web) applications lies with it. And CSS selectors for now (and for a long time will be, before implementing querySelectorAll in IE) are one of the “whales” of building any application. And the quicker this whale turns out, the faster everything else works.

The current version of YASS (reads yeasss! :) - 0.1. Will develop. The current code size is about 6 KB with all comments, 1.5 is the minimized version and 650 bytes is an archive with it. Think about it: 650 bytes of shrink code to select any element on the page using CSS1 selectors. And choose almost no slower (the difference is -10 ... + 30% of the native methods - due to caching) than using direct methods.

Testing is coming. So far, the current implementation is faster than Peppy by one and a half to two times, which already overtakes everyone.

PS In general, as expected, yass is much faster than current libraries. Only here tests fails :)
Thanks to lusever for the test environment. Results can be found here.

Source: https://habr.com/ru/post/46117/


All Articles