《锋利的jQuery》笔记 第1章 认识jQuery

jQuery是支持链式操作的,现在我们用jQuery来实现一个导航栏:
./css/default.css:

/* reset */
body{margin:0;padding:0 0 12px 0;font-size:12px;line-height:22px;font-family:"\5b8b\4f53","Arial Narrow";background:#fff;}
form,ul,li,p,h1,h2,h3,h4,h5,h6{margin:0;padding:0;}
input,select{font-size:12px;line-height:16px;}
img{border:0;}
ul,li{list-style-type:none;}
a {color:#00007F;text-decoration:none;}
a:hover {color:#bd0a01;text-decoration:underline;}

.box {
    width: 150px;
	margin: 0 auto;
}
.menu{
	overflow:hidden;
	border-color: #C4D5DF;
    border-style: solid;
    border-width: 0 1px 1px;
}
/* lv1 */
.menu li.level1 a{
    display: block;
    height: 28px;
    line-height: 28px;
    background:#EBF3F8;
	font-weight:700;
    color: #5893B7;
	text-indent: 14px;
	border-top: 1px solid #C4D5DF;
}
.menu li.level1 a:hover{text-decoration:none;}
.menu li.level1 a.current{background:#B1D7EF;}
/* lv2 */
.menu li ul{overflow:hidden;}
.menu li ul.level2{display:none;}
.menu li ul.level2 li a{
    display: block;
    height: 28px;
    line-height: 28px;
    background:#ffffff;
	font-weight:400;
    color: #42556B;
	text-indent: 18px;
	border-top: 0px solid #ffffff;
    overflow: hidden;
}
.menu li ul.level2 li a:hover{
	color:#f60;
}

navigator.html:

<!DOCTYPE html>
<html>

<head>
	<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
	<title>导航栏</title>
	<link rel="stylesheet" href="css/default.css" type="text/css" />

	<!-- 引入 jQuery -->
	<script src="../../scripts/jquery.js" type="text/javascript"></script>
	<script type="text/javascript">
		//等待dom元素加载完毕.
		$(document).ready(function () {
			$(".level1 > a").click(function () {
				$(this).addClass("current")   //给当前元素添加"current"样式
					.next().show()                //下一个元素显示
					.parent().siblings().children("a").removeClass("current")        //父元素的兄弟元素的子元素<a>移除"current"样式
					.next().hide();                //它们的下一个元素隐藏
				return false;
			});
		});
	</script>

</head>

<body>
	<p>第三步:优化后:</p>

	<div class="box">
		<ul class="menu">
			<li class="level1">
				<a href="#none">衬衫</a>
				<ul class="level2">
					<li><a href="#none">短袖衬衫</a></li>
					<li><a href="#none">长袖衬衫</a></li>
					<li><a href="#none">短袖T恤</a></li>
					<li><a href="#none">长袖T恤</a></li>
				</ul>
			</li>
			<li class="level1">
				<a href="#none">卫衣</a>
				<ul class="level2">
					<li><a href="#none">开襟卫衣</a></li>
					<li><a href="#none">套头卫衣</a></li>
					<li><a href="#none">运动卫衣</a></li>
					<li><a href="#none">童装卫衣</a></li>
				</ul>
			</li>
			<li class="level1">
				<a href="#none">裤子</a>
				<ul class="level2">
					<li><a href="#none">短裤</a></li>
					<li><a href="#none">休闲裤</a></li>
					<li><a href="#none">牛仔裤</a></li>
					<li><a href="#none">免烫卡其裤</a></li>
				</ul>
			</li>
		</ul>
	</div>

</body>

</html>

显示效果如下:
在这里插入图片描述
导航菜单可以正常响应,我们来分析一下事件是如何处理的吧。

  1. $(document)就是返回了一个jQuery特有的实例,其实就是jQuery.fn.init实例;
  2. ready函数上篇文章已经分析过了,就是把参数里的回调函数加入事件队列里,等处理下一个事件队列里的时候就可以执行了,这时候通常所有dom元素都可以正常访问了,当然包含一些特殊情况,比如图片可能还没有加载完成等。
  3. $(".level1 > a"),这个函数也好分析,就是使用css选择器来获得它对应的jquery实例,我们看一下jquery.js源码,第2430行:
// Handle HTML strings
if (typeof selector === "string") {

直接进入第1个if语句执行,接下来:

//我们的selector第一个字符是 .,所以进入else
if (selector[0] === "<" &&
	selector[selector.length - 1] === ">" &&
	selector.length >= 3) {

	// Assume that strings that start and end with <> are HTML and skip the regex check
	match = [null, selector, null];

} else {
	match = rquickExpr.exec(selector);
}

看下rquickExpr.exec()做了什么,先看一下rquickExpr是个什么东东,原来是个RegExp类型的实例,也就是一个正则表达式实例,定义如下:

rquickExpr = /^(?:\s*(<[\w\W]+>)[^>]*|#([\w-]+))$/

与selector进行正则匹配,结果返回给match变量,match是init()函数里的局部变量,我们可以先自己计算出match的结果值,那就是null,好了,继续往下看:
在这里插入图片描述
match = null, context == undefined, 那么就会进入第1个else if语句:

else if (!context || context.jquery) {
    // 最后实际调用root.find('.level1 > a');
	return (context || root).find(selector);
} else {
	return this.constructor(context).find(selector);
}

root等于什么呢,root参数是undefined,在第2427行赋值:

root = root || rootjQuery;

所以root等于rootjQuery,rootjQeury在第2520行赋值:

rootjQuery = jQuery(document);

好吧,这个值就是document元素对应的jQuery实例,就是$(document)。那我们接下来看一下它的find函数是怎么工作的呢?在2363行可以看到find方法,代码不多:

find: function (selector) {
	var i, ret,
		len = this.length,
		self = this;
    // if条件显示不满足,因为我们传进来的就是一个字符串,跳过这个if语句
	if (typeof selector !== "string") {
		return this.pushStack(jQuery(selector).filter(function () {
			for (i = 0; i < len; i++) {
				if (jQuery.contains(self[i], this)) {
					return true;
				}
			}
		}));
	}

	ret = this.pushStack([]);

	for (i = 0; i < len; i++) {
		jQuery.find(selector, self[i], ret);
	}

	return len > 1 ? jQuery.uniqueSort(ret) : ret;
}

如果要看懂find代码内容,就得继续分析里面的代码了。在此之前,你会发现经常看到一些方法调用,比如this.pushStack(), 或者jQuery.find(),在1万行代码里怎么查找呢?如果你没有源码,只有一个jquery.js文件,那么教你一个方法,凡是使用jQuery.extend函数调用,参数里的方法都是加入到jQuery函数里了,可以直接使用jQuery.<functionName>()的,如果使用jQuery.fn.extend()调用,那么方法就加入到jQuery.fn.init()实例里了,可以简单理解为jQuery实例。使用extend()调用的就相当于创建了静态方法,fn.extend()调用的就相当于创建了实例方法。
另外在1万多行代码里来回跳转看,肯定会疯掉的。下面,我们直接在源码的文件夹下面查看了,这样就会方便很多。

我们接下来看一下find()方法中调用的pushStack()方法:

ret = this.pushStack([]);

在core.js第58行里有它的实现:

pushStack: function( elems ) {
	// 这里constructor()是个什么东西,constructor定义在第34行,就是jQuery函数
	// this.contructor(),就是创建了一个jQuery实例
	var ret = jQuery.merge( this.constructor(), elems );
	// Add the old object onto the stack (as a reference)
	ret.prevObject = this;
	// Return the newly-formed element set
	return ret;
}

在第318行,看一下merge函数做了什么:

merge: function( first, second ) {
	var len = +second.length,
		j = 0,
		i = first.length;
	for ( ; j < len; j++ ) {
		first[ i++ ] = second[ j ];
	}
	first.length = i;
	return first;
}

简单,就是把second的所有属性全部添加到first的末尾,其实就是属性拷贝嘛。由于我们在调用pushStack的时候传入了一个空数组[],所以pushStack其实就是创建一个新的jQuery实例。接下来继续分析find()下面的这行:

// len = this.length, this其实是rootjQuery,它在init.js第120行进行了初始化的,在第99行
// 添加了属性"0"和length,this[0] = document, this.length = 1。所以len = 1,下面循环就执行了一次
for (i = 0; i < len; i++) {
    // 相当于调用jQuery.find(".level1 > a", document, ret);// ret就是上面pushStack返回的jQuery实例
	jQuery.find(selector, self[i], ret);
}

再看一下jQuery.find方法,在selector.js最下面,可以看到如下语句:

jQuery.find = find;

原来它就是这个文件里的find()函数,看一下这个函数:

// selector = ".level1 > a", context = document, results = jQuery实例, seed = undefined
function find( selector, context, results, seed ) {
	var m, i, elem, nid, match, groups, newSelector,
		newContext = context && context.ownerDocument,
		// nodeType defaults to 9, since context defaults to document
		nodeType = context ? context.nodeType : 9;
	results = results || [];
	// 我们的selector就是一个string,那么下面的if不执行
	if ( typeof selector !== "string" || !selector ||
		nodeType !== 1 && nodeType !== 9 && nodeType !== 11 ) {
		return results;
	}

	// seed = undefined,满足if条件,进入if语句
	if ( !seed ) {
	    // 下面这个函数很简单,我们相当于调用了setDocument(document),主要初始化了3个全局变量
	    // document = document, documentElement= html, documentIsHTML = true
		setDocument( context );
		context = context || document;

        // if条件成立,进入if语句
		if ( documentIsHTML ) {
		    // nodeType=1,因为是document类型,但是match是null,不执行这个if语句
			if ( nodeType !== 11 && ( match = rquickExpr.exec( selector ) ) ) {
			//....此处代码省略
			}
			// 这里if条件满足,进入if语句
			if ( !nonnativeSelectorCache[ selector + " " ] &&
				( !rbuggyQSA || !rbuggyQSA.test( selector ) ) ) {

				newSelector = selector; 
				newContext = context;
				// if条件不成立,跳过
				if ( nodeType === 1 &&
					( rdescend.test( selector ) || rcombinators.test( selector ) ) ) {
					//....代码省略
				}

				try {
					//开始执行此处,push其实就是import push from "./var/push.js",本质就是[].push方法
					//也就是数组的push方法嘛,使用apply来调用,传入第一个this参数为我们之前创建的jQuery实例,
					//第2个参数就是document.querySelectorAll(".level1 > a"),简单,这其实就是使用js的标准接
					//口来获取所有的元素,然后把返回的NodeList元素全部添加到jQuery实例中,然后返回这个jQuery
					//实例。
					push.apply( results,
						newContext.querySelectorAll( newSelector )
					);
					return results;
				} catch ( qsaError ) {
					nonnativeSelectorCache( selector, true );
				} finally {
					if ( nid === expando ) {
						context.removeAttribute( "id" );
					}
				}
			}
		}
	}
	// All others
	return select( selector.replace( rtrim, "$1" ), context, results, seed );

好吧,代码看了这么多,晕头转向,现在总结一下,$(".level1 > a") 这句话实际上就是创建一个jQuery实例,然后把查找到的原始元素都添加到这个jQuery类数组里,它的属性如下图所示:
在这里插入图片描述

一共找到了3个a元素,所以都加到这里面了。

接下来看一下$(".level1 > a").click()这个方法调用做了什么。在deprecated/event.js最后几行,可以看到如下代码:

jQuery.each(
	( "blur focus focusin focusout resize scroll click dblclick " +
	"mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " +
	"change select submit keydown keypress keyup contextmenu" ).split( " " ),
	function( _i, name ) {
		// Handle event binding
		jQuery.fn[ name ] = function( data, fn ) {
			return arguments.length > 0 ?
				this.on( name, null, data, fn ) :
				this.trigger( name );
		};
	}
);

在这里把常用的事件都给加到了jQuery原型中去了,这样就变成了实例方法了,类似这样jQuery.fn[“click”]。
根据参数的不同,调用this.on或者this.trigger,这2个方法都定义在event.js或者event/trigger.js中,比较好找。
根据当前的调用,我们给click传入了一个回调函数,所以参数data = 回调函数,那么实际$(".level1 > a").click(…)就会调用this.on(“click”, null, 回调函数, undefine),接下来看一下event.js中的on方法的实现:

jQuery.fn.extend( {
	on: function( types, selector, data, fn ) {
		return on( this, types, selector, data, fn );
	},

继续看同文件中的on函数:

// elem就是我们的jQuery实例$(".level1 > a"), types="click", selector=null, data=我们传入的回调函数
// fn=undefined, one=undefined
function on( elem, types, selector, data, fn, one ) {
	var origFn, type;

	// 我们的types是字符串,不是object,跳过
	if ( typeof types === "object" ) {
	  //省略代码
	}

    // if不成立,跳过
	if ( data == null && fn == null ) {
		// ( types, fn )
		fn = selector;
		data = selector = undefined;
	} else if ( fn == null ) {//条件成立,进入
		if ( typeof selector === "string" ) {//不成立,不进入if

			// ( types, selector, fn )
			fn = data;
			data = undefined;
		} else {
			// 这个地方交换一下参数,现在fn=我们的回调函数,data=null
			fn = data;
			data = selector;
			selector = undefined;
		}
	}
	//这里的if,else if都不成立,都不执行
	if ( fn === false ) {
		fn = returnFalse;
	} else if ( !fn ) {
		return elem;
	}

    // one = undefined,不成立,跳过
	if ( one === 1 ) {
		//省略代码
	}
	//执行我们jQuery实例的each方法
	return elem.each( function() {
		jQuery.event.add( this, types, fn, data, selector );
	} );
}

上面的代码执行了elem.each方法,那么我们看看这个each方法是怎么执行的,在core.js里有定义:

// Execute a callback for every element in the matched set.
each: function( callback ) {
	return jQuery.each( this, callback );
},
...
...
//jQuery.each还在当前文件中可以找到,如下所示
// obj = this,我们的jQuery实例$(".level1 > a"),它的length是3,索引0到2分别保存了搜索到的原始的a元素
// 下面这个方法就是把每个a元素绑定到callback的this参数
each: function( obj, callback ) {
	var length, i = 0;

	if ( isArrayLike( obj ) ) {
		length = obj.length;
		for ( ; i < length; i++ ) {
			if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) {
				break;
			}
		}
	} else {
		for ( i in obj ) {
			if ( callback.call( obj[ i ], i, obj[ i ] ) === false ) {
				break;
			}
		}
	}
	return obj;
},

现在回到上面讲到的on方法的最后一行:

//执行我们jQuery实例的each方法
// 这个回调一共调用了3次,相当于这样调用
// jQuery.event.add(a/*第0个a元素*/, "click", 我们的回调函数, null, undefined);
//jQuery.event.add(a/*第1个a元素*/, "click", 我们的回调函数, null, undefined);
//jQuery.event.add(a/*第2个a元素*/, "click", 我们的回调函数, null, undefined);
return elem.each( function() {
	jQuery.event.add( this, types, fn, data, selector );
} );

再看一下event.add方法是怎样运行的。还是在当前的event.js文件中:

//我删除了很多代码,只看主要的吧
jQuery.event = {
	add: function( elem, types, handler, data, selector ) {
        //.....
		if ( !( eventHandle = elemData.handle ) ) {
		    //这是真正执行处理事件的地方,原来的回调已经被存起来了
			eventHandle = elemData.handle = function( e ) {

				// 比如用户点击菜单时,执行这个回调函数,其中我们的回调中的this绑定为了
				// 当前元素,也就是你当前点击的元素a
				return typeof jQuery !== "undefined" && jQuery.event.triggered !== e.type ?
					jQuery.event.dispatch.apply( elem, arguments ) : undefined;
			};
		}
       //.....
		while ( t-- ) {
				// Only use addEventListener if the special events handler returns false
				if ( !special.setup ||
					special.setup.call( elem, data, namespaces, eventHandle ) === false ) {
	                // jQuery包装的回调函数通过addEventListener添加到元素上了
					if ( elem.addEventListener ) {
						elem.addEventListener( type, eventHandle );
					}
				}
			}
		}

	},

当用户点击了导航栏时,我们看看回调函数里的语句是怎么执行的:

$(this).addClass("current")   //给当前元素添加"current"样式
	.next().show()                //下一个元素显示
	.parent().siblings().children("a").removeClass("current")        //父元素的兄弟元素的子元素<a>移除"current"样式
	.next().hide();                //它们的下一个元素隐藏
return false; //返回false,表示阻止默认处理,比如a元素,不会发生跳转

$(this)执行会执行init.js第98行,创建一个当前元素a的jQuery实例,这样他就可以使用jQuery实例方法了:

// HANDLE: $(DOMElement)
} else if ( selector.nodeType ) {
	this[ 0 ] = selector;
	this.length = 1;
	return this;
}

接着调用addClass()方法给元素添加css类,在attributes/classes.js中可以看到它的定义:

jQuery.fn.extend( {
	addClass: function( value ) {
		var classes, elem, cur, curValue, clazz, j, finalValue,
			i = 0;

		if ( typeof value === "function" ) {
			return this.each( function( j ) {
				jQuery( this ).addClass( value.call( this, j, getClass( this ) ) );
			} );
		}

		classes = classesToArray( value );

		if ( classes.length ) {
			while ( ( elem = this[ i++ ] ) ) {
				curValue = getClass( elem );
				cur = elem.nodeType === 1 && ( " " + stripAndCollapse( curValue ) + " " );

				if ( cur ) {
					j = 0;
					while ( ( clazz = classes[ j++ ] ) ) {
						if ( cur.indexOf( " " + clazz + " " ) < 0 ) {
							cur += clazz + " ";
						}
					}

					// Only assign if different to avoid unneeded rendering.
					finalValue = stripAndCollapse( cur );
					if ( curValue !== finalValue ) {
						elem.setAttribute( "class", finalValue );
					}
				}
			}
		}

		return this;
	},

很简单,就是通过setAttribute把这个class类加到入元素的class属性中。最后返回this,也就是元素的jQuery实例本身,这是为了方便链式调用。接下来调用next()方法,在traversing.js第120行可以找到定义:

function sibling( cur, dir ) {
	while ( ( cur = cur[ dir ] ) && cur.nodeType !== 1 ) {}
	return cur;
}
//....
next: function( elem ) {
	return sibling( elem, "nextSibling" );
},

原来就是调用元素的"nextSibling"属性,这是标准属性,直接返回下一个兄弟结点,也就是html中紧接着这个导航元素a的ul元素,这个ul元素的class是"level2",当前最后返回的就是这个ul元素对应的jQuery实例。接着调用这个jQuery实例的show()方法,其实就是设置这个元素的display属性。后面的链式调用流程都差不多,就不细说了。

jQuery对象和DOM对象的相互转换

前面我们已经知道了,每个jQuery实例都是一个类数组,原始的元素就存放在索引0时,如果是多个元素,那么会依次存放在索引0…length-1上。
例如:

const $a = $(".level1 > a");// jQuery实例,代表3个a元素
//那么我们可以依次获取这3个DOM元素a
const a1 = $a[0],a2 = $a[1],a3 = $a[2];

假如当前我们正在使用DOM元素a1,如果要把它转回jQuery对象,很简单:

const $a = $(a1);

处理和其它库的命名冲突

我们为了使用简单,一般使用$来代表jQuery函数调用,当然如果你同时引用了别的第3方库的话,记住要把jQuery.js引入放到其它3方库的后面,比如:

<!-- 引入 prototype  -->
<script src="lib/prototype.js" type="text/javascript"></script>
<!-- 引入 jQuery  -->
<script src="../../scripts/jquery.js" type="text/javascript"></script>
<script type="text/javascript">
	jQuery.noConflict();
</script>

调用noConflict()方法就可以解决冲突,这是为什么呢?例如上面的prototype.js也使用全局名字$,那我们看看noConflict()是怎么解决这个命名冲突的,查看exports/global.js :

var
    // 看下面这2行代码,jQuery覆盖同名的变量时,提前把可能第3库的名字写到了
    // _jQuery和_$变量里,这样如果在调用noConflict时,就可以把window.$恢复成原来的
    // $
	// Map over jQuery in case of overwrite
	_jQuery = window.jQuery,
	// Map over the $ in case of overwrite
	_$ = window.$; 

jQuery.noConflict = function( deep ) {
	if ( window.$ === jQuery ) {
		window.$ = _$;
	}

	if ( deep && window.jQuery === jQuery ) {
		window.jQuery = _jQuery;
	}

	return jQuery;
};

// Expose jQuery and $ identifiers, even in AMD
// (#7102#comment:10, https://github.com/jquery/jquery/pull/557)
// and CommonJS for browser emulators (#13566)
if ( typeof noGlobal === "undefined" ) {
	window.jQuery = window.$ = jQuery;
}

解决冲突之后,就不能再使用$来代表jQuery了,你可以直接使用jQuery,自己创建一个别名指向jQuery也可以。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值