jQuery中的‘$‘是个啥?

时间:2024-11-19 07:17:17

jquery.js源码文件稍微收缩一下,大致看一下在html文件中引入这个js文件时,执行了什么:

( function( global, factory ) {
	"use strict";
	if ( typeof module === "object" && typeof module.exports === "object" ) {
		module.exports = global.document ?
			factory( global, true ) :
			function( w ) {
				if ( !w.document ) {
					throw new Error( "jQuery requires a window with a document" );
				}
				return factory( w );
			};
	} else {
		factory( global );
	}
} )( typeof window !== "undefined" ? window : this, function( window, noGlobal ) {
// 此处省略一万字
} );
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
'
运行

原来加载时,直接执行了一个IIFE(立即调用函数表达式),由于主要是在html环境中执行的,所以函数调用的实参就是 window,和匿名函数 function(window, noGlobal),进入函数后,if 判断无效,直接调用 factory(global),实际上也就是用参数window和undefined调用了 function(window=window, noGlobal=undefined)这个匿名函数。

接下来进入这个匿名函数看看,好家伙,竟然有小1万行代码,直接看最后倒数几行:

if ( typeof noGlobal === "undefined" ) {
	window.jQuery = window.$ = jQuery;
}
return jQuery;
  • 1
  • 2
  • 3
  • 4

前面我们传的参数noGlobal是undefined,所以进入了 if 语句,也就是说给window对象设置了2个全局属性, jQuery和$,让他们直接等于了jQuery这个对象,所以实际上window.$和就是同一个对象,也就是jQuery这个库的根对象。所以的操作都是基于这个属性开始的。

那个jQuery是个什么东西呢,再看第118行(代码经常变动,行数可能会不一样的):

jQuery = function( selector, context ) {
	return new jQuery.fn.init( selector, context );
};
  • 1
  • 2
  • 3
'
运行

原来它本质上就是一个函数,jQuery就是它的函数名,有2个参数selector,和context,我们大部分情况下使用时就是传入给selector参数了。

再看一下:

jQuery.fn = jQuery.prototype = {/*...*/};
  • 1

原来fn等于函数原型啊,通常会把实例方法定义在原型里。找到了,再找一下函数吧。在2417行:

init = jQuery.fn.init = function( selector, context, root ) {}
init.prototype = jQuery.fn;
  • 1
  • 2

init也是一个函数,但是在jQuery调用的时候,使用了new操作符,这样实际上init就是一个构造函数,我们返回了init实例,然后把这个函数的原型设置成了和一样的函数,这样init实例也具备了中所有的方法。
假设我们这样使用jQuery:

const h1 = $('h1');
  • 1

执行时,本质上等于调用了:

const h1 = new jQuery.fn.init('h1');
  • 1

咱们进去看一下init做了什么:

// 处理: $(""), $(null), $(undefined), $(false)这些调用直接返回new init()本身
if ( !selector ) {
	return this;
}
  • 1
  • 2
  • 3
  • 4

空谈误人,咱们创建一个测试html文件,来看看测试的结果吧:

<!DOCTYPE html>
<html>

<head>
	<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
	<title></title>
	<!-- 引入 jQuery -->
	<script src="../scripts/" type="text/javascript"></script>
	<script type="text/javascript">
		//等待dom元素加载完毕.
		$(document).ready(function () {
			console.log("Hello World!");
		});
	</script>
</head>

<body>
</body>

</html>
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20

chrome中打开这个文件,并且打开开发者工具,进入console面板,可以看到log输出"Hello World!":
在这里插入图片描述
顺便验证一下前面所说的jQuery, $的关系吧,把下面的代码直接粘贴到console中执行:

console.log(jQuery === $);//true
console.log($(''));// {} 对象
  • 1
  • 2

咱们接下来看一下$(document)是个什么玩意?继续读init函数的源码,收缩一下,能看到接下来要走一个if-else判断:
在这里插入图片描述
显示应该进入第一个else if分支,因为 == 9,表示Document类型,就执行了下面几条语句:

else if (selector.nodeType) {
	this[0] = selector;
	this.length = 1;
	return this;
}
  • 1
  • 2
  • 3
  • 4
  • 5

把document赋给了属性"0",并且给属性"length"赋值为1,看来,这个对象是当成了类似数组来用啊。
接下来看一下ready函数是怎么执行的,在原型里我们可以找到这个实例函数,在3342行:

var readyList = jQuery.Deferred();
jQuery.fn.ready = function (fn) {
	readyList
		.then(fn)
		.catch(function (error) {
			jQuery.readyException(error);
		});
	return this;
};
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9

其中 fn 就是我们在代码里输出"Hello World"的匿名函数,看ready函数里的内容,很像ES6新增加的Promise类型方法。那我们看看 readyList是个什么类型,readyList = (),那这个函数返回值是什么呢,我们在2971行找到了这个函数:

jQuery.extend({
		Deferred: function (func) {
			var tuples = [
  • 1
  • 2
  • 3

这个方法是扩展的方法,方法就是把Deferred方法添加到jQuery这个函数对象中,这样jQuery就可以直接调用这个方法了,看看Deferred()创建了一个什么对象:
在这里插入图片描述
好家伙,仔细看看里面的代码,它真的用ES5的方式实现了ES6中新增的Promise类型,如果你学过Promise的使用,那么就很容易理解().catch()这种调用的方式,ready()方法就是把回调函数放到事件队列里,等下一次执行的时候就会调用到它,其实想想,当前正在执行DOM加载,那么加载完成后才能继续队列中的下一个事件,显然这时候执行到回调函数的时候,DOM元素初始加载肯定完成了。但是ready()方法和肯定有区别,因为ready()中的函数只在下一次处理事件队列时执行,这个肯定不能保证图片能否加载完成。