jQuery总体架构
jQuery的模块分类和主要依赖关系
jQuery入口模块
构建jQuery对象 jQuery()
参见: 选择器 Sizzle
底层支持模块
工具方法 Utilities
提供一些编程辅助方法,用于简化对jQuery对象,DOM元素,数组,对象,字符串等的操作。其他所有的模块都会用到方法模块。
参见: 功能模块
浏览器功能测试 Support
针对不同浏览器功能和bug的测试结果,其他模块基于这些测试结果来解决浏览器之间的兼容问题。
参见: 功能模块
回调函数列表 Callbacks Object
增强对回调函数的管理,支持添加、移除、触发、锁定、禁用回调函数等功能。
参见: 异步队列 Deferred Object
异步队列 Deferred Object
解耦异步任务和回调函数,它在回调函数列表的基础为回调函数增加了状态,并提供了多个回调函数列表,支持传播任意同步或异步回调函数的成功或失败状态。
参见: 回调函数列表 Callbacks Object, 异步请求 Ajax
数据缓存 Data
为DOM元素和Javascript对象附加任意类型的数据。
参见: 队列 Queue, 事件系统 Events
队列 Queue
管理一组函数,支持函数的入队和出队操作,并确保函数按顺序执行,它基于数据缓存模块实现。
参见: 数据缓存 Data, 动画 Effects
选择器 Sizzle
纯javascript实现的CSS选择器,用于查找与选择器表达式匹配的元素集合
参见: 构建jQuery对象 jQuery()
功能模块
参见: 工具方法 Utilities, 浏览器功能测试 Support
属性操作 Attributes
对HTML属性和DOM属性进行读取、设置和移除操作
事件系统 Events
提供统一的事件绑定、响应、手动触发和移除机制,它并没有将事件直接绑定到DOM元素上,而是基于数据缓存来管理事件
参见: 数据缓存 Data
DOM遍历 Taversing
在DOM树中遍历父元素、子元素和兄弟元素
DOM操作 Manipulation
插入、移除、复制和替换DOM元素
样式操作 CSS
计算样式 & 内联样式;
坐标 Offset(读取或设置DOM元素的文档坐标);
尺寸 Dimensions(获取DOM元素的高度和宽度)
异步请求 Ajax
基于异步队列模块来管理和触发回调函数
参见: 异步队列 Deferred Object
动画 Effects
基于队列模块来管理和执行动画函数
参见: 队列 Queue
jQuery源码的总体结构
1 | (function( window,undefined ){ |
源码分析
jQuery无new构建
通常我们创建一个对象或实例的方式是在运算符new后紧跟一个构造函数,如下:1
2
3
4
5
6
7
8
9
10var aQuery = function(){
}
aQuery.prototype = {
name : "aQuery",
getName : function(){
return this.name;
}
};
var a = new aQuery();
a.getName // aQuery
但是jQuery并没有使用new运算符将jQuery显示实例化。而是直接调用其函数1
jQuery(); // 并非new jQuery();
要实现jQuery()返回类的实例,可以这样操作:
1 | var bQuery = function(){ |
在上面的示例中aQuery()返回new bQuery()的实例。如果我们把返回改成new aQuery()是不是就可以直接得到aQuery的实例了呢?答案是内存会溢出,死循环。
如何返回一个正常的实例呢?
在aQuery原型上添加一个init方法,用来返回aQuery类;1
2
3
4
5
6
7
8
9
10
11
12
13var aQuery = function(){
return aQuery.prototype.init();
}
aQuery.prototype = {
init:function(){
return this;
},
name : "aQuery",
getName : function(){
return this.name;
}
};
console.log(aQuery());
当执行aQuery()返回的实例:
很明显aQuery()返回的是aQuery类的实例,那么在init中的this其实也是指向的aQuery类的实例,但是这样的实现存在这样一个问题,因为this是共享的,所以当一个实例改变this上的属性时,另一个实例对象相同的属性也会被改变。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20var aQuery = function(){
return aQuery.prototype.init();
}
aQuery.prototype = {
init:function(){
this.name="bQuery";
return this;
},
name : "aQuery",
getName : function(){
return this.name;
},
setName :function(name){
this.name = name;
}
};
var a = aQuery();
var b = aQuery();
a.setName("cQuery");
b.getName() // cQuery
如何解决这个问题呢?
分隔this,避免混淆
通过实例init函数,每次都构建新的init实例对象,来分隔this,避免交互混淆1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20var aQuery = function(){
return new aQuery.prototype.init();
}
aQuery.prototype = {
init:function(){
this.name="bQuery";
return this;
},
name : "aQuery",
getName : function(){
return this.name;
},
setName :function(name){
this.name = name;
}
};
var a = aQuery();
var b = aQuery();
a.setName("cQuery");
b.getName() // bQuery
这样生成的实例之间的属性就不会互相影响。
返回的实例对象是基于init新构建的实例,aQuery原型上的name,getName都不存在。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18var aQuery = function(){
return new aQuery.prototype.init();
}
aQuery.prototype = {
init:function(){
this.name="bQuery";
return this;
},
name : "aQuery",
getName : function(){
return this.name;
},
setName :function(name){
this.name = name;
}
};
var a = aQuery();
a.getName(); //a.getName is not a function
如何访问aQuery类原型上的属性方法?
1 | jQuery.fn.init.prototype = jQuery.fn; |
通过原型传递解决问题,jQuery()的原型对象覆盖了构造函数jQuery.fn.init()的原型对象,从而使构造函数jQuery.fn.init()的实例也可以访问构造函数jQuery()的原型方法和属性。因为是引用传递所以不需要担心这个循环引用的性能问题。1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16var aQuery = function(){
return new aQuery.prototype.init();
}
aQuery.prototype = {
init:function(){
this.name="bQuery";
return this;
},
name : "aQuery",
getName : function(){
return this.name;
}
};
aQuery.prototype.init.prototype = aQuery.prototype;
var a = aQuery();
a.getName();//bQuery