看书时注意到下面两条语句的功效是相同的,
- $(function(){alert("hello!");});
- $(document).ready(function(){alert("hello!");});
这个特殊写法就是用$()代替$(document).ready(),类似于(有差异)window.onload弹出个窗口:
查看jQuery1.8.3源代码,是这样封装的:
- (function( window, undefined ) {
-
- })( window );
下列语句把封装在内部的jQuery先赋给window.$,紧接着再赋给window.jQuery。这意味着在实际使用时window.$和window.jQuery是一回事。因为$这个符号只有1个字母,比jQuery短,所以更常用一些,但要注意到$非jQuery所独有,节约字母的代价是增加了命名冲突的风险。
-
- window.jQuery = window.$ = jQuery;
下面是jQuery的初始化语句(注意到此时函数并未执行):
-
- jQuery = function( selector, context ) {
-
- return new jQuery.fn.init( selector, context, rootjQuery );
- }
找到jQuery.fn的定义,这是一个对象,其中有一个叫init的函数元素:
- jQuery.fn = jQuery.prototype = {
- constructor: jQuery,
- init: function( selector, context, rootjQuery ) {
- var match, elem, ret, doc;
-
-
- if ( !selector ) {
- return this;
- }
-
-
- if ( selector.nodeType ) {
- this.context = this[0] = selector;
- this.length = 1;
- return this;
- }
-
继续下去,init中有一段逻辑:
-
-
- } else if ( jQuery.isFunction( selector ) ) {
- return rootjQuery.ready( selector );
- }
晕了晕了,rootjQuery的定义又回到了jQuery:
-
- 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 ) {
-
- jQuery.ready.promise().done( fn );
-
- return this;
- },
阅读代码探究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 ) {
-
- var start = list.length;
- (function add( args ) {
- jQuery.each( args, function( _, arg ) {
- var type = jQuery.type( arg );
- if ( type === "function" ) {
- if ( !options.unique || !self.has( arg ) ) { list.push( arg ); }
- } else if ( arg && arg.length && type !== "string" ) {
-
- }
- });
- })( arguments );
-
-
- if ( firing ) {
- firingLength = list.length;
-
-
- } else if ( memory ) {
- firingStart = start;
- fire( memory );
- }
- }
- return this;
- }
好在代码不长,看起来关键就在于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;
- break;
- }
- }
- firing = false;
- if ( list ) {
- if ( stack ) {
- if ( stack.length ) {
- fire( stack.shift() );
- }
- } else if ( 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();
-
-
-
-
- if ( document.readyState === "complete" ) {
-
- setTimeout( jQuery.ready, 1 );
-
-
- } else if ( document.addEventListener ) {
-
- document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false );
-
-
- window.addEventListener( "load", jQuery.ready, false );
-
-
- } else {
-
- document.attachEvent( "onreadystatechange", DOMContentLoaded );
-
-
- window.attachEvent( "onload", jQuery.ready );
-
-
-
- var top = false;
-
- try {
- top = window.frameElement == null && document.documentElement;
- } catch(e) {}
-
- if ( top && top.doScroll ) {
- (function doScrollCheck() {
- if ( !jQuery.isReady ) {
-
- try {
-
-
- top.doScroll("left");
- } catch(e) {
- return setTimeout( doScrollCheck, 50 );
- }
-
-
- jQuery.ready();
- }
- })();
- }
- }
- }
- return readyList.promise( obj );
- };
从代码的注释中可以看到这段代码在消除bug的过程中还是颇费了些心思的。查看其中一个网址,是关于IE9/10的一个bug(document ready is fired too early on IE 9/10),好在已经解决。
绕了这么多弯子,整个事情看起来就是这样,如果每一个浏览器都能有document.readyState === "complete",就简单了。再看到$(),要感谢编写jQuery的大神们(以及其他类似框架的大神们),是他们的努力,让世界变得完美。
本文转自 hexiaini235 51CTO博客,原文链接:http://blog.51cto.com/idata/1119589,如需转载请自行联系原作者