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 checkmatch = [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 setreturn 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 documentnodeType = 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 = truesetDocument( 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 othersreturn 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 bindingjQuery.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=nullfn = 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绑定为了// 当前元素,也就是你当前点击的元素areturn 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 falseif ( !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也可以。

《锋利的jQuery》笔记 第1章 认识jQuery相关推荐

  1. 锋利的jQuery第2版学习笔记8~11章

    第8章,用jQuery打造个性网站 网站结构 文件结构 images文件夹用于存放将要用到的图片 styles文件夹用于存放CSS样式表,个人更倾向于使用CSS文件夹 scripts文件夹用于存放jQ ...

  2. jQuery实战读书笔记(第一章至第四章)

    2019独角兽企业重金招聘Python工程师标准>>> 第一章 jQuery 基础 1. 包装器 jQuery对包装器(Wrapper)或包装集(wrapped set)进行操作,即 ...

  3. jQuery笔记总结篇

    首先,来了解一下jQuery学习的整体思路 jQuery系统学习篇 jQuery系统学习篇-XMind源文件提供参考下载 Jquery笔记 jQuery笔记总结-XMind源文件提供参考下载 第一节 ...

  4. jQuery 笔记目录

    jQuery笔记目录 part1 part2 part3 part4 Zepto.js 这是早期的笔记,当时写笔记还没有那么有规划,所以写的比较随意,每篇文章内容相对比较少,现在复习一下jQuery, ...

  5. 【jQuery笔记Part2】03-jQuery-addBack()与end()的区别-children()与find()的区别

    前几章的方法对比与总结 示例页面:3层div css():指定元素修改属性 parrent():返回父节点(被包装成jQuery对象) addBack():额外添加调用链上一级 end():选择调用链 ...

  6. 王道考研 计算机网络笔记 第六章:应用层

    本文基于2019 王道考研 计算机网络: 2019 王道考研 计算机网络 个人笔记总结 第一章:王道考研 计算机网络笔记 第一章:概述&计算机网络体系结构 第二章:王道考研 计算机网络笔记 第 ...

  7. 王道考研 计算机网络笔记 第五章:传输层

    本文基于2019 王道考研 计算机网络: 2019 王道考研 计算机网络 个人笔记总结 第一章:王道考研 计算机网络笔记 第一章:概述&计算机网络体系结构 第二章:王道考研 计算机网络笔记 第 ...

  8. 王道考研 计算机网络笔记 第四章:网络层

    本文基于2019 王道考研 计算机网络: 2019 王道考研 计算机网络 个人笔记总结 第一章:王道考研 计算机网络笔记 第一章:概述&计算机网络体系结构 第二章:王道考研 计算机网络笔记 第 ...

  9. 王道考研 计算机网络笔记 第三章:数据链路层

    本文基于2019 王道考研 计算机网络: 2019 王道考研 计算机网络 个人笔记总结 第一章:王道考研 计算机网络笔记 第一章:概述&计算机网络体系结构 第二章:王道考研 计算机网络笔记 第 ...

最新文章

  1. Xamarin.Forms探索--使用 Xamarin.Forms 来创建跨平台的用户界面
  2. python编程小游戏-python编程游戏有哪些
  3. 软件成本度量方法及CMMI V2.0,你Get到了吗?
  4. Leetcode4-寻找两个正序数组的中位数原理及代码实现
  5. 4月27日云栖精选夜读丨阿里CMO董本洪:你要运营流量,更要运营超级用户
  6. false sharing
  7. es6笔记 day3---对象简介语法以及对象新增
  8. .Net/C#中Cache的用法
  9. 富士通成功开发全球最快的36量子位量子模拟器
  10. linux目录结构与功能_深入理解linux系统的目录结构(总结的非常详细)
  11. (原创)C++ IOC框架
  12. Flurry 统计(国际版)
  13. 【转载】DDR2 DDR3 PCBlayout规则
  14. Intelligent driver model(IDM)
  15. Android~adb卸载系统预装应用(亲测有效)
  16. layui上传图片(加大小限制)
  17. 手机麦克风权限在哪里开启_手机麦克风权限怎么设置
  18. oracle数据库修改pga,18.1.2 修改PGA
  19. matlab 地质学,大类学子有话说 | 地球科学与工程学院:探寻地球的奥秘
  20. python supper代码详解

热门文章

  1. 如何把pandas每一行数据转为一个列表
  2. selinux中Enforcing, Permissive 和Disable这三种模式的区别
  3. ansible:permissive: access permissions must restrict access to only the owner
  4. %.nf和%m.nf的区别
  5. java8函数式编程实例
  6. zdm各命令的功能和作用_ZDM快捷键(按功能分类整理)
  7. 想学习SharePoint,需要准备哪些方面的准备?--写给SharePoint新人
  8. CodeSmith基础教程
  9. C语言的文件的写入------C语言
  10. day-06 is ==小数据池编码解码