,四第一个jQuery.extend代码段

第一个jQuery.Extend代码中的init实现较为复杂,尝试分析init引用的函数并进行阅读。代码量有点大,于是点击所有的jQuery.Extend代码查看,发现在第二个new function之上的jQuery.Extend拓展了jQuery的ready方法,该代码段的实现比较简明易读。此段代码是jQuery.extend({..}),extend只有一个参数且为对象时,对象上的属性被拓展到了调用者jQuery身上。于是可以通过jQuery.ready等访问代码中的方法和属性。

jQuery.extend({
/*
* All the code that makes DOM Ready work nicely.
*/
isReady: false,
readyList: [],
// Handle when the DOM is ready
ready: function() {
// Make sure that the DOM is not already loaded
if ( !jQuery.isReady ) {
// Remember that the DOM is ready
jQuery.isReady = true;
// If there are functions bound, to execute
if ( jQuery.readyList ) {
// Execute all of them
for ( var i = 0; i < jQuery.readyList.length; i++ )
jQuery.readyList[i].apply( document );
// Reset the list of functions
jQuery.readyList = null;
}
}
}
});

上面的代码是一段很简明的代码。稍微阅读几遍就应该知道了这段代码所实现的功能。这段代码实现了jQuery上的ready方法,看到isReady以及其在ready方法中的使用,肯定了我前面对ready只执行一次的假设。我记得jQuery允许多次添加对ready的监听,这里对readyList:[]的定义可以看到实现的基础。但是,这里并没有向readyList中挂载函数的代码,需要在其他地方查找。

我们再次对第一段jQuery.extend中的init方法进行解读。init方法引用了each,查看each方法实现比较单纯,没有引用依赖其它的方法。each代码如下:

each: function( obj, fn, args ) {
if ( obj.length == undefined )
for ( var i in obj )
fn.apply( obj[i], args || [i, obj[i]] );
else
for ( var i = 0; i < obj.length; i++ )
fn.apply( obj[i], args || [i, obj[i]] );
return obj;
}

这里需要注意apply 与 call 的异同。fn.apply(thisObj,[]) fn.call(thisObj,arg1,arg2...)。你可以在网上搜寻apply的作用。这里的重点是,如果obj没有length属性则遍历obj的属性;如果obj有length属性,则把obj当作数组遍历。这里each实现的功能是,对于给定的obj对象进行遍历,并调用fn回调函数,fn中的this指针指向当前for循环比例的obj[key]的值。并向fn传递指定的参数arg或者each默认的参数[key,obj[key]]。

继续查看init函数实现,发现其中引用了jQuery.macros ,于是去查看jQuery.macros的定义,jQuery.macros中前半部分多时数组属性的定义,这些属性的作用不甚明了。jQuery.macros.each的定义中使用到了jQuery.className。jQuery.className定义在init函数所在的jQuery.extend代码中。返回去查看jQuery.className的定义。

className: {
add: function(o,c){
if (jQuery.className.has(o,c)) return;
o.className += ( o.className ? " " : "" ) + c;
},
remove: function(o,c){
o.className = !c ? "" :
o.className.replace(
new RegExp("(^|\\s*\\b[^-])"+c+"($|\\b(?=[^-]))", "g"), "");
},
has: function(e,a) {
if ( e.className != undefined )
e = e.className;
return new RegExp("(^|\\s)" + a + "(\\s|$)").test(e);
}
}

先看remove的定义,当不设置c参数时,直接将o对象的className属性置空。当设置了c参数时,通过正则表达式进行删除。has函数,对一个e对象(应该是元素element的缩写),检查指定的css类名a是否存在。为什么has中使用的正则表达式和remove中使用的正则表达式不同呢?add函数则将指定的类名添加到对象上。如果对象上已经存在类名则不重复添加。jQuery对CSS的草组包含修改CSS属性值和CSS类名。从这里的定义可以看出,作者把对CSS类名的操作通过className组织在了一起。原来如此啊,jQuery对CSS类名的操作是通过正则表达式啊。

返回继续查看jQuery.macros.each的代码实现,发现其中引用了jQuery.event。jQuery.event的定义也在init所在的jQuery.extend代码段中。event的简明结构如下:

event: {
// Bind an event to an element
// Original by Dean Edwards
add: function(element, type, handler) {
...
},
guid: 1,
global: {},
// Detach an event or set of events from an element
remove: function(element, type, handler) {
...
},
trigger: function(type,data,element) {
...
},
handle: function(event) {
...
},
fix: function(event) {
...
}
}

event定义不是很复杂,至少从这个简明结构图上来说。先从add函数顺序向下看,发现add函数代码挺多。于是梳理函数调用关系,fix属于被调用者。于是先从fix函数开始下手:

fix: function(event) {
if ( event ) {
event.preventDefault = function() {
this.returnValue = false;
};
event.stopPropagation = function() {
this.cancelBubble = true;
};
}
return event;
}

上面代码行数很少。但是除看之下有两个疑问:

  1. event.preventDefault 和 event.stopPropagation是否已经定义存在?
  2. this.returnValue 和 this.cancelBubble 是什么意思?

单独看fix函数的实现很难对这几段代码的作用和意义进行理解。但是我们注意到each.hadle的函数里引用了fix函数,引用代码如下:

handle: function(event) {
...
event = event || jQuery.event.fix( window.event );
...
}

如上代码的意思是,如果没有传入event的话,则使用经过fix处理的window.event。这究竟是为啥呢?也许对此有所了解的人会很快就明白这是ie下事件处理的一些区别导致的。但是,一开始我明显不属于那群知道的人,所以我选择了谷歌这个问题(Google:window.event)。非常幸运我找到了如下一篇文章:http://www.quirksmode.org/js/events_access.html

提炼文章中提到的对于我们理解这个问题的要点如下:

// W3C/Netscape
element.onclick = function (e) {alert('Event type is ' + e.type)}
// Microsoft
element.onclick = function () {alert('Event type is ' + window.event.type)}

至于你们看不看得懂,我反正是懂了 - by klvoek

原来,ie下的事件响应并不会把event当作参数传递给处理函数,而是需要通过window.event处理啊。如果你想了解更多,欢迎把上面那篇文章读透或者继续使用谷歌搜索。

那好,handle中的那段代码也是对ie的一段兼容代码啊。接下来fix中的内容就比较好把握了,抱着ie缺、ie缺的心态看如下代码:

var evtObj;
if (document.createEvent) {
// ("FF");
evtObj = document.createEvent('MouseEvents');
evtObj.initEvent('MouseEvents', true, false);
}
else if (document.createEventObject) {
// ("IE");
evtObj = document.createEventObject();
}
alert(evtObj); // 弹窗显示 event object
alert(evtObj.preventDefault); // 弹窗显示 ie下为undefined W3C兼容浏览器为 native code
alert(evtObj.stopPropagation); // 弹窗显示 ie下为undefined W3C兼容浏览器为 native code

你需要尝试在ie下和非ie下测试如上代码。这样,刚刚提到的两个问题有了答案:

  1. event.preventDefault 和 event.stopPropagation之前是否已经定义存在?

这两个为W3C中定义的事件所属的方法。第一个是阻止调用默认事件的发生,第二个是阻止事件继续触发。

  1. this.returnValue 和 this.cancelBubble 是什么意思?

第一个是ie下event的属性,可以用来取消发生的event事件。发散一下以前用来阻止默认事件响应函数执行的代码,到底那个好呢?再有可能需要对浏览器页面上的事件模型有所了解。

<a click="return false;">No Response</a>
<a click="return noResponse();">No Response</a>
function noResponse(){
return false;
}

cancelBubble是ie下对冒泡事件机制的控制,设置为false则事件不再进行后续冒泡处理。

再返回去看handle函数的实现

handle: function(event) {
if ( typeof jQuery == "undefined" ) return;
event = event || jQuery.event.fix( window.event );
// If no correct event was found, fail
if ( !event ) return;
var returnValue = true;
var c = this.events[event.type];
for ( var j in c ) {
if ( c[j].apply( this, [event] ) === false ) {
event.preventDefault();
event.stopPropagation();
returnValue = false;
}
}
return returnValue;
}

看到handle实现中的第一行代码,我的第一感觉是:jQuery不是已经定义了嘛?这样的校验是不是显得多余呢?后来,我回忆起了一段信息。在jQuery中,将所有附加的事件可能在jQuery被销毁的时候没有从元素上解绑定。于是,这一段原本看似冗余和多此一举的代码就不难理解了。如果jQuery被销毁而监听事件没有解绑定的话,后续代码没有继续执行的必要或者继续执行会引发不可预料(作者是认为为不可预料,但是作为看代码的我应该要了解大概会是什么错误。现在还不能给出准确估计,要等继续看过代码之后才能再次评估了)的错误。继续向后看代码,

  1. this.events是什么啊?
  2. 这里貌似没有定义啊。for..in循环,遍历了什么?

里面的内容大概是调用c[j]如果返回了false的话就让当前事件不再继续触发下去。有碍于this.events的定义并没有在event{}的范围内找到,所以考虑handle调用时this指针可能通过apply的形式定位到其它对象上。先对handle函数存疑,返回去从头看add函数的实现。