jquery hasclass vs addclass with simple perf check (v7)

Revision 7 of this benchmark created on


Description

See http://jsperf.com/jquery-if-hasclass-then-addclass-vs-addclass/4 for an idea of how this performs without the check.

This simple patch checks if the className attr has actually changed before setting it on the element, preventing us from writing to the DOM if we don't need to.

With recent jQuery.

Preparation HTML

<script src="//code.jquery.com/jquery-2.1.1.js"></script>
<script>
// Overwrite addClass, removeClass

// from core
var core_rnotwhite = /\S+/g;

var nodeHook, boolHook,
  rclass = /[\t\r\n\f]/g,
  rreturn = /\r/g,
  rfocusable = /^(?:input|select|textarea|button)$/i;

jQuery.fn.extend({

  addClass: function( value ) {
    var classes, elem, cur, clazz, j, result
      i = 0,
      len = this.length,
      proceed = typeof value === "string" && value;

    if ( jQuery.isFunction( value ) ) {
      return this.each(function( j ) {
        jQuery( this ).addClass( value.call( this, j, this.className ) );
      });
    }

    if ( proceed ) {
      // The disjunction here is for better compressibility (see removeClass)
      classes = ( value || "" ).match( core_rnotwhite ) || [];

      for ( ; i < len; i++ ) {
        elem = this[ i ];
        cur = elem.nodeType === 1 && ( elem.className ?
          ( " " + elem.className + " " ).replace( rclass, " " ) :
          " "
        );

        if ( cur ) {
          j = 0;
          while ( (clazz = classes[j++]) ) {
            if ( cur.indexOf( " " + clazz + " " ) < 0 ) {
              cur += clazz + " ";
            }
          }
          result = jQuery.trim( cur ); // edited here
          if( result !== elem.className ){
            elem.className = result;
          }
        }
      }
    }

    return this;
  },

  removeClass: function( value ) {
    var classes, elem, cur, clazz, j, result,
      i = 0,
      len = this.length,
      proceed = arguments.length === 0 || typeof value === "string" && value;

    if ( jQuery.isFunction( value ) ) {
      return this.each(function( j ) {
        jQuery( this ).removeClass( value.call( this, j, this.className ) );
      });
    }
    if ( proceed ) {
      classes = ( value || "" ).match( core_rnotwhite ) || [];

      for ( ; i < len; i++ ) {
        elem = this[ i ];
        // This expression is here for better compressibility (see addClass)
        cur = elem.nodeType === 1 && ( elem.className ?
          ( " " + elem.className + " " ).replace( rclass, " " ) :
          ""
        );

        if ( cur ) {
          j = 0;
          while ( (clazz = classes[j++]) ) {
            // Remove *all* instances
            while ( cur.indexOf( " " + clazz + " " ) >= 0 ) {
              cur = cur.replace( " " + clazz + " ", " " );
            }
          }
          result = value ? jQuery.trim( cur ) : "";
          if( result !== elem.className ){ // edited here
            elem.className = result;
          }
        }
      }
    }

    return this;
  }
});
</script>

<div id="div1">
</div>
<div id="div2" class="test2">
</div>
<div id="div3" class="test2 test3">
</div>
<div id="div4" class="test1">
</div>
<div id="div5" class="test1 test2">
</div>
<div id="div6" class="test1 test2 test3">
</div>

Setup

var div1 = $('#div1');
    var div2 = $('#div2');
    var div3 = $('#div3');
    var div4 = $('#div4');
    var div5 = $('#div5');
    var div6 = $('#div6');

Teardown


    div1[0].className = '';
    div2[0].className = 'test2';
    div3[0].className = 'test2 test3';
    div4[0].className = 'test1';
    div5[0].className = 'test1 test2';
    div6[0].className = 'test1 test2 test3';
  

Test runner

Ready to run.

Testing in
TestOps/sec
if hasclass then addclass
if (!div1.hasClass('test1')) div1.addClass('test1');
if (!div2.hasClass('test1')) div2.addClass('test1');
if (!div3.hasClass('test1')) div3.addClass('test1');
if (!div4.hasClass('test1')) div4.addClass('test1');
if (!div5.hasClass('test1')) div5.addClass('test1');
if (!div6.hasClass('test1')) div6.addClass('test1');
ready
addclass
div1.addClass('test1');
div2.addClass('test1');
div3.addClass('test1');
div4.addClass('test1');
div5.addClass('test1');
div6.addClass('test1');
ready
if !hasclass then removeClass
if (div1.hasClass('test1')) div1.removeClass('test1');
if (div2.hasClass('test1')) div2.removeClass('test1');
if (div3.hasClass('test1')) div3.removeClass('test1');
if (div4.hasClass('test1')) div4.removeClass('test1');
if (div5.hasClass('test1')) div5.removeClass('test1');
if (div6.hasClass('test1')) div6.removeClass('test1');
ready
removeclass
div1.removeClass('test1');
div2.removeClass('test1');
div3.removeClass('test1');
div4.removeClass('test1');
div5.removeClass('test1');
div6.removeClass('test1');
ready
if !hasclass addclass multiple classes
if (!div1.hasClass('test1') && !div1.hasClass('test2')) div1.addClass('test1 test2');
if (!div2.hasClass('test1') && !div2.hasClass('test2')) div2.addClass('test1 test2');
if (!div3.hasClass('test1') && !div3.hasClass('test2')) div3.addClass('test1 test2');
if (!div4.hasClass('test1') && !div4.hasClass('test2')) div4.addClass('test1 test2');
if (!div5.hasClass('test1') && !div5.hasClass('test2')) div5.addClass('test1 test2');
if (!div6.hasClass('test1') && !div6.hasClass('test2')) div6.addClass('test1 test2');
ready
addclass multiple classes
div1.addClass('test1 test2');
div2.addClass('test1 test2');
div3.addClass('test1 test2');
div4.addClass('test1 test2');
div5.addClass('test1 test2');
div6.addClass('test1 test2');
ready
if hasclass removeclass multiple classes
if(div1.hasClass('test1')) div1.removeClass('test1');
if(div1.hasClass('test2')) div1.removeClass('test2');

if(div2.hasClass('test1')) div2.removeClass('test1');
if(div2.hasClass('test2')) div2.removeClass('test2');

if(div3.hasClass('test1')) div3.removeClass('test1');
if(div3.hasClass('test2')) div3.removeClass('test2');

if(div4.hasClass('test1')) div4.removeClass('test1');
if(div4.hasClass('test2')) div4.removeClass('test2');

if(div5.hasClass('test1')) div5.removeClass('test1');
if(div5.hasClass('test2')) div5.removeClass('test2');

if(div6.hasClass('test1')) div6.removeClass('test1');
if(div6.hasClass('test2')) div6.removeClass('test2');
ready
removeclass multiple classes
div1.removeClass('test1 test2');
div2.removeClass('test1 test2');
div3.removeClass('test1 test2');
div4.removeClass('test1 test2');
div5.removeClass('test1 test2');
div6.removeClass('test1 test2');
ready

Revisions

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