select vs natives vs jQuery (v8)

Revision 8 of this benchmark created on


Preparation HTML

<script src="//ajax.googleapis.com/ajax/libs/jquery/1/jquery.min.js"></script>
<div></div>
<div id="foo"></div>
<div class="bar"></div>
<script>
var selecthash = {
    "#": "getElementById",
    ".": "getElementsByClassName"
};
function select2(selector, context) {
    var c = selector.charAt(0),
        method = selecthash[c];

    if (method) {
        selector = selector.substr(1);
    } else {
        method = "getElementsByTagName"
    }

    return (context || document)[method](selector);
}

function select(selector, context) {
    var c = selector.charAt(0),
        method;
    
    if (c === "#") {
        method = "getElementById";
        selector = selector.substr(1);
    } else if (c === ".") {
        method = "getElementsByClassName";
        selector = selector.substr(1);
    } else {
        method = "getElementsByTagName";
    }
    
    return (context || document)[method](selector);
}

var By = {
  id: function _byId(id) {
    return document.getElementById(id);
  },
  tag: function _byTag(tag, context) {
    return (context || document).getElementsByTagName(tag);
  },
  "class": function _byClass(klass, context) {
    return (context || document).getElementsByClassName(klass);
  }
};

function select3(selector, context) {
    var c = selector.charAt(0),
        context = context || document;

    if (c === '#') {
        return context.getElementById(selector.substr(1));
    } else if (c === '.') {
        return context.getElementsByClassName(selector.substr(1));
    } else {
        return context.getElementsByTagName(selector);
    }
}

var byId = By.id;
var byClass = By.class;
var byTag = By.tag;
var byId2 = document.getElementById.bind(document);

</script>

Setup

/**
    
      @file qs.js
    
      Query Selector
    
      == Terminology ==
      
      Query:        Comma-separated group of selectors (a.nav, button)
                
      Compound:     Cluster of simple selectors (a#login.onsite)
      
      Chain:        Group of compounds separated by combinators.
      
      Combinator:   Separates compounds. Currently only whitespace. 
    
    */
    
    "use strict";
    
    /**
    
      qs
      
      Perform a simple query selection.
      
      @param String query
           
      @param Node root 
          optional root node, defaults to `document`. 
      
      @return Array<Node>
    
    */
    function qs (query, root){
      return (qs.cache[query] || qs.compile(query))(root || document);
    };
    
    qs.cache = {};
    
    qs.A = [];
    
    qs.a = function (obj) { 
      return qs.A.slice.call(obj, 0); 
    };
    
    qs.hasClass = function (node, v) {
      return (' ' + node.className + ' ').indexOf(' ' + v + ' ') > -1;
    };
    
    qs.check = function (node, cmp) {
      if (node == this.doc) return false;
      var cn, i = 0;
      if (cmp.id && (cmp.id != node.id)) return false;
      if (cmp.tn && (cmp.tn != node.tagName)) return false;
      if (cmp.cn) while (cn = cmp.cn[i++]) 
        if (!qs.hasClass(node, cn)) return false;
      return true;
    };
    
    qs.go = function (root, doc) { 
      return new qs.Selection(root, doc); 
    };
    
    // regexen
    
    qs.r = {
      singletons: /^(?:body|head|title)$/i,
      cn: /\.[^\s\.#]+/g,
      id: /#[^\s\.#]+/g,
      tn: /^[^\s\.#]+/g,
      lTrim: /^\s\s*/,
      rTrim: /\s\s*$/,
      comma: /\s*,\s*/,
      space: /\s+/
    };
    
    // regex madness
    
    qs.compile = function (query) {
      
      var fn, id, out = '', q = new qs.Query(query), 
          selectors = q.selectors,
          selector = selectors[0], 
          compound, lastChain, i = -1; 
      
      if (qs.cache[q]) {
        return qs.cache[query] = qs.cache[q];
      }
      
      if (selectors.length > 1) {
        return  qs.cache[q] = qs.cache[query] = new Function(
          'root', 'return qs.combine(\n  qs("' 
          + selectors.join('", root),\n  qs("') + '", root)\n);'
        );
      }
    
      out = '\nvar doc = root.ownerDocument || root;' 
          + '\nvar nextRoot;'
          + '\nvar lastAncestor;'
          + '\nvar nodes = [root];';
      
      lastChain = 0;
      while (compound = selector[++i]) {
        if (compound.id || 
            qs.r.singletons.test('' + compound.tn) || 
            (i == selector.length - 1)) {
          out += qs.compile.compoundToChain(
            compound, selector.slice(lastChain, i)
          );
          lastChain = i + 1;
        }
      }
      
      out += '\nreturn nodes || [];'
      
      return qs.cache[query] = qs.cache[q] = new Function('root', out);
    }
    
    
    qs.compile.compoundToLiteral = function (cmp) {
      var cn = cmp.cn.join('","');
      return '{' +
          ( (cmp.tn ? ',tn:"' + cmp.tn + '"' : '') 
          + (cmp.id ? ',id:"' + cmp.id + '"' : '') 
          + (cn ? ',cn:["' + cn.substring(1) + '"]' : '') 
          ).substring(1) + '}';
    };
    
    
    qs.compile.compoundToChain = function (cmp, ancestorChecks) {
    
        var out, check, hasId, 
            exit = '\nif (!nodes.length) return [];';
        
        cmp = cmp.copy();
    
        if (qs.r.singletons.test('' + cmp.tn)) { 
          out = '\nroot = (nodes = root.getElementsByTagName("' 
              + cmp.tn + '"))[0];' + exit;
          cmp.tn = false;
        }
        else if (cmp.id) { 
          out = '\nnodes = [doc.getElementById("' 
              + cmp.id.substring(1) + '")];'
          hasId = true;
          cmp.id = false;
        }
        else if (cmp.cn.length) { 
          out = '\nnodes = qs.getByClass(nodes[0], "' 
              + cmp.cn.shift().substring(1) + '");';
        }
        else if (cmp.tn) { 
          out = '\nnodes = root.getElementsByTagName("' 
              + cmp.tn + '");' + exit;
          cmp.tn = false;
        }
        
        if (cmp.id || cmp.tn || cmp.cn && cmp.cn.length) {
          out += exit + '\nnodes = qs.filter(nodes, ' 
              + qs.compile.compoundToLiteral(cmp) + ');';
        }
        
        if (ancestorChecks.length) {
          out += exit + '\nnodes = qs.checkAncestors(nodes, root,';
          while (check = ancestorChecks.shift()) {
            out += qs.compile.compoundToLiteral(check);
            if (ancestorChecks.length) out += ', ';
          }
          out += ');';
        }
        
        if (hasId) {
          out += exit + '\nroot = (nodes = [qs.checkRoot(nodes, root)])[0];';
        }
        return out;
        
    };
    
    qs.Compound = function (s) {
      this.cn = s.match(qs.r.cn) || [];
      this.id = (s.match(qs.r.id) || [])[0] || '';
      this.tn = ((s.match(qs.r.tn) || [])[0] || '').toUpperCase();
      this.cn.sort();
    };
    
    qs.Compound.prototype.copy = function () { 
      return {cn: this.cn.slice(), id:this.id, tn:this.tn};
    };
    
    qs.Compound.prototype.toString = function () { 
      if (this.normalized) return this.normalized; 
      return this.normalized = this.tn + this.id + this.cn.join('');
    };
    
    qs.Query = function (selector) {
      var selector, selectors = selector.split(qs.r.comma),
          compound, compounds, i = -1, j;
      this.original = selector;
      while (selector = selectors[++i]) {
        selector = selector.replace(qs.r.lTrim, '').replace(qs.r.rTrim, '');
        compounds = selector.split(qs.r.space);
        j = -1;
        while (compound = compounds[++j]) {
          compounds[j] = new qs.Compound(compound);
        }
        selectors[i] = compounds;
      }
      this.selectors = selectors.sort();
    }
    
    qs.Query.prototype.toString = function () { 
      if (this.normalized) return this.normalized; 
      var selector, selectors = this.selectors, i = -1, out = '';
      while (selector = selectors[++i]) {
        if (i) out += ', '; 
        out += selector.join(' ');
      }
      return this.normalized = out;
    }
    
    // generated code calls these 
    
    qs.getByClass = function (root, v) {
      return root.getElementsByClassName(v);
    };
    
    qs.filter = function (nodes, cmp) {
      var node, result = [], i = 0;
      while (node = nodes[i++]) {
        if (!qs.check(node, cmp)) continue;
        result.push(node);
      }
      return result;
    };
    
    qs.checkAncestors = function (nodes, root) {
      
      var node, result = [], ancestors = [], ancestor,
          argv, argc = arguments.length, argi = argc,
          lastAncestor, i = 0;
      
      if (argc > 2) {
      
        result = [];
      
        while (node = nodes[i++]) {
        
          ancestor = node;
          
          argv = arguments[--argi];
          
          while ((ancestor = ancestor.parentNode)) {
            if (qs.check(ancestor, argv)) {
              argv = arguments[--argi];
              if (argi < 2) {
                if (!lastAncestor) lastAncestor = ancestor;
                result.push(node); 
                break;
              }
            }
            if (ancestor == root) {
              break;
            }
          }
          
          argi = argc;
          
        }
        
      }
      
      this.lastAncestor = lastAncestor;
      
      return result;
    };
      
    qs.checkRoot = function (root, node) {
      if (!root.ownerDocument) return node;
      while ((node = node.parentNode)) {
        if (node == root) return node;
      }
      return false;
    };
        
    
    // TODO: remove me
    window.qs = qs;

Test runner

Ready to run.

Testing in
TestOps/sec
native
document.getElementsByTagName("div");
document.getElementById("foo");
document.getElementsByClassName("bar");
ready
select
select("div");
select("#foo");
select(".bar");
ready
QSA
document.querySelectorAll("div");
document.querySelectorAll("#foo");
document.querySelectorAll(".bar");
ready
By
By.id("foo");
By.class("bar");
By.tag("div");
ready
jQuery
$("div");
$("#foo");
$(".bar");
ready
select2
select2("div");
select2("#foo");
select2(".bar");
ready
select3
select3("div");
select3("#foo");
select3(".bar");
ready
local
byId("foo");
byClass("bar");
byTag("div");
ready
local2
byId2("foo");
byClass("bar");
byTag("div");
ready
qs
qs("div");
qs("#foo");
qs(".bar");
ready

Revisions

You can edit these tests or add more tests to this page by appending /edit to the URL.