文章目录
  1. 1. jQuery的模块分类和主要依赖关系
    1. 1.1. jQuery入口模块
    2. 1.2. 底层支持模块
    3. 1.3. 功能模块
    4. 1.4. jQuery源码的总体结构
  2. 2. 源码分析
    1. 2.1. jQuery无new构建
      1. 2.1.1. 如何返回一个正常的实例呢?
      2. 2.1.2. 分隔this,避免混淆
      3. 2.1.3. 如何访问aQuery类原型上的属性方法?

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
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
(function( window,undefined ){
// 构造jQuery对象
var jQuery = function(selector,context){
return new jQuery.fn.init(selector,context,rootjQuery);
}
// 一堆局部变量声明
jQuery.fn = jQuery.prototype = {
constructor : jQuery,
init : function(selector, context, rootjQuery){...}
// 一堆原型属性和方法
}
jQuery.fn.init.prototype = jQuery.fn;
jQuery.extend = jQuery.fn.extend = function(){...}
jQuery.extend({
// 一堆静态属性和方法
});
// 工具方法 Utilities
// 回调函数列表 Callbacks object
// 浏览器功能测试 Support
// 数据缓存 Data
// 队列 Queue
// 属性操作 Attributes
// 事件系统 Events
// 选择器 Sizzle
// DOM遍历 Traversing
// DOM操作 Manipulation
// 样式操作 CSS
// 异步请求 AJAX
// 动画 Effects
// 坐标 Offset 尺寸 Dimensions
window.jQuery = window.$ = jQuery;
})(window);

源码分析

jQuery无new构建

通常我们创建一个对象或实例的方式是在运算符new后紧跟一个构造函数,如下:

1
2
3
4
5
6
7
8
9
10
var 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
2
3
4
5
6
7
8
9
10
11
12
13
14
var bQuery = function(){
this.name = "bQuery";
}
var aQuery = function(){
return new bQuery();
}
aQuery.prototype = {
name : "aQuery",
getName : function(){
return this.name;
}
};
var a = aQuery();
a.name // bQuery

在上面的示例中aQuery()返回new bQuery()的实例。如果我们把返回改成new aQuery()是不是就可以直接得到aQuery的实例了呢?答案是内存会溢出,死循环。

如何返回一个正常的实例呢?

在aQuery原型上添加一个init方法,用来返回aQuery类;

1
2
3
4
5
6
7
8
9
10
11
12
13
var 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
20
var 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
20
var 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
18
var 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
16
var 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

文章目录
  1. 1. jQuery的模块分类和主要依赖关系
    1. 1.1. jQuery入口模块
    2. 1.2. 底层支持模块
    3. 1.3. 功能模块
    4. 1.4. jQuery源码的总体结构
  2. 2. 源码分析
    1. 2.1. jQuery无new构建
      1. 2.1.1. 如何返回一个正常的实例呢?
      2. 2.1.2. 分隔this,避免混淆
      3. 2.1.3. 如何访问aQuery类原型上的属性方法?