Created
December 16, 2013 09:52
-
-
Save markyun/7984625 to your computer and use it in GitHub Desktop.
.removeClass()、.addClass()、toggleClass、hasClass
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/** | |
* 从匹配的每个元素上,移除 一个 或 多个 或 全部class | |
* | |
* .removeClass( [className] ) | |
* className 一个或多个以空格分隔的class,这些class将被从匹配元素的class属性中溢出 | |
* | |
* .removeClass( function(index, class) ) | |
* function(index, class) 函数返回一个或多个以空格分隔的class,用于移除。 | |
* index 当前元素在匹配元素集合中的位置, class 旧class值 | |
* 核心技巧:前后加空格 + replace | |
*/ | |
removeClass: function( value ) { | |
var classNames, i, l, elem, className, c, cl; | |
// 如果传入函数则执行函数,取返回值作为要移除的classNames | |
if ( jQuery.isFunction( value ) ) { | |
return this.each(function( j ) { | |
// 迭代调用,见.addClass()的注释 | |
jQuery( this ).removeClass( value.call(this, j, this.className) ); | |
}); | |
} | |
/* | |
* 对比.addClass()的条件:if ( value && typeof value === "string" ) | |
* 从这里可以看出.removeClass()支持的参数类型: | |
* 函数 迭代处理 | |
* 非空字符串 移除 | |
* undefined 全部移除 | |
* 注:空字符串不做任何处理 | |
*/ | |
if ( (value && typeof value === "string") || value === undefined ) { | |
classNames = ( value || "" ).split( rspace ); // 分割成数组 | |
// value || "" 避免空引用错误的常用技巧(ReferenceError: value is undefined) | |
for ( i = 0, l = this.length; i < l; i++ ) { // 遍历匹配的元素,缓存集合长度 | |
elem = this[ i ]; | |
// Element,并且有className属性,没有className就不需要删除 | |
if ( elem.nodeType === 1 && elem.className ) { | |
// 如果有value,则从当前的className属性中删除 | |
if ( value ) { | |
className = (" " + elem.className + " ").replace( rclass, " " ); // 前后加空格,将\n\t\r替换为空格 | |
for ( c = 0, cl = classNames.length; c < cl; c++ ) { | |
className = className.replace(" " + classNames[ c ] + " ", " "); // 将要删除的className替换为空格 | |
} | |
// 删除前后的空白符,然后赋值给elem.className | |
elem.className = jQuery.trim( className ); | |
// 没有指定value undefined,清空className属性 | |
} else { | |
// 清空 | |
elem.className = ""; | |
} | |
} | |
} | |
} | |
return this; | |
}, | |
/** | |
* 为匹配的每个元素增加指定的class(es) | |
* .addClass( className ) | |
* className 添加到每个匹配元素的class属性上的一个或多个class | |
* | |
* .addClass( function(index, currentClass) ) | |
* function(index, currentClass) 返回一个或多个class名称,多个class用空格分开,这些class被添加到现有的class属性中 | |
* index 当前元素在集合中的位置,currentClass 当前的class名,this 指向集合中的当前元素 | |
* 核心技巧:前后加空格 + indexOf | |
*/ | |
addClass: function( value ) { | |
/* | |
* 从1.6.2开始,这些局部变量被提取到方法的头部,似乎这是jQuery一直以来的习惯:不断的重构代码 | |
* 我个人是反对这种集中定义变量的写法的,一个很明显的理由是: | |
* 我往下读的过程中,遇到没看懂的变量,我需要跳到方法头来理解和验证,然后我再跳回去 | |
*/ | |
var classNames, i, l, elem, | |
setClass, c, cl; | |
// 如果传入函数则执行函数,取返回值作为要设置的classNames | |
if ( jQuery.isFunction( value ) ) { | |
return this.each(function( j ) { | |
/* | |
* 迭代调用 | |
* 在1.6.2以前的版本中会创建var self = jQuery(this); | |
* 通过self.attr("class") || "" 获取当前的class值 | |
* 从1.6.2开始,使用this.className来获取 | |
* 稍微提高性能,但是之前为什么调用attr呢?不理解 | |
* | |
* 另外要注意到,没有jQuery.addClass函数, | |
* 事实上用.addClass()调用jQuery.addClass()这种写法,可以避免构建新的jQuery对象 | |
* 可能创建一个jQuery.addClass,然后复用的地方很少,就全部在jQuery.fn.addClass中实现了 | |
*/ | |
jQuery( this ).addClass( value.call(this, j, this.className) ); | |
}); | |
} | |
// 如果value是字符串,可以看到.addClass()只接受字符串和函数 | |
if ( value && typeof value === "string" ) { | |
classNames = value.split( rspace ); // 用空白符分割classNames,转换为数组 | |
for ( i = 0, l = this.length; i < l; i++ ) { // 遍历所有的匹配元素,缓存长度length | |
elem = this[ i ]; // 缓存下来,避免再次查找 | |
if ( elem.nodeType === 1 ) { // Element | |
/* | |
* 如果没有在HTML中指定class属性,或class属性为空字符串 | |
* 从1.6.2开始增加判断条件classNames.length === 1,多于一个需要去重 | |
* 1.6.2之前未对classNames的长度做判断,即没有去重 | |
* | |
* 在Chrome15中测试,未指定class的div,它的className返回空字符串"" | |
*/ | |
if ( !elem.className && classNames.length === 1 ) { | |
elem.className = value; | |
// 已有className 或 classNames长度大于1 | |
} else { | |
/* | |
* 前后加空格,能正确的通过indexOf判断 | |
* 这里先将elem.className取出来缓存起来,拼装完后再一次性赋值 | |
* 避免因多次修改className造成浏览器多次渲染 | |
*/ | |
setClass = " " + elem.className + " "; | |
for ( c = 0, cl = classNames.length; c < cl; c++ ) { | |
/* | |
* 关于~,摘自《JavaScript权威指南 5th》 | |
* ~ 按位非运算符,~是一元运算符,位于一个整形参数前,将运算数的所有位取反。 | |
* 相当于改变它的符号并且减一。 | |
* 其实这里简单的简单的对indexOf的返回值判断即可,小于0表示不存在 | |
* 不存在,则追加到setClass后 | |
* | |
* 测试: | |
* ~-1 == 0; ~0 == -1; ~1 == 2; ~2 == -3 | |
* !~-1 == true; !~0 == fase; !~1 == false; ~2 == false | |
* | |
* 所以if的判断逻辑是:不存在(-1)返回true,其他情况都返回false | |
* 从1.6.2开始,这里变风骚了;忍不住想测试验证一下: | |
* <pre> | |
* var count = 100000; | |
* console.time('1yuan'); for( i = 0; i < count; i++ ) !~-1; console.timeEnd('1yuan') | |
* console.time('2yuan'); for( i = 0; i < count; i++ ) 1 < -1; console.timeEnd('2yuan') | |
* </pre> | |
* 这个case很简单,将测试用例反复运算、调整顺序运算,并没有发现一元运算符比二元运算符快! | |
* 有待继续挖掘!不排除John Resig开了个玩笑。真心不能排除John Resig偶尔调皮一下的可能性! | |
*/ | |
if ( !~setClass.indexOf( " " + classNames[ c ] + " " ) ) { | |
setClass += classNames[ c ] + " "; // 追加,最后加一个空格 | |
} | |
} | |
/* | |
* 去掉前后的空白符 | |
* trim中将替换过程分为替换前空白符和替换后空白符两步 | |
* 事实上除了在trim中,我也没发现有其他代码用用到了trimLeft trimRight | |
* 如果单考虑效率的话,合并一起来更快 | |
* rtrim = /^\s+|\s+$/; | |
* text.toString().replace( rtrim, "" ); | |
* 这么分开可能是为了潜在的复用 | |
* 因此性能不是唯一的追求,这是John Resig在可读性、复用粒度、性能之间的权衡 | |
*/ | |
elem.className = jQuery.trim( setClass ); | |
} | |
} | |
} | |
} | |
return this; | |
}, | |
/** | |
* 对匹配元素集中的每个元素增加或删除一个或多个class | |
* 增加或删除的行为依赖当前元素是否含有指定的class,或switch参数的值 | |
* | |
* .toggleClass( className ) 1.0 | |
* className 一个或多个class(用空格隔开),在匹配元素集的每个元素上切换class | |
* 如果集合中的某个元素含有指定的className,className会被删除;如果没有会添加。 | |
* | |
* .toggleClass( className, switch ) 1.3 | |
* switch 一个布尔值,依据这个布尔值来决定是添加(true)还是删除(false) | |
* | |
* .toggleClass( [switch] ) 1.4 | |
* switch 一个布尔值,依据这个布尔值来决定是添加还是删除 | |
* | |
* .toggleClass( function(index, class, switch) [, switch] ) 1.4 | |
* function(index, class, switch) 函数返回用于切换的calss名称 | |
* index是当前元素是集合中的下标位置, class是当前元素的就class值 | |
* | |
* 核心技巧:调用addClass 或 removeClass 或 直接赋值elem.className | |
*/ | |
toggleClass: function( value, stateVal ) { | |
var type = typeof value, // value的类型,可以是字符串(一个或多个class),也可以是function,(undefined和boolean是另说) | |
isBool = typeof stateVal === "boolean"; | |
// 如果是函数,则执行函数,用函数的返回值作为切换的className,迭代调用jQuery.fn.toggleClass | |
if ( jQuery.isFunction( value ) ) { | |
return this.each(function( i ) { | |
// 迭代调用 | |
jQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal ); | |
}); | |
} | |
// 遍历当前jQuery对象 | |
return this.each(function() { | |
// value是字符串,挨个遍历value中的类样式,switch的优先级高于hasClass,hasClass返回false则addClass返回true则removeClass | |
if ( type === "string" ) { | |
// toggle individual class names | |
// 切换单个class | |
var className, | |
i = 0, | |
self = jQuery( this ), | |
state = stateVal, | |
classNames = value.split( rspace ); // 可能有多个class,用空白符分割 | |
// 因为不需要在className前后加空格,所以这里可以将取值、自增、判断合并为while循环。很好的技巧。 | |
while ( (className = classNames[ i++ ]) ) { | |
// check each className given, space seperated list | |
/* | |
* 如果state是布尔值,则以state为准,否则检查是否含有className | |
* 含有 state为false,表示需要addClass;反之需要removeClass | |
* 这个三元表达式合并了state与self.hasClass的判断,小技巧 | |
*/ | |
state = isBool ? state : !self.hasClass( className ); | |
self[ state ? "addClass" : "removeClass" ]( className ); | |
} | |
/* | |
* type === "undefined" 未指定参数,即.toggleClass() | |
* type === "boolean" 省略className,只有switch,即.toggleClass( switch ) | |
*/ | |
// 未指定参数 或 只有switch,则切换整个className | |
} else if ( type === "undefined" || type === "boolean" ) { | |
// 如果有className,则缓存下来,以便再次调用时恢复 | |
if ( this.className ) { | |
// store className if set | |
// 以内部数据的方式缓存 | |
jQuery._data( this, "__className__", this.className ); | |
} | |
// toggle whole className | |
/* | |
* 切换整个className | |
* 又是一个合并了几个判断条件的三元,分解为四个逻辑: | |
* this.className && value 是 true/undefined "" | |
* this.className && value 是 false "" | |
* !this.className && value 是 true/undefined jQuery._data( this, "__className__" ) || "" | |
* !this.className && value 是 false "" | |
* | |
* 分析一下上边的四个逻辑,可以总结如下:(value用switch代替) | |
* 1. 如果this.className存在,无论switch什么状态(true/false/undefined),都置为空"" | |
* 2. 如果this.className不存在,如果switch为true/undefined,才会恢复className | |
* 3. 如果this.className不存在,如果switch为false,置空(保持不变) | |
* | |
* .toggleClass( switch )的用法可以总结如下: | |
* 1. switch为true,进行正常的切换,等价于.toggleClass() | |
* 2. switch为false,总是置空 | |
* | |
* 一开始真心看不懂,很精致很风骚! | |
*/ | |
this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || ""; | |
} | |
}); | |
}, | |
/** | |
* 检测匹配的元素是否指定了传入的class,只要有一个匹配就返回true | |
* .hasClass( className ) | |
* className 要查找的class | |
* 核心技巧:前后加空格 + indexOf | |
*/ | |
hasClass: function( selector ) { | |
var className = " " + selector + " ", // 前后加空格 | |
i = 0, | |
l = this.length; | |
for ( ; i < l; i++ ) { | |
// 必须是Element,技巧同样是前后加空格,同样是indexOf | |
if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) > -1 ) { | |
return true; | |
} | |
} | |
return false; | |
}, |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
http://www.cnblogs.com/nuysoft/archive/2011/11/14/2248023.html