From a338b407f2479f82df40635055effc163835183f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Micha=C5=82=20Go=C5=82=C4=99biowski-Owczarek?= Date: Mon, 24 Jan 2022 18:56:49 +0100 Subject: [PATCH] CSS: Skip falsy values in `addClass( array )`, compress code This change makes jQuery skip falsy values in `addClass( array )` & `removeClass( array )` instead of stopping iteration when the first falsy value is detected. This makes code like: ```js elem.addClass( [ "a", "", "b" ] ); ``` add both the `a` & `b` classes. The code was also optimized for size a bit so it doesn't increase the minified gzipped size. Fixes gh-4998 Closes gh-5003 --- src/attributes/classes.js | 84 ++++++++++++++++++++------------------- test/unit/attributes.js | 38 ++++++++++++++++++ 2 files changed, 82 insertions(+), 40 deletions(-) diff --git a/src/attributes/classes.js b/src/attributes/classes.js index 796fbcc80..5e64ea2ae 100644 --- a/src/attributes/classes.js +++ b/src/attributes/classes.js @@ -20,8 +20,7 @@ function classesToArray( value ) { jQuery.fn.extend( { addClass: function( value ) { - var classes, elem, cur, curValue, clazz, j, finalValue, - i = 0; + var classNames, cur, curValue, className, i, finalValue; if ( typeof value === "function" ) { return this.each( function( j ) { @@ -29,36 +28,35 @@ jQuery.fn.extend( { } ); } - classes = classesToArray( value ); + classNames = classesToArray( value ); - if ( classes.length ) { - while ( ( elem = this[ i++ ] ) ) { - curValue = getClass( elem ); - cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); + if ( classNames.length ) { + return this.each( function() { + curValue = getClass( this ); + cur = this.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); if ( cur ) { - j = 0; - while ( ( clazz = classes[ j++ ] ) ) { - if ( cur.indexOf( " " + clazz + " " ) < 0 ) { - cur += clazz + " "; + for ( i = 0; i < classNames.length; i++ ) { + className = classNames[ i ]; + if ( cur.indexOf( " " + className + " " ) < 0 ) { + cur += className + " "; } } // Only assign if different to avoid unneeded rendering. finalValue = stripAndCollapse( cur ); if ( curValue !== finalValue ) { - elem.setAttribute( "class", finalValue ); + this.setAttribute( "class", finalValue ); } } - } + } ); } return this; }, removeClass: function( value ) { - var classes, elem, cur, curValue, clazz, j, finalValue, - i = 0; + var classNames, cur, curValue, className, i, finalValue; if ( typeof value === "function" ) { return this.each( function( j ) { @@ -70,38 +68,40 @@ jQuery.fn.extend( { return this.attr( "class", "" ); } - classes = classesToArray( value ); + classNames = classesToArray( value ); - if ( classes.length ) { - while ( ( elem = this[ i++ ] ) ) { - curValue = getClass( elem ); + if ( classNames.length ) { + return this.each( function() { + curValue = getClass( this ); // This expression is here for better compressibility (see addClass) - cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); + cur = this.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " ); if ( cur ) { - j = 0; - while ( ( clazz = classes[ j++ ] ) ) { + for ( i = 0; i < classNames.length; i++ ) { + className = classNames[ i ]; // Remove *all* instances - while ( cur.indexOf( " " + clazz + " " ) > -1 ) { - cur = cur.replace( " " + clazz + " ", " " ); + while ( cur.indexOf( " " + className + " " ) > -1 ) { + cur = cur.replace( " " + className + " ", " " ); } } // Only assign if different to avoid unneeded rendering. finalValue = stripAndCollapse( cur ); if ( curValue !== finalValue ) { - elem.setAttribute( "class", finalValue ); + this.setAttribute( "class", finalValue ); } } - } + } ); } return this; }, toggleClass: function( value, stateVal ) { + var classNames, className, i, self; + if ( typeof value === "function" ) { return this.each( function( i ) { jQuery( this ).toggleClass( @@ -115,24 +115,28 @@ jQuery.fn.extend( { return stateVal ? this.addClass( value ) : this.removeClass( value ); } - return this.each( function() { - var className, i, self, classNames; + classNames = classesToArray( value ); - // Toggle individual class names - i = 0; - self = jQuery( this ); - classNames = classesToArray( value ); + if ( classNames.length ) { + return this.each( function() { - while ( ( className = classNames[ i++ ] ) ) { + // Toggle individual class names + self = jQuery( this ); - // Check each className given, space separated list - if ( self.hasClass( className ) ) { - self.removeClass( className ); - } else { - self.addClass( className ); + for ( i = 0; i < classNames.length; i++ ) { + className = classNames[ i ]; + + // Check each className given, space separated list + if ( self.hasClass( className ) ) { + self.removeClass( className ); + } else { + self.addClass( className ); + } } - } - } ); + } ); + } + + return this; }, hasClass: function( selector ) { diff --git a/test/unit/attributes.js b/test/unit/attributes.js index 98fae06c7..5f2418c7e 100644 --- a/test/unit/attributes.js +++ b/test/unit/attributes.js @@ -1644,6 +1644,44 @@ QUnit.test( "addClass, removeClass, hasClass on elements with classes with non-H testMatches(); } ); +( function() { + var rnothtmlwhite = /[^\x20\t\r\n\f]+/g; + + function expectClasses( assert, elem, classes ) { + var actualClassesSorted = ( elem.attr( "class" ).match( rnothtmlwhite ) || [] ) + .sort().join( " " ); + var classesSorted = classes.slice() + .sort().join( " " ); + assert.equal( actualClassesSorted, classesSorted, "Expected classes present" ); + } + + QUnit.test( "addClass on arrays with falsy elements (gh-4998)", function( assert ) { + assert.expect( 3 ); + + var elem = jQuery( "
" ); + + elem.addClass( [ "b", "", "c" ] ); + expectClasses( assert, elem, [ "a", "b", "c" ] ); + elem.addClass( [ "", "d" ] ); + expectClasses( assert, elem, [ "a", "b", "c", "d" ] ); + elem.addClass( [ "e", "" ] ); + expectClasses( assert, elem, [ "a", "b", "c", "d", "e" ] ); + } ); + + QUnit.test( "removeClass on arrays with falsy elements (gh-4998)", function( assert ) { + assert.expect( 3 ); + + var elem = jQuery( "
" ); + + elem.removeClass( [ "e", "" ] ); + expectClasses( assert, elem, [ "a", "b", "c", "d" ] ); + elem.removeClass( [ "", "d" ] ); + expectClasses( assert, elem, [ "a", "b", "c" ] ); + elem.removeClass( [ "b", "", "c" ] ); + expectClasses( assert, elem, [ "a" ] ); + } ); +} )(); + QUnit.test( "contents().hasClass() returns correct values", function( assert ) { assert.expect( 2 );