jQuery源码分析-13 CSS操作-CSS-类样式-addClass+remov

时间:2015-11-23 22:44来源:未知 作者:上海网站建设 点击:
概述 本人开发时偶尔需要 操作样式表 ,更多的是操作类样式,由JavaScript负责数据、业务逻辑、交互,CSS负责呈现效果,尽可能的各司其职,减少JavaScript与CSS的耦合,便于维护。 jQu

概述

 

本人开发时偶尔需要操作样式表,更多的是操作类样式,由JavaScript负责数据、业务逻辑、交互,CSS负责呈现效果,尽可能的各司其职,减少JavaScript与CSS的耦合,便于维护。

jQuery提供了4个操作class的方法:

jQuery.fn.extend({
	// ...
	// 为匹配的每个元素增加指定的class(es)
	addClass: function( value ) {},
	// 从匹配的每个元素上,移除 一个 或 多个 或 全部class
	removeClass: function( value ) {},
	// 对匹配元素集中的每个元素增加或删除一个或多个class
	toggleClass: function( value, stateVal ) {},
	// 检测匹配的元素是否指定了传入的class,只要有一个匹配就返回true
	hasClass: function( selector ) {},
	// ...
});

实现的核心技巧是:先前后加空格,然后用 indexOf 查找类样式位置 或 用 replace 删除类样式。非常简洁实用,值得借鉴。

相对操作样式表要兼容浏览器差异、DOM/HTML样式属性以及一些特殊的bug等等,操作类样式的实现更优雅规整一些。开始逐个学习吧。

 

.addClass()

 

.addClass()负责为匹配的每个元素增加指定的class(es),一次可以添加多个class;.addClass()不会替换已有的class属性值,仅仅是简单的追加,重复的class会被过滤不会被重复追加(1.6.2之前可能不会过滤);从jQuery1.4开始,.addClass()支持参数为函数。

代码执行过程概述如下:

如果是函数,执行函数,将返回结果再次调用.addClass()

如果是字符串

    如果className为空,且只传入一个className,则直接赋值给elem.className

    否则在elem.className和classNames[c]前后加空格,判断indexOf的返回值,是忽略还是追加

核心技巧:前后加空格 + indexOf,详看源码分析

/**
 * 为匹配的每个元素增加指定的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;
},

 

.removeClass()

 

.removeClass() 从匹配的每个元素上,移除 一个 或 多个 或 全部class;如果传入一个class参数,只有这个class会被移除;如果没有指定任何参数,所有的class会被移除;一次可以移除多个class,多个class之间用空格分隔;这个方法经常与.addClass配合使用,用来切换元素的class从一个变为另一个;如果要用一个class替换所有的现有class,可以使用.attr('class', 'newClass')代替;从jQuery1.4开始,.removeClass()方法支持传入一个函数作为参数。

代码执行过程概述如下:

如果是函数,执行函数,将返回结果再次调用jQuery.fn.removeClass

如果是字符串,在elem.className和classNames[c]前后加空格,判断replace删除

如果是undefined,置为空字符串elem.className = ""

核心技巧:前后加空格 + replace,详看源码分析

/**
 * 从匹配的每个元素上,移除 一个 或 多个 或 全部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;
},

 

.toggleClass()

 

.toggleClass() 负责对匹配元素集中的每个元素增加或删除一个或多个class,增加或删除的行为依赖当前元素是否含有指定的class或switch参数的值;.toggleClass()接受一个或多个class;自从jQuery1.4以后,如果没有为.toggleClass()指定参数,元素上的所有class名称将被切换;自从jQuery1.4以后,className可以是一个函数,函数的返回值作为切换的className。

代码执行过程概述如下:

如果是函数,则执行函数,用函数的返回值作为切换的className,迭代调用jQuery.fn.toggleClass

遍历当前jQuery对象

    如果 value 是字符串,挨个遍历value中的类样式,switch的优先级高于hasClass,hasClass返回false则addClass返回true则removeClass

    如果 未指定参数 或 只有switch,则切换整个className

核心技巧:调用addClass 或 removeClass 或 直接赋值elem.className。

使用时要注意:在看源码的过程中发现switch在不同的参数环境下功能不一致。.toggleClass()用4种用法(详见后文源码注释):

1  .toggleClass( className ) 1.0
2  .toggleClass( className, switch ) 1.3
3  .toggleClass( [switch] ) 1.4
4  .toggleClass( function(index, class, switch) [, switch] ) 1.4

在2和4中依据switch来决定是添加(true)还是删除(false),例如:

$('#foo').toggleClass(className, addOrRemove);
// 本质上等价于:
if (addOrRemove) $('#foo').addClass(className);
else $('#foo').removeClass(className);

但是在3中,如果switch为true,进行正常的全部切换,等价于undefined;如果switch为false,则总是置空;事实上,这一点在官方API中并没有提及。这一逻辑在最后一行的三元表达式中得到体现。

详看源码分析(从代码技巧的风骚度看,个人觉得最精妙的是最后一行代码):

/**
 * 对匹配元素集中的每个元素增加或删除一个或多个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__" ) || "";
		}
	});
},

 

.hasClass()

 

.hasClass() 检测匹配的元素是否指定了传入的class,只要有一个匹配就返回true;元素可能有多个class,在HTML中多个class用空格隔开;如果遇到某个元素含有指定的className,.hasClass()将会返回true,即便还指定了其他的className。

注意:

1. 如果selector自带前/后空格,就不能正确的检测,事实上这里可以过滤前后空格,然后再检测,这样在动态的检测className时,代码更灵活

2. selector可以含有多个类样式,但不会拆分为数组而是作为整体检测,例如下面的情况就不能返回正确的结果:

$('div').addClass('1 2 3').hasClass('1 3') // false

核心技巧:前后加空格 + indexOf

源码分析

/**
* 检测匹配的元素是否指定了传入的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;
},


(责任编辑:上海网站建设)
------分隔线----------------------------
关于我们
企业简介
企业概况
企业文化
经营理念
人才招聘
新闻资讯
行业动态
公司新闻
服务区域
联系我们
付款方式
网站地图
联系我们
服务项目
网站建设
模版型
设计型
营销型
集团豪华型
电子商务
手机网站建设
网站建设流程
网站优化
SEO优化
网站排名优化
关键词优化
增值服务
域名注册
服务器租用
企业邮箱
OA办公软件
微信营销
微信公众号申请
微商城开发
微店怎么开
视频制作
企业宣传片
产品介绍摄影
商业活动
产品拍照
400电话
特级号
超级号
A类号
B类号
C类号
D类号
软件/APP开发
APP开发
微信开发
软件开发
创意设计
LOGO设计
VI设计
包装设计
宣传册设计
广告设计
UI设计
营销推广
搜索推广
百度推广
谷歌推广
360推广
诚信通推广
营销软件
搜索下拉框推广
抓取qq软件
群发软件
微信群发
淘宝营销
淘宝设计
淘宝店铺装修
淘宝运营
案例展示
网页设计案例
模板网站
设计网站
营销网站
集团网站
电子商务网站
手机网站
微信营销案例
微信公众号
微商城
手机网站开发
微店
APP开发
视频案例
企业宣传片
产品介绍摄影
商业活动
产品拍照
400案例
特级号/超级号
A类B类C类D类
创意设计
LOGO设计
VI设计
包装设计
宣传册设计
广告设计
UI设计
技术课堂
优化网站
SEO网站优化
网络营销
品牌营销
网站营销推广
微信营销
网站知识
技术课堂
帮助中心
网络问题
网站建设问题
服务器问题
企业邮箱问题
网站优化问题
400问题
常见问题
营销推广问题
微信问题
APP开发问题
微店问题
营销问题
设计问题
常见问题
联系我们