上古利器jQuery源码剖析(一)

上古利器 虽经沧海变革 宝剑尚锋

 随着数据驱动时代的到来,jQuery不可避免的跌下神坛,但应记得它曾何等辉煌
 我们学习jQuery,不是学习它的用法,而是学习它的架构,它的思想,来深刻理解JS
 优秀的jQuery并未落伍,像当下最火着的axios/fetch等都源自对ajax的吸纳,11年就出现的异步解决方法deferred,我们在ES6的Promise里看到了它的影子
 开拔

 $ 只是一个 jQuery的一个别名
 jQuery是一个构造函数,现在可以理解为就是一个最普通的构造函数
 但是我们在调用jQuery的时候,并没有使用new关键字呢.其实这里,jQuery帮我们做了一层语法糖的封装,即调用$()的时候,它自动帮我们创建了jQuery的实例对象
 即$() 就是new $()

  * 起手式

 首先我们创造一个自执行函数,传入this,通过root来接收
 在匿名函数里 我们创建一个叫jQuery的变量,jQuery变量拿到的是一个匿名函数的引用
 这个匿名函数,我们会把它当成构造函数
 因为我们是在全局环境下传入的this,所以这里的this指的就是window对象
 给环境变量root扩展一个叫做jQuery的属性,让它指向jQuery变量,同时再给root扩展一个叫做$的别名

1
2
3
4
5
6
7
(function(root){
var jQuery = function () { // 构造函数

};
//root.jQuery = jQuery;
root.$ = root.jQuery = jQuery;
})(this); // 环境变量this 因为是全局环境 所以这里指的是window

 这个时候我们输出$,得到的会是一个匿名函数,那源码是如何将其这个语法糖封装的?
 我们给jQuery的原型对象封装一个init的方法
 我们会把这个init对象当做一个构造函数
 我们如何把这个init方法当做一个构造函数呢
 在ES5中构造函数本身就是一个函数对象,也就是说,构造函数与我们普通创建的函数是完全一样的(在其他语言中绝非如此,JS中为了得以区分我们都会将构造函数首字母大写)
 一个普通函数,如果我们通过new去创建它的实例,那么我们就把它当做构造函数,并给他指明原型对象
 我们将init方法的原型对象指向为jQuery的原型对象
 init是jQuery.prototype的一个方法,init的原型对象又是jQuery.prototype,那么init拥有了jQuery.prototype的所有方法(包括它自己)
 我们将jQuery返回init的实例new jQuery.prototype.init()

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
(function(root){
var jQuery = function () { // 别忘了 jQuery我们也是当做构造函数的
return new jQuery.prototype.init(); // 返回构造函数init的实例对象
};

jQuery.prototype = {
init: function(){ // init是一个匿名对象,在这里我们将它当做一个构造函数
console.log(111); // 测试代码
},
};

jQuery.prototype.init.prototype = jQuery.prototype; // init的原型对象指向为jQuery的原型原型对象

root.$ = root.jQuery = jQuery;
})(this);

 设计的思路是这样的,我们有两个函数jQuery和init,$()就等同于实例化jQuery即new jQuery(),但们无法直接让jQuery函数返回new jQuery,这样会形成死循环,于是我们实例化了另一个构造函数init
 我们将init方法的原型也指向了jQuery的方法原型,实现了原型共享
 因此,jQuery.prototype原型对象里封装的所有属性和方法,jQuery与init都是可以调用的
 最反思维的地方就在于了,init是Query.prototype原型对象的一个方法,可以想想计算机的自举~
 此时我们再调用$(),将输入111

* 工具函数

 jQuery框架非常重要的一个函数-工具函数,它的作用是将jQuery所有的自身对象或者实例对象的属性和方法,全部都剥离开来,进行分类管理
 jQuery中一共有两个对象,一个是实例对象,一个是jQuery本身的对象
 起手式中我们实现了实例对象,而jQuery除了是一个构造函数,它也是一个函数对象,我们也有很多函数对象是挂载到jQuery本身上的,例如$.ajax/$.each等
 我们给jQuery添加一个静态属性fn,我们让fn等于jQuery的原型对象,这样我们在外部扩展时,至需要扩展jQuery.fn即可

1
2
3
4
5
6
7
8
9
10
11
12
13
14
(function(root){
var jQuery = function () {
return new jQuery.prototype.init();
};

jQuery.fn = jQuery.prototype = { // jQuery的静态方法等于jQuery的原型对象,原型对象也是对象
init: function(){},
};

jQuery.fn.init.prototype = jQuery.fn; // 替换

root.$ = root.jQuery = jQuery;

})(this);

 外部扩展时,只需要$.fn.新方法名 = function(){} ->插件编写的形式
 通过调用静态属性扩展到jQuery的实例对象上

* extend

 extend在jQuery函数内部是作为一个工具函数存在,用它来管理不同对象(jQuery本身以及它的原型对象)的扩展
 extend在外部可以给 ①任意对象扩展/浅拷贝/深拷贝 ②jQuery本身扩展 ③jQuery实例对象扩展

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
(function(root){
var jQuery = function () {
return new jQuery.prototype.init();
};
jQuery.fn = jQuery.prototype = {
init: function(){},
};


// jQuery与 jQuery.prototype的原型都扩展了一个extend属性
jQuery.extend = jQuery.prototype.extend = function () {
// 方法调用时,我们有可能给任何对象/jQuery对象/jQuery原型对象进行扩展
// 我们通过参数来进行区分
// 参数1 是我们要扩展的对象 参数2 是我们要变量的对象
// 分析 有1个参数 可能给jQuery对象/jQuery原型对象扩展 有2个及以上 给任意对象arguments[0]扩展

var target = arguments[0] || {}; // 参数处理: 参数为空? 赋值对象不是引用类型?
var length = arguments.length; // 参数个数
var i = 1; // 默认参数个数
var option;
var name;

if(typeof target !== "object"){
target = {};
}

// 1个参数
if(length == i){

}

// 2个及以上参数
for(;i<length;i++){
if((option = arguments[i]) !== null){
for(name in option){

}
}
}
};

jQuery.fn.init.prototype = jQuery.fn;
root.$ = root.jQuery = jQuery;

})(this);