Getting elements with this query method versus manually (v2)

Revision 2 of this benchmark created by Craig Patik on


Description

When querying a complex selector (multiple tag names and attributes), compare the use of an intermediary query() method to manually calling getElementsByTagName and getElementsByClassName separately.

Preparation HTML

<style>
                div.a, div.b, p.a, p[id^="result"] {
                        border: 1px solid #444;
                        padding: 3px;
                }
        </style>
<div class="a">div.a
                <div class="a b">div.a.b
                        <input type="text" class="a" value="input.a">
                        <input type="button" class="a" value="input.a">
                        <input type="button" class="b" value="input.b">
                        <p class="b">p.b</p>
                </div>
        </div>
        <div class="b">div.b
                <div>div
                        <input type="text" value="input">
                        <input type="button" value="input">
                        <input type="button" value="input">
                        <input type="button" class="a b" value="input.a.b">
                </div>
                <div class="b">div.b</div>
        </div>
        <p id="result1">p</p>
        <p id="result2">p</p>
        <p id="result3">p</p>
        <p id="result4">p</p>
        <p id="result5">p</p>
        <p id="result6">p</p>

Setup

var elements, i, results, filter1;
  
  function query (sSelector,oNode,oSettings)
  {
        if (!sSelector || typeof sSelector !== 'string') { return false; }
        var aElements = [], sTagName, sProperty, i,
        bIsArray = false,
            aSelectors = sSelector.replace(/\s*\,\s*/,',').split(','),
                        bSingleSelector = (aSelectors.length === 1);
        oNode = oNode || document;
        // Check settings
        oSettings = oSettings || {filter:null,toArray:false};
        fFilter = oSettings.filter || null;
        bToArray = oSettings.toArray || false;
        
        // Loop through each selector
        i = 0;
        while (i < aSelectors.length) {
                sSelector = aSelectors[i];
                // By class name
                if (sSelector.indexOf('.') > -1) {
                        sTagName = sSelector.split('.')[0] || "*";
                        sProperty = sSelector.split('.')[1] || "";
                        // If there is only one selector or the previous selectors did not return 
                        //   anything, this will be the only set of results so far
                        if (bSingleSelector || !aElements.length) {
                                aElements = getElementsByClass(oNode,sTagName,sProperty);
                        }
                        else {
                                // Otherwise, these results will need to be concatenated with the others 
                                //   so we need an actual array
                                if (!bIsArray && aElements.length) {
                                        aElements = toArray(aElements);
                                }
                                aElements = aElements.concat(getElementsByClass(oNode,sTagName,sProperty));
                        }
                        bIsArray = true; // getElementsByClass() always returns an array
                }
                // By element ID
                else if (sSelector.indexOf('#') > -1) {
                        // Get the ID
                        sProperty = sSelector.substr(sSelector.indexOf('#')+1);
                        if (oNode.getElementById(sProperty)) {
                                // If the results list is not already an array and it's not empty, it must be converted first
                                if (!bSingleSelector && !bIsArray && aElements.length) {
                                        aElements = toArray(aElements);
                                }
                                aElements.push(oNode.getElementById(sProperty));
                                bIsArray = true;
                        }
                }
                // By tag name
                else {
                        // Don't need to convert to an array if these are the only results so far
                        if (bSingleSelector || !aElements.length) {
                                aElements = oNode.getElementsByTagName(sSelector);
                        }
                        else {
                                if (!bIsArray && aElements.length) {
                                        aElements = toArray(aElements);
                                        bIsArray = true;
                                }
                                aElements = aElements.concat(toArray(oNode.getElementsByTagName(sSelector)));
                        }
                }
                i++;
        }
        
        // Convert to array based on settings, if it hasn't been done already
        if (!bIsArray && (bToArray || fFilter)) {
                aElements = toArray(aElements);
                bIsArray = true;
        }
        
        // Filter results
        if (fFilter) {
                        i = 0;
                        while (i < aElements.length) {
                                if (!fFilter(aElements[i])) {
                                        // Remove this element from the array
                                        aElements.splice(i, 1);
                                        i--;
                                }
                                i++;
                        }
        }
        
        return aElements;
  }
        
  function toArray (aList) {
        var aArray = [];
        try {
                // This method is defined in IE7/8 but doesn't work on all types of lists
                aArray = Array.prototype.slice.call(aList);
        }       catch (e) { }
        if (!aArray.length && aList.length) {
                var i = 0;
                while (i < aList.length) {
                        aArray.push(aList[i]);
                        i++;
                }
        }
        return aArray;
  }
  
  function getElementsByClass (oNode, sTag, sClassNames)
  {
        if (!sClassNames || typeof sClassNames != 'string') { return false; }
        // Set defaults if these parameters weren't specified
        if (typeof oNode != 'object') { oNode = document; }
        if (!sTag) { sTag = '*'; }
        
        var i, j, aReturnElements = []
                        // Trim class list, reduce whitespace to single spaces, then split on spaces
                 ,aClasses = sClassNames.replace(/^\s+|\s+$/g,'').replace(/\s+/g,' ').split(' ')
                        // Get all elements to search through
                 ,aElements = oNode.getElementsByTagName(sTag);
        
        // Loop through elements and check each one's classes against the list
        // The outer loop cannot be 'reversed' like while(i--), causes problems with clickCheckbox()
        i = 0;
        while (i < aElements.length) {
                j = aClasses.length;
                while (j--) {
                        if (hasClass(aElements[i],aClasses[j])) {
                                aReturnElements.push(aElements[i]);
                                // Quit the classes loop so this element doesn't get added again if it matches another class
                                break;
                        }
                }
                i++;
        }
        return aReturnElements;
  }
  
  function hasClass (oElem,sClassName)
  {
        // Fix oElem if 'this' was passed in IE
        if (!oElem && window.event) { oElem = event.srcElement; }
        // Check parameters
        if (!oElem.className || typeof sClassName != 'string') { return null; }
        // Test for the class
        sClassName = ' ' + sClassName.replace(/^\s+|\s+$/g,'') + ' ';
        return (' ' + oElem.className + ' ').indexOf(sClassName) > -1;
  }

  Benchmark.prototype.setup = function() {
    elements = [];
    i = 0;
    results = [];
    filter1 = null;
  };

Teardown


    elements = [];
    i = 0;
    results = [];
    filter1 = null;
  

Test runner

Ready to run.

Testing in
TestOps/sec
Manual tag names
// Selector: "input, div, p"

results = [];
elements = document.getElementsByTagName('input');
i = 0;
while (i < elements.length) {
        results.push(elements[i]);
        i++;
}
elements = document.getElementsByTagName('div');
i = 0;
while (i < elements.length) {
        results.push(elements[i]);
        i++;
}
elements = document.getElementsByTagName('p');
i = 0;
while (i < elements.length) {
        results.push(elements[i]);
        i++;
}
document.getElementById('result1').innerHTML = 'Manual tag names: ' + results.length;
ready
Query() tag names
// Selector: "input, div, p"

results = query('input,div,p');
document.getElementById('result2').innerHTML = 'Query() tag names: ' + results.length;
ready
Manual class names
// Selector: "div.a, p"

results = getElementsByClass(document,'div','a');
elements = document.getElementsByTagName('p');
i = 0;
while (i < elements.length) {
        results.push(elements[i]);
        i++;
}
document.getElementById('result3').innerHTML = 'Manual class names: ' + results.length;
ready
Query() class names
// Selector: "div.a, p"

results = query('div.a,p');
document.getElementById('result4').innerHTML = 'Query() class names: ' + results.length;
ready
Manual complex
// Selector: "input[type='button'].a"

elements = getElementsByClass(document,'input','a');
i = elements.length;
results = [];
while (i--) {
        if (elements[i].type == 'button') {
                results.push(elements[i]);
        }
}
document.getElementById('result5').innerHTML = 'Manual complex: ' + results.length;
ready
Query() complex
// Selector: "input[type='button'].a"

filter1 = function (elem) { return elem.type == 'button'; };
results = query('input.a',document,{filter:filter1});
document.getElementById('result6').innerHTML = 'Query() complex: ' + results.length;
ready

Revisions

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

  • Revision 1: published by Craig Patik on
  • Revision 2: published by Craig Patik on