上一篇博客大体了解了jQuery整个的架构,即分成jQuery入口模块,底层支持模块以及功能模块这三个大块,并且各个模块之间有一定的联系;下来主要理解了jQuery入口模块,即jQuery对象是如何创建的,其实真正创建的jQuery实例是由jQuery.fn.init()函数创建的,并且为了使得创建的每个对象实例都拥有jQuery对象的属性和方法,即所有jQuery对象共享一份属性和方法代码,这样可大大减少所需的内存空间,利用了原生JS原型链的概念,并使得jQuery.fn.init.prototype = jQuery.fn = jQuery.prototype。
这一次主要想重点理解init()方法中是如何来创建jQuery对象的。我们使用过jQuery库的都知道如何将DOM元素转换成jQuery对象,如何创建一个含有HTML片段的jQuery对象,如何获取到id为‘id’的jQuery对象,一个简单的$(DOM元素)、$("<div></div>")和$("#id")
就可以实现。这些都是init这一个函数解决的,可见,init函数里面的逻辑太庞大。下来一点一点的缕清它。
jQuery入口模块
想想我们用jQuery时,最终结果是创建了一个jQuery对象的常用方式有哪些?
我能想到的就是:
//参数为函数,ready方法,等价于$().ready(function(){})
$(function(){
})
//参数为各种选择器
$('#id');$(''.class'');$(各种选择器);
//参数为body
$('body');
//参数为HTML代码片段
$('<div></div>'); $('<img src = '' />');
//参数为DOM元素,即将DOM元素转换为jQuery对象;或者是一个包含多个DOM元素的数组
var node = document.getELementById("id");
var nodeArray = document.getElementsByTagName("div");
$(node);
$(nodeArray);
上面是我们利用jQuery常用的一些操作,参考了“jQuery技术内幕:深入解析jQuery架构设计与实现原理” 高云”这本书之后,里面对$()的用法还有一些补充,比如:
下面就是针对不同的参数去进行不同的操作,相当于函数的重载。
上面也可以看出参数为选择器,HTML片段或者为’body’,这些都是字符串形式,所以大方向上可以是分成下面这几种情况:
if(!selector) //参数为空
{
return this;
}
if(jQuery.isFunction(selector)) //参数为函数
{
//调用jQuery的ready方法
return jQuery.ready(selector);
}
if(typeof selector === 'string') //参数为字符串
{
//里面包含了选择器,HTML片段,'body'等情况
return this;
}
if( selector.nodeType ) //参数为DOM元素或者DOM元素构成的数组
{
return this;
}
if( selector.selector !== 'undefined' ) //参数为jQuery对象
{
return this;
}
//当上面情况都没有return时,即其他情况
return jQuery.makeArray(selector, this);
整个init函数的大体逻辑就是这样,在这里我们看到了几个jQuery静态的方法,即直接挂载在jQuery 上面的方法,而不是jQuery对象上的方法。如
上面的jQuery.isFunction(selector);
jQuery.makeArray(selector, this);
这类方法算是jQuery的底层支持模块里的工具方法了。如下图:
图片来源:
jQuery底层支持模块-工具方法
可以利用源码查看工具来看工具方法的源码:地址
工具方法ifFunction()
对于isFunction这个方法:
代码很少,只有一行,但是又去调了另外一个工具方法type,由这一句就可以大概知道type的作用是判断参数的类型的,下面是type的源码:
这个代码也同样很短,但是它涵盖的内容却是极其庞大的,在jQuery中用到了很多技巧。
首先是&&,||逻辑。
我们平时用到的&& 、||,主要是if判断时,将多个条件的关系用&& 或者||来组合,但在jQuery中,却更多的是用他们来表达值。比如:
var s = num && num1; //如果num存在,并为真,还要判断num1的值,s就为num1的值;如果num不存在或者为假,不需要判断num1,s 就是num的值;
var s1 = num2 || num3; //同理:如果num2存在,并为真,不需要后面计算num3,s就为num2的值;如果num不存在或者为假,还要判断num3,s 就是num3的值;
这种的好处就是省去了if代码去判断,省了不少代码量,另一方面,应该也可以快速的执行。
另一个就是 jQuery中的钩子机制。
在type 这个方法中,引用了class2type这个数组,我们先来看看这个数组是什么样子。
class2type[ ‘[object Boolean]’ ] = ‘boolean’;
class2type[ ‘[object String]’ ] = ‘String’;
class2type[ ‘[object Number]’ ] = ‘Number’;
class2type[ ‘[object Function]’ ] = ‘function’;
class2type[ ‘[object Array]’ ] = ‘array’;
class2type[ ‘[object Date]’ ] = ‘date’;
class2type[ ‘[object RegExp]’ ] = ‘regexp’;
class2type[ ‘[object Object]’ ] = ‘object’;
为什么要这么做呢?如果不这么做,我们要去判断是哪一类,就需要if分支去判断,这样一步步去判断必然会导致比较慢。
而利用事先做的这个类似于表的对应关系,可以省去很多if-else操作。这就是jQuery中的钩子机制的一种,即表驱动方式。这只是钩子机制的一个小小的技巧,在别的地方还有很多其他的用法。
下来,判断类型主要有这么几种情况:
基本数据类型:null,undefined, boolean, string,number
引用数据类型:Object, Function
对于基本数据类型,typeof 就可以得到类型;
而对于Object类型, typeof并不能准确的判断出对象的具体类型,准确的方法是调用Object.prototype.toString.call()方法。
而对于Function类型,既可以用typeof操作得到,也可以调Object.toString()方法。源码如下:
这里先判断了null 和undefined类型,之后主要分成object、function类型和基本数据类型。
return null == a ? a+"" : typeof a == 'object' || typeof a == 'function' ? class2Type[toString.call(a)] || 'object' : typeof a;
工具方法makeArray
该方法主要是将伪数组对象转换成数组对象。
function (array, results) {
var ret = results || [];
if (array != null) { //array不为空、undefined时
// The window, strings (and functions) also have 'length'
// Tweaked logic slightly to handle Blackberry 4.7 RegExp issues #6930
var type = jQuery.type(array); //得到array的类型
if (array.length == null || type === "string" || type === "function" || type === "regexp" || jQuery.isWindow(array)) { //如果array不为数组,也不是对象(伪数组)
push.call(ret, array); //调数组的push方法
} else {
jQuery.merge(ret, array); //调jQuery的工具方法merge,该方法主要是将两个数组合并并保存到第一个数组里面
}
}
return ret;
}
jQuery.merge方法
该方法主要是将两个数组进行合并并将结果保存到第一个里面。举两个例子:
var arr1 = ['12', 'sd', '34'];
var arr2 = ['ad', '12', 'df'];
$.merge(arr1, arr2); //结果为["12", "sd", "34", "ad", "12", "df"]
var t = {
0: 'li',
1: 'li',
length: 2
};
$.merge(t, arr1); //
下面是merge函数的源码:
function (first, second) {
var i = first.length,
j = 0;
if (typeof second.length === "number") { //伪数组,对象,必须有length属性,并且length属性为Number类型的值
for (var l = second.length; j < l; j++) {
first[i++] = second[j];
}
} else {
while (second[j] !== undefined) {
first[i++] = second[j++];
}
}
first.length = i;
return first;
}
这里,就先总结这么多,感觉还是要好好理解才能深入下去,不过jQuery源码再怎么说也是原生JS,只要能将原生JS的基础理解和掌握,jQuery源码还是可以看明白的。另外一点,jQuery源码里面的很多思想都是值得学习的。