jQuery vs dojo vs mootools vs jQuery 1.7.2

JavaScript performance comparison

Revision 135 of this test case created

Info

Newest Dojo and Jquery and Mootools

Preparation code

<script src="http://yui.yahooapis.com/3.5.1/build/yui/yui-min.js">
</script>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.8.2/jquery.min.js">
</script>
<script src="//ajax.googleapis.com/ajax/libs/mootools/1.4.5/mootools-yui-compressed.js">
</script>
<script src="//ajax.googleapis.com/ajax/libs/dojo/1.8.0/dojo/dojo.js">
</script>
<div class="section">
  <div class="age">
  </div>
  <div class="age2">
  </div>
</div>
<script>
Benchmark.prototype.setup = function() {
    /**
   
        A simple, fast query selector.
   
        @fileOverview
       
    */

    /**
     
    Perform a simple query selection.
   
    @param {String} query
       
    @param {Node} root
        Optional root node, defaults to qs.global.document.
   
    @returns {Array|NodeList}
        DOM nodes matching the query.
   
    @namespace The root qs namespace.
   
    */

    function qs(query, root) {
      var doc = root ? root.ownerDocument || root :
          (root = qs.global.document);
      return qs.run(qs.cache[query] || qs.compile(query), {
        root: root,
        doc: doc
      }).nodes;
    }
    /**
   
    A reference to the global object.
    @type Object
   
    */

    qs.global = (function () {
      return this || [eval][0]('this');
    }());
    /**
   
    Holds command arrays, keyed by query string.
    @type Object
   
    */

    qs.cache = {};
    /**
   
    Various regexen.
    @type Object
   
    @private
       
    */

    qs.rx = {
      singletons: /^(?:body|head|title)$/i,
      className: /\.[^\s\.#]+/g,
      id: /#[^\s\.#]+/g,
      tagName: /^[^\s\.#]+/g,
      lTrim: /^\s\s*/,
      rTrim: /\s\s*$/,
      comma: /\s*,\s*/,
      space: /\s+/
    };
    /**
   
    Check whether a DOM node has a css class.
   
    @param {Node} node
       
    @param {String} className
       
    @returns {Boolean} true if success, else false.
   
    */

    qs.hasClass = function (node, className) {
      return (' ' + node.className + ' ')
          .indexOf(' ' + className + ' ') > -1;
    };
    /**
   
    @param {String} text
    @returns {String}
   
    */

    qs.trim = function (text) {
      return text.replace(qs.rx.lTrim, '').replace(qs.rx.rTrim, '');
    };
    /**
   
    Check a DOM node against a qs.Compound object.
   
    @param {Node} node
        The DOM node to check.
       
    @param {qs.Compound} compound
        An object constructed by qs.Compound, or an equivalent object.
       
    @returns {Boolean} true if success, else false.
   
    */

    qs.check = function (node, compound) {
      var className, i = -1;
      if ((compound.tagName && (compound.tagName !== node.tagName)) ||
          (compound.id && (compound.id !== node.id)) ||
          (!compound.className)) {
        return false;
      }
      while ((className = compound.className[++i])) {
        if (!qs.hasClass(node, className)) {
          return false;
        }
      }
      return true;
    };
    /**
   
    Create an array of commands, store it in the cache, and return it.
   
    @param {String} queryString
       
    @returns {Array}
        qs.Command objects to run for this queryString.
   
    */

    qs.compile = function (queryString) {
      var result = [], query = new qs.Query(queryString),
          selectors = query.compounds,
          selector = selectors[0],
          compound, prevChain, i = -1,
          isLast, isSingleton;
     
      /*  If the normalized query is already cached, create a new
          reference to the command array in the cache using this
          version of the queryString as the key.
     
      */

      if (qs.cache[query]) {
        return (qs.cache[queryString] = qs.cache[query]);
      }
     
      // FIXME: handle groups of selectors (recursive qs call)
      // if (selectors.length > 1) { }
     
      prevChain = 0;
      while ((compound = selector[++i])) {
        isLast = i === selector.length - 1;
        isSingleton = qs.rx.singletons.test(compound.tagName);
        if (compound.id || isSingleton || isLast) {
          result = result.concat(qs.compoundToChain(
            compound, selector.slice(prevChain, i)
          ));
          prevChain = i + 1;
        }
      }
     
      return (qs.cache[queryString] = qs.cache[query] = result);
    };
    /**
   
    Called by qs.compile. Creates an array of commands from a
    qs.Compound object.
   
    @param {qs.Compound} compound
   
    @param {Array} ancestorChecks
   
    @returns {Array}
        Array of command objects.
   
    */

    qs.compoundToChain = function (compound, ancestorChecks) {
   
      var result = [], hasId, hasAncestorChecks, className;
     
      compound = compound.copy();
   
      if (qs.rx.singletons.test(compound.tagName)) {
        result.push({
          fn: qs.cmd.getByTag,
          args: [compound.tagName, true]
        });
        compound.tagName = false;
      }
      else if (compound.id) {
        result.push({
          fn: qs.cmd.getById,
          args: [compound.id]
        });
        hasId = true;
        compound.id = false;
      }
      else if (compound.className[0]) {
        className = compound.className.shift();
        result.push({
          fn: qs.cmd.getByClass,
          args: [className]
        });
      }
      else if (compound.tagName) {
        result.push({
          fn: qs.cmd.getByTag,
          args: [compound.tagName]
        });
        compound.tagName = false;
      }
     
      if (compound.id || compound.tagName ||
          (compound.className && compound.className[0])) {
        result.push({
          fn: qs.cmd.filter,
          args: [compound]
        });
      }
      if (ancestorChecks.length) {
        result.push({
          fn: qs.cmd.checkAncestors,
          args: ancestorChecks
        });
        hasAncestorChecks = true;
      }
      if (hasId) {
        result.push({
          fn: qs.cmd.checkIdRoot
        });
      }
      return result;
       
    };
    /**
   
    Run a set of commands in a given context.
   
    @param {Array} commands
        List of commands to run.
       
    @param {Object} context
        Shared object referenced by `this` in each command.
       
    @returns {Object} context.
   
    */

    qs.run = function (commands, context) {
      var command, i = -1;
      if (!context) {
        context = {};
      }
      while ((command = commands[++i])) {
        if (command.fn.apply(context, command.args)) {
          return context;
        }
      }
      return context;
    };
    /**
   
    @namespace
   
    Predefined commands for manipulating a collection of DOM nodes.
   
    @description
   
    */

    qs.cmd = {
      /**
       
      Get an element by id from the context document,
      and set the context nodes to an array containing the result,
      or an empty array.
   
      @param {String} id
     
      @return {Boolean}
          true if no more commands should be processed, else false.
         
      */

      getById: function (id) { // getById
        var e = this.doc.getElementById(id);
        this.nodes = e ? [e] : [];
        return !e;
      },
      /**
       
      Get a NodeList by class name from the context root,
      and set the context nodes to the result.
   
      @param {String} className
     
      @return {Boolean}
          true if no more commands should be processed, else false.
         
      */

      getByClass: function (className) {  // getByClass
        this.nodes = this.root.getElementsByClassName(className);
        return !this.nodes.length;
      },
      /**
       
      Get a NodeList by tag name from the context root,
      and set the context nodes to the result.
   
      @param {String} tagName
     
      @param {Boolean} setRoot
          If true, set the context root to the first found node.
     
      @return {Boolean}
          true if no more commands should be processed, else false.
         
      */

      getByTag: function (tagName, setRoot) { // getByTag
        this.nodes = this.root.getElementsByTagName(tagName);
        if (setRoot) {
          this.root = this.nodes[0];
        }
        return !this.nodes.length;
      },
      /**
       
      Filter the context nodes.
   
      @param {qs.Compound} compound
     
      @return {Boolean}
          true if no more commands should be processed, else false.
         
      */

      filter: function (compound) { // filter
        var nodes = this.nodes, node, i = -1, result = [];
        while ((node = nodes[++i])) {
          if (qs.check(node, compound)) {
            result.push(node);
          }
        }
        this.nodes = result;
        return !result.length;
      },
      /**
       
      Check whether the context nodes' ancestors match a chain of
      compound selectors.
   
      @param {qs.Compound} compound...
          One argument for each ancestor in the chain. The "oldest"
          ancestor should be the first argument, and the "youngest"
          should be the last.
     
      @return {Boolean}
          true if no more commands should be processed, else false.
           
      */

      checkAncestors: function (/*...*/) { // checkAncestors
        var root = this.root, nodes = this.nodes, node, result = [],
            check, len = arguments.length, checkIndex = len, i = -1,
            ancestor, topAncestor;
        while ((node = nodes[++i])) {
          ancestor = node;
          check = arguments[--checkIndex];
          while ((ancestor = ancestor.parentNode) &&
                (ancestor !== root)) {
            if (!qs.check(ancestor, check)) {
              continue;
            }
            check = arguments[--checkIndex];
            if (checkIndex < 0) {
              topAncestor = ancestor;
              result.push(node);
              break;
            }
          }
          checkIndex = len;
        }
        this.topAncestor = topAncestor;
        this.nodes = result;
        return !result.length;
      },
      /**
       
      Check whether the context node is contained by the root node.
     
      This command should be run if the *getById* command has been run.
      It should run after any *filter* or *checkAncestors* commands
      immediately following each *getById* command.
       
      @return {Boolean}
          true if no more commands should be processed, else false.
         
      */

      checkIdRoot: function () { // checkIdRoot
        var root = this.root,
            node = this.topAncestor || this.nodes[0];
        if (!root.ownerDocument) {
          root = this.nodes[0];
          return false;
        }
        while ((node = node.parentNode)) {
          if (node === root) {
            root = this.nodes[0];
            return false;
          }
        }
        return true;
      }
     
    };
    /**
   
    @class
   
    Stores a compound selector in object form.
   
    @see <a href="http://www.w3.org/TR/selectors4/#structure">
    Selectors Level 4: Structure and Terminology
    </a>
   
    @param {String} text
        Normalized compound selector text.
       
    */

    qs.Compound = function (text) {
      /**
     
      CSS class to match.
      @type String
     
      */

      this.className = (text.match(qs.rx.className) || [])
          .join('').substring(1).split('.');
      /**
     
      Tag name to match.
      @type String
     
      */

      this.tagName = ((text.match(qs.rx.tagName) || [])[0] || '')
          .toUpperCase();
      /**
     
      Id attribute to match.
      @type String
     
      */

      this.id = ((text.match(qs.rx.id) || [])[0] || '')
          .substring(1);
      this.className.sort();
    };
   
    qs.Compound.prototype = {
      /**
   
      Create a plain object copy of the current object.
         
      @returns {Object}
     
      */

      copy: function () {
        return {
          id: this.id,
          className: this.className.slice(),  
          tagName: this.tagName
        };
      },
      /**
   
      Get the normalized version of the compound selector.
   
      @returns {String}
          The normalized compound selector.
   
      */

      toString: function () {
       
        return this.normalized ||
            (this.normalized = this.tagName +
            (this.id ? '#' + this.id  : '') +
            (this.className[0] ? '.' + this.className.join('.') : ''));
      }
    };
    /**
   
    @class
   
    Stores information about a query selector.
       
    @constructor
   
    @param {String} text
        Query selector text.
   
    */

    qs.Query = function (text) {
      var compoundStrings = text.split(qs.rx.comma),
          compound, compounds, i = -1, j,
          original = text;
      while ((text = compoundStrings[++i])) {
        compounds = qs.trim(text).split(qs.rx.space);
        j = -1;
        while ((compound = compounds[++j])) {
          compounds[j] = new qs.Compound(compound);
        }
        compoundStrings[i] = compounds;
      }
      compounds = compoundStrings;
      /**
     
      The original (non-normalized) query string.
      @type String
     
      */

      this.original = original;
      /**
     
      Compound selectors composing the query.
      @type Array
     
      */

      this.compounds = compounds.sort();
      /**
     
      Normalized version of the query.
      @type String
     
      */

      this.normalized = '';
    };
    /**
   
    Get the normalized version of the original query selector.
   
    @returns {String}
        The normalized query selector.
   
    */

    qs.Query.prototype.toString = function () {
      if (this.normalized) {
        return this.normalized;
      }
      var compounds = this.compounds, selector, i = -1, result = '';
      while ((selector = compounds[++i])) {
        result += (i ? ', ' : '') + selector.join(' ');
      }
      return (this.normalized = result);
    };
};
</script>

Preparation code output

Test runner

Warning! For accurate results, please disable Firebug before running the tests. (Why?)

Java applet disabled.

Testing in unknown unknown
Test Ops/sec
Dojo
var d1 = dojo.query(".section");
pending…
jQuery
var j1 = jQuery('.section');
pending…
Mootols
var m1 = $$('.section');
pending…
jQuery - inside
var j2 = jQuery('.section .age2');
pending…
Mootools - inside
var m2 = $$('.section .age2');
pending…
Dojo - inside
var d2 = dojo.query(".section .age2");
pending…
YUI
YUI().use('node', function(Y) {
  var y1 = Y.one('.section');
});
pending…
YUI Inside
YUI().use('node', function(Y) {
  var y1 = Y.one('.section .age2');
});
pending…
qs
var q1 = qs('.section');
pending…
qs Inside
var q2 = qs('.section .age2');
pending…

Compare results of other browsers

Revisions

You can edit these tests or add even more tests to this page by appending /edit to the URL. Here’s a list of current revisions for this page:

0 comments

Add a comment