看书时注意到下面两条语句的功效是相同的,

$(function(){alert("hello!");}); $(document).ready(function(){alert("hello!");});

这个特殊写法就是用$()代替$(document).ready(),类似于(有差异)window.onload弹出个窗口:

查看jQuery1.8.3源代码,是这样封装的:

(function(window,undefined){ /*...jQuery源代码全部都在这里了...*/})(window);

下列语句把封装在内部的jQuery先赋给window.$,紧接着再赋给window.jQuery。这意味着在实际使用时window.$和window.jQuery是一回事。因为$这个符号只有1个字母,比jQuery短,所以更常用一些,但要注意到$非jQuery所独有,节约字母的代价是增加了命名冲突的风险。

//ExposejQuerytotheglobalobject window.jQuery=window.$=jQuery;

下面是jQuery的初始化语句(注意到此时函数并未执行):

//DefinealocalcopyofjQuery jQuery=function(selector,context){ //ThejQueryobjectisactuallyjusttheinitconstructor'enhanced' returnnewjQuery.fn.init(selector,context,rootjQuery); }

找到jQuery.fn的定义,这是一个对象,其中有一个叫init的函数元素:

jQuery.fn=jQuery.prototype={ constructor:jQuery, init:function(selector,context,rootjQuery){ varmatch,elem,ret,doc; //Handle$(""),$(null),$(undefined),$(false) if(!selector){ returnthis; } //Handle$(DOMElement) if(selector.nodeType){ this.context=this[0]=selector; this.length=1; returnthis; } /*...以下省略...*/

继续下去,init中有一段逻辑:

//HANDLE:$(function) //Shortcutfordocumentready }elseif(jQuery.isFunction(selector)){ returnrootjQuery.ready(selector); }

晕了晕了,rootjQuery的定义又回到了jQuery:

//AlljQueryobjectsshouldpointbacktothese rootjQuery=jQuery(document);

有点递归的意思了,嗯,就是递归。jQuery不仅仅是一个函数,而且还是一个递归函数。

如果调用jQuery时输入的是一个函数,例如文章开头提到的:

$(function(){alert("hello!");});

那么这个函数就会走到rootjQuery那里,再回到jQuery,执行jQuery(document).ready。而$与jQuery是一回事,这样就解释了$(inputFunction)可以代替$(document).ready(inputFunction)。

现在还不想结束此文,我的问题是$(document)做了什么?嗯,还是要进入到jQuery.fn.init,确认存在nodeType属性,达到“Handle$(DOMElement)”的目的。怎么Handle呢?具体就是把输入参数(此时为document)赋值给this的context属性,然后再返回this。也就是说,$(document)执行完了返回的还是jQuery,但是情况发生了变化,具体就是context属性指向了输入参数(此时为document)。暂时还不明白绕这么大个圈子为context(上下文)属性赋值有何意义?

接下去的问题可能会是$(document).ready和window.onload的区别?提取ready函数的定义如下:

ready:function(fn){ //Addthecallback jQuery.ready.promise().done(fn); returnthis; },

阅读代码探究promise是有点晕啊,想到自己的iJs工具包了,打印jQuery.ready.promise()如下:

[Object] jQuery.ready.promise()
|--[function] always
|--[function] done
|--[function] fail
|--[function] pipe
|--[function] progress
|--[function] promise
|--[function] state
|--[function] then

进一步打印整理done函数代码如下(这下彻底晕了~~):

function(){ if(list){ //First,wesavethecurrentlength varstart=list.length; (functionadd(args){ jQuery.each(args,function(_,arg){ vartype=jQuery.type(arg); if(type==="function"){ if(!options.unique||!self.has(arg)){list.push(arg);} }elseif(arg&&arg.length&&type!=="string"){ //Inspectrecursivelyadd(arg); } }); })(arguments); //Doweneedtoaddthecallbackstothe //currentfiringbatch? if(firing){ firingLength=list.length; //Withmemory,ifwe'renotfiringthen //weshouldcallrightaway }elseif(memory){ firingStart=start;       fire(memory); } } returnthis; }

好在代码不长,看起来关键就在于fire函数了。嗯,找回一丝清醒了。在上面的done函数里面可以注意到使用了默认的arguments变量,将注入的函数push到了list数组。下面是fire函数:

fire=function(data){ memory=options.memory&&data; fired=true; firingIndex=firingStart||0; firingStart=0; firingLength=list.length; firing=true; for(;list&&firingIndex<firingLength;firingIndex++){ if(list[firingIndex].apply(data[0],data[1])===false&&options.stopOnFalse){ memory=false;//Topreventfurthercallsusingadd break; } } firing=false; if(list){ if(stack){ if(stack.length){ fire(stack.shift()); } }elseif(memory){ list=[]; }else{ self.disable(); } } }

可以看到代码中对list数组里面使用了apply。用iJs包调试可发现data[0]就是document对象,也就是说,调用$(myFunction)的结果是在document对象上执行了myFunction。因为list是个数组,所以也就不难理解$()其实是多次输入,一次执行。

最后,回过头来阅读promise源代码,关于$()输入函数的执行时机的秘密就在这里了:

jQuery.ready.promise=function(obj){ if(!readyList){ readyList=jQuery.Deferred(); //Catchcaseswhere$(document).ready()iscalledafterthebrowsereventhasalreadyoccurred. //weoncetriedtousereadyState"interactive"here,butitcausedissuesliketheone //discoveredbyChrisShere:http://bugs.jquery.com/ticket/12282#comment:15 if(document.readyState==="complete"){ //Handleitasynchronouslytoallowscriptstheopportunitytodelayready setTimeout(jQuery.ready,1); //Standards-basedbrowserssupportDOMContentLoaded }elseif(document.addEventListener){ //Usethehandyeventcallback document.addEventListener("DOMContentLoaded",DOMContentLoaded,false); //Afallbacktowindow. window.addEventListener("load",jQuery.ready,false); //IfIEeventmodelisused }else{ //Ensurefiringbefore document.attachEvent("onreadystatechange",DOMContentLoaded); //Afallbacktowindow. window.attachEvent("onload",jQuery.ready); //IfIEandnotaframe //continuallychecktoseeifthedocumentisready vartop=false; try{ top=window.frameElement==null&&document.documentElement; }catch(e){} if(top&&top.doScroll){ (functiondoScrollCheck(){ if(!jQuery.isReady){ try{ //UsethetrickbyDiegoPerini //http://javascript.nwbox.com/IEContentLoaded/ top.doScroll("left"); }catch(e){ returnsetTimeout(doScrollCheck,50); } //andexecuteanywaitingfunctions jQuery.ready(); } })(); } } } returnreadyList.promise(obj); };

从代码的注释中可以看到这段代码在消除bug的过程中还是颇费了些心思的。查看其中一个网址http://bugs.jquery.com/ticket/12282#comment:15,是关于IE9/10的一个bug(document ready is fired too early on IE 9/10),好在已经解决。

绕了这么多弯子,整个事情看起来就是这样,如果每一个浏览器都能有document.readyState==="complete",就简单了。再看到$(),要感谢编写jQuery的大神们(以及其他类似框架的大神们),是他们的努力,让世界变得完美。