jQuery 源码分析:当 selector 传来一个函数时,怎么进行处理?

本文章为 0.9 版本,将会在稍后润色更新。本文使用的 jQuery 版本为 3.4.0

我们知道使用 $ 操作符时,可以往里面塞很多类型的参数,字符串,对象,函数...,jQuery 会根据不同的参数类型,让我们执行不同的操作。这其实就是“函数重载”的价值所在:它暴露出一个简洁的接口给用户,允许用户在使用这个接口时,通过参数类型控制函数的行为方式,是一种对用户非常友好的设计。

那么 jQuery 在 $ 这里的函数重载是怎样实现的呢?这篇文章我们只关心其中的一个细枝末节,传入一个函数时,jQuery 会怎么做:

首先我们看这里,jQuery 首先会判断传入的 selector 是不是一个函数,这里使用的是包装后的 isFunction 函数,它基本等同于 typeof 判断:

if (isFunction(selector)) {
  return root.ready !== undefined ?
    root.ready(selector) :

    // Execute immediately if ready is not present
    selector(jQuery);
}

如果判断是一个函数,jQuery 会去判断 root.ready 的值,这里 root 是什么呢?看下面的代码:

// Method init() accepts an alternate rootjQuery
// so migrate can support jQuery.sub (gh-2101)
root = root || rootjQuery;

rootjQuery = jQuery(document);

一般情况下,rootjQuery(document) 的返回值,这里就有点绕了,因为我们本来是要解决“选择器是函数的时候,jQuery 会怎么做”的问题,现在我们先要解决“选择器是对象的时候,jQuery 会怎么做”。在源码里 jQuery 是这样处理的:

return jQuery.makeArray(selector, this);

我们越来越深入了,现在我们要解决 jQuery.makeArray 这个方法在做什么的问题,这部分的代码在这里:

makeArray: function (arr, results) {
    var ret = results || [];

    if (arr != null) {
      if (isArrayLike(Object(arr))) {
        jQuery.merge(ret,
          typeof arr === "string" ?
            [arr] : arr
        );
      } else {
        push.call(ret, arr);
      }
    }

    return ret;
  },

可以简单理解为,jQuery 会把一个对象传入到它的数组中。

所以到目前为止,我们大概弄懂了 root 到底是个什么东西,简单来说,是一个数组,并且第一个元素是 document 对象。我们继续,接下来,我们想知道的实际上是 root.ready 是什么,当我们回顾一下最初的代码就能知道,jQuery 处理函数的逻辑就是判断 root.ready 的值,如果该值为真值,就调用 root.ready 方法,并把我们的函数当做参数传进去,如果为假值,则直接调用这个函数,把我们的 jQuery 对象当做参数传进去。

对不起,接下来的重头戏我将会在日后补上了,这次我先描述一个大概,让我们看看和 root.ready 有关的源码:

// The deferred used on DOM ready
var readyList = jQuery.Deferred();

jQuery.fn.ready = function (fn) {

  readyList
    .then(fn)

    // Wrap jQuery.readyException in a function so that the lookup
    // happens at the time of error handling instead of callback
    // registration.
    .catch(function (error) {
      jQuery.readyException(error);
    });

  return this;
};

jQuery.extend({

  // Is the DOM ready to be used? Set to true once it occurs.
  isReady: false,

  // A counter to track how many items to wait for before
  // the ready event fires. See #6781
  readyWait: 1,

  // Handle when the DOM is ready
  ready: function (wait) {

    // Abort if there are pending holds or we're already ready
    if (wait === true ? --jQuery.readyWait : jQuery.isReady) {
      return;
    }

    // Remember that the DOM is ready
    jQuery.isReady = true;

    // If a normal DOM Ready event fired, decrement, and wait if need be
    if (wait !== true && --jQuery.readyWait > 0) {
      return;
    }

    // If there are functions bound, to execute
    readyList.resolveWith(document, [jQuery]);
  }
});

// ===> readyList
var readyList = jQuery.Deferred();

其实注释里也写的很清晰了,jQuery.ready 将会在 DOM 加载完毕后,调用传入的参数,但是就是这样一个简单地功能,jQuery 采用了 jQuery.Deferred() 方法去实现,这个方法到底做了什么呢?请看我(可能的)下一篇文章:)

总结一下,如果给 jQuery 传入一个函数类型的参数会发生什么?不完全准确的回答是,它会等 DOM 加载完毕后被调用。具体来说,是等待 doument 对象上的 DOMContentLoaded 事件被触发。

下面是无声的证据:

        jQuery.ready.then = readyList.then;

	// The ready event handler and self cleanup method
	function completed() {
		document.removeEventListener("DOMContentLoaded", completed);
		window.removeEventListener("load", completed);
		jQuery.ready();
	}

	// Catch cases where $(document).ready() is called
	// after the browser event has already occurred.
	// Support: IE <=9 - 10 only
	// Older IE sometimes signals "interactive" too soon
	if (document.readyState === "complete" ||
		(document.readyState !== "loading" && !document.documentElement.doScroll)) {

		// Handle it asynchronously to allow scripts the opportunity to delay ready
		window.setTimeout(jQuery.ready);

	} else {

		// Use the handy event callback
		document.addEventListener("DOMContentLoaded", completed);

		// A fallback to window.onload, that will always work
		window.addEventListener("load", completed);
	}
posted @ 2019-04-22 15:42  libinfs  阅读(206)  评论(0编辑  收藏  举报