jQuery-v2.0.3源码浅析02-$(function(){})

前言

上篇文章讲解了jQuery的链式操作,接下来我们来看一下在jQuery中使用最频繁的代码:

$(function(){
  ***
})

这段代码的意思是指当页面dom加载完毕之后会自动调用这个匿名函数。
我们来看一下源码是怎么实现的

源码

/**源码90行**/
completed = function() {
  document.removeEventListener( "DOMContentLoaded", completed, false );
  window.removeEventListener( "load", completed, false );
  jQuery.ready();
};
/**源码96行**/
jQuery.fn = jQuery.prototype = {
  /**源码101行**/
  init: function( selector, context, rootjQuery ) {
    ***
    /**源码184行**/
    } else if ( jQuery.isFunction( selector ) ) {
      return rootjQuery.ready( selector );
    }
  },
  /**源码239行**/
  ready: function( fn ) {
    // Add the callback
    jQuery.ready.promise().done( fn );
    return this;
  }
}
/**源码348行**/
jQuery.extend({
  /**源码381行**/
  ready: function( wait ) {
    ***
    /**源码397行**/
    readyList.resolveWith( document, [ jQuery ] );
  }
}
/**源码818行**/
jQuery.ready.promise = function( obj ) {
  if ( !readyList ) {
    readyList = jQuery.Deferred();
    if ( document.readyState === "complete" ) {
      setTimeout( jQuery.ready );
    } else {
      document.addEventListener( "DOMContentLoaded", completed, false );
      window.addEventListener( "load", completed, false );
    }
  }
  return readyList.promise( obj );
};
/**源码865行**/
rootjQuery = jQuery(document);

我们从上篇文章已经得知$()最终创建的是 jQuery.fn.init 的实例。
我们调用$(function(){})。最终init的第一个参数selector就是我们传入的回调函数。
在源码184行可以看到jQuery通过判断selector是函数又调用了rootjQuery.ready( selector )。
从源码865行可以得知rootjQuery 其实就是 jQuery(document) 等价于 $(document)。
这个时候我们就可以猜测jQuery原型下面应该挂载了一个ready方法。
当然我们的猜测是正确的 我们可以通过源码239行看到这个ready方法。
最终调用了jQuery.ready.promise().done( fn )。
我们可以从818行找到jQuery.ready.promise函数,可以看到函数最终return了一个promise对象(jQuery自定义的promise非es6中自带的Promise)。
最终调用了jQuery.ready();

相信很多刚开始看的同学可能已经看糊涂了(我一开始也搞得很迷糊),不要慌,接下来我们再来重新理一下代码的执行过程和逻辑,不懂的代码就要不断的去重新整理重新阅读。或者使用调试工具跑一遍看看。

$(function(){}) -> 
return new jQuery.fn.init( selector, context, rootjQuery );->
return rootjQuery.ready( selector );->//到这一步为止jQuery就进行了一些简单判断相信大家都能看得懂
jQuery.ready.promise() //执行该函数时候内部进行了一些判断我们来一起看一下

函数
1、第一步先判断readList是否存在,可以通过源码26行看到定义了readList并没有赋值,所以第一次执行该函数的时候条件成立。然后给readyList赋值为一个 Deferred 对象(后续会介绍Deferred的实现原理)

2、接着又判断了document.readyState === "complete" 这一步的作用是,有可能当执行到这段代码的时候dom已经加载完毕了,这个时候document.readyState的值为‘complete’。这个时候其实我们就可以直接调用回调函数了,这里jQuery利用setTimeout调用了jQuery.ready。else 就是来处理当dom还没加载完毕的逻辑。我们发现jQuery监听了document 的 ‘DOMContentLoaded’ 和 window的‘load’ 两个事件监听(兼容IE老版本有的时候load事件会比DOMContentLoaded来的快)。jQuery为了确保第一时间执行回调函数所以监听了两个事件。然后最后返回promise对象。

3、接下里我们来看下completed函数,先清除了两个监听事件(防止重复调用)然后再最终也调用了jQuery.ready

4、最后我们来看一下jQuery.ready函数,先忽略其他代码看到397行,我们可以看到readyList.resolveWith来最终执行了回调函数。也就是自己传入的匿名函数。当然这块匿名函数是通过
jQuery.ready.promise().done( fn );传入的。

所以最终可以把

解释

可能部分同学没有使用过jQuery的Deferred方法,接下来简单介绍一下Deferred方法的使用,后续再对源码进行详细剖析。

var def = $.Deferred()
var promise = def.promise();
promise.done(function(){
  console.log(1);
});
setTimeout(function(){
  def.resolveWith( document );
}, 3000)

这段代码会在3秒之后在控制台打印出1。
dfe大家可以先理解成改状态用的,promise大家可以理解成添加回调方法用的。(后续介绍延迟对象的时候会详解)
done可以看成是添加成功回调函数的方法(对应也有添加失败的方法)。在resolve或者resolveWith会依次执行(resolve和resolveWith的区别是resolveWith可以改变函数执行时候的this指向,resolve最终调用的其实还是resolveWith)。
resolveWith其实就是改变状态(可以看成会循环触发done里面添加的函数)

其他

/**源码400行**/
if ( jQuery.fn.trigger ) {
  jQuery( document ).trigger("ready").off("ready");
}

//以上代码处理的是通过事件形式绑定的回调,例如:

$(document).on('ready', function(){
  ***
})

再看jQuery.ready方法的其他判断代码:

/**源码372行**/
holdReady: function( hold ) {
  if ( hold ) {
    jQuery.readyWait++;
  } else {
    jQuery.ready( true );
  }
}
/**源码384行**/
if ( wait === true ? --jQuery.readyWait : jQuery.isReady ) {
  return;
}
jQuery.isReady = true;
if ( wait !== true && --jQuery.readyWait > 0 ) {
  return;
}

以上代码处理的是配合$.holdReady(true)来使用的,有的时候我们想在dom加载完毕之后不立即触发回调函数可以通过调用该代码来实现。
其实底层实现就是通过类似计数器方式来实现,利用isReady来存储dom加载状态,计数器的变量名称定义为readyWait
调用$.holdReady(true)就把计数器+1,不传参数或者传false就调用jQuery.ready( true )来让计数器-1,当计数器减到0就执行回调函数。

你可能感兴趣的:(jQuery-v2.0.3源码浅析02-$(function(){}))