这篇文章将主要介绍jQuery框架的前面几百行代码并说明jQuery框架的整体结构。

1.0 源码解读

这里先简单贴出jQuery框架3.3.1版本中的前600行代码,其它和整体结构无关的部分省略了。

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
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
* jQuery JavaScript Library v3.3.1
* https://jquery.com/ 官网地址
*
* Includes Sizzle.js
* https://sizzlejs.com/ 核心选择器
*
* Copyright JS Foundation and other contributors
* Released under the MIT license 开源协议
* https://jquery.org/license 开源协议地址
*
* Date: 2018-01-20T17:24Z 更新(发布)时间
//jQuery的外城结构是一个闭包(即时调用函数)
//整体结构可以抽象为(fn)(....)
( function( global, factory ) {
"use strict"; //开启严格模式
//判断的当前的环境是否是CommonJs
//说明:CommJs 环境中会有一个 module 对象,这个对象上会有一个 exports 对象
if ( typeof module === "object" && typeof module.exports === "object" )
{
// For CommonJS and CommonJS-like environments where a proper `window`
// is present, execute the factory and get jQuery.
// For environments that do not have a `window` with a `document`
// (such as Node.js), expose a factory as module.exports.
// This accentuates the need for the creation of a real `window`.
// e.g. var jQuery = require("jquery")(window);
// See ticket #14549 for more info.
//如果在 CommonJs 环境下,那么将 jQuery 对象挂载到 module.exports 对象
//factory函数的第二个参数传递为true,表示将不会在window上注册jQuery对象
//因为可能运行在非浏览器下,所有对global.document进行检查
//如果global.document有值,那么调用factory( global, true )得到结果赋值给module.exports
//如果global.document没有值(在非浏览器环境下),我们没有window对象,那么就需要自己创建了一个模拟浏览器的环境
//传入自己的 window 对象,这种情况下 jQuery 对象将绑定到你传入的这个特殊的 window 对象上
module.exports = global.document ?
factory( global, true ) :
function( w ) {
if ( !w.document ) {
//如果没有document,那么就抛出错误信息
throw new Error( "jQuery requires a window with a document" );
}
return factory( w );
};
}
else
{
//如果不是在 CommonJs 环境下,直接执行工厂函数
//调用函数的时候传递一个参数(window|this),第二个参数没有传值,默认为undefined
factory( global );
}
// Pass this if window is not defined yet
//实参说明:第一个参数(global)的值为window 或者 是当前上下文this,第二个参数为函数
} )( typeof window !== "undefined" ? window : this, function( window, noGlobal )
{
// Edge <= 12 - 13+, Firefox <=18 - 45+, IE 10 - 11, Safari 5.1 - 9+, iOS 6 - 9.1
// throw exceptions when non-strict code (e.g., ASP.NET 4.5) accesses strict mode
// arguments.callee.caller (trac-13335). But as of jQuery 3.0 (2016), strict mode should be common
// enough that all such attempts are guarded in a try block.
//开启严格模式
"use strict";
//声明变量
var arr = [];
//获取document文件
var document = window.document;
//保存Object对象上获取原型对象的方法
var getProto = Object.getPrototypeOf;
//保存数组中截取元素的方法
var slice = arr.slice;
//保存数组中合并数组的方法
var concat = arr.concat;
//保存数组中添加元素的方法
var push = arr.push;
//保存数组中返回元素索引的方法
var indexOf = arr.indexOf;
//初始化空的对象
var class2type = {};
//保存{}.toString方法,其实是Object.prototype.toString方法
//该方法用于获取指定对象的类型和真实构造函数 ex: [object String]
var toString = class2type.toString;
//保存检查是否是实例成员的方法
var hasOwn = class2type.hasOwnProperty;
//保存 Object.prototype.hasOwnProperty.toString方法
var fnToString = hasOwn.toString;
//保存 Object.prototype.hasOwnProperty.toString.call(Object) 方法
// ==> "function Object() { [native code] }"
var ObjectFunctionString = fnToString.call( Object );
// 初始化空的对象
var support = {};
// 工具函数:检查传入的对象是否是函数类型的
var isFunction = function isFunction( obj ) {
// Support: Chrome <=57, Firefox <=52
//浏览器的支持情况
// In some browsers, typeof returns "function" for HTML <object> elements
// 在很多的浏览器中对HTML对象节点执行typeof会返回 function
// (i.e., `typeof document.createElement( "object" ) === "function"`).
// 在ie中typeof document.createElement( "object" )的结果为function
// We don't want to classify *any* DOM node as a function.
// 在检查函数的时候排除任何的DOM节点
// 注:DOM节点均拥有nodeType属性,属性值为number类型,根据具体的数值不同来进行区分 1为元素节点 2为属性节点
return typeof obj === "function" && typeof obj.nodeType !== "number";
};
//工具函数:检查传入的参数是否是window
var isWindow = function isWindow( obj ) {
//检查window的方式 window = window.window 即window对象本身拥有window属性来标名自己是window
//null不能拥有任何的属性,排除null
return obj != null && obj === obj.window;
};
//保存script属性的字面量对象:类型|资源|noModule
var preservedScriptAttributes = {
type: true,
src: true,
noModule: true
};
// 该方法作为 $.globalEval();方法的内部实现,作用类似于js原生的eval方法
// $.globalEval( "var a = 1;" );方法其实就是调用 DOMEval("var a = 1;");
function DOMEval( code, doc, node ) {
//如果doc没有值,那么初始化为document
doc = doc || document;
//创建空的script标签
var i,
script = doc.createElement( "script" );
//设置script标签的文本内容
script.text = code;
if ( node ) {
//循环preservedScriptAttributes对象
//把node节点中的type && src && noModule拷贝给script标签
for ( i in preservedScriptAttributes ) {
if ( node[ i ] ) {
script[ i ] = node[ i ];
}
}
}
//doc.head 表示访问页面中的header头部标签
//把先创建的script标签插入到页面然后删除掉
doc.head.appendChild( script ).parentNode.removeChild( script );
}
// 获取参数对应的数据类型
function toType( obj ) {
// 如果参数是null,那么就返回"null"
if ( obj == null ) {
return obj + "";
}
// Support: Android <=2.3 only (functionish RegExp)
// 如果typeof的结果为object 或者是function 那么就通过class2type[ toString.call( obj ) ]方法计算
// class2type[ toString.call( obj ) ] 其实就是Object.prototype.toString.call(obj) 形式
// 如果toString方法计算的结果为false,那么就返回object,否则返回typeof obj的值
return typeof obj === "object" || typeof obj === "function" ?
class2type[ toString.call( obj ) ] || "object" :
typeof obj;
}
/* global Symbol */
// Defining this global in .eslintrc.json would create a danger of using the global
// unguarded in another place, it seems safer to define global only for this module
var
// 当前版本
version = "3.3.1",
// Define a local copy of jQuery 定义jQuery的本地副本
// jQuery工厂函数的定义(声明)
// 参数1 : 选择器
// 参数2 : 上下文对象
jQuery = function( selector, context ) {
// The jQuery object is actually just the init constructor 'enhanced'
// Need init if jQuery is called (just allow error to be thrown if not included)
// jQuery.fn,init 作为构造函数,这里返回的其实是jQuery.fn.init构造函数的实例化对象
// 调用jQuery函数的时候其实是以构造函数的方式调用 xxx...init函数
return new jQuery.fn.init( selector, context );
},
// Support: Android <=4.0 only
// Make sure we trim BOM and NBSP
// 确保对BOM和NBSP的处理,清除字符串开始和结尾的一个或多个空格的正则表达式
rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g;
//设置jQuery的原型对象,并把原型对象赋值给jQuery.fn
jQuery.fn = jQuery.prototype = {
//这里列出了一部分jQuery原型成员(属性和方法)
//所有的jQuery实例对象都能够访问这些属性和方法
// The current version of jQuery being used
//当前正在使用的jQuery版本信息
jquery: version,
//构造器属性 ---> jQuery
constructor: jQuery,
// The default length of a jQuery object is 0
//jQuery实例对象中数据的个数,默认长度为0
length: 0,
// 把jQuery实例对象转换为数组类型的方法
toArray: function() {
//其实调用的是Array.prototype.slice.call(this) 方法 相当于this.slice()
//在数组的slice方法中,如果不接受参数则表示截取所有的元素保存到一个新的数组中返回
return slice.call( this );
},
// Get the Nth element in the matched element set OR
// Get the whole matched element set as a clean array
// 获取jQ对象中指定索引对应的数据(通常为DOM节点)
get: function( num ) {
// Return all the elements in a clean array
//如果没有传递参数,那么等价于调用了toArray方法
//把当前jQ对象中所有的value值保存到数组中返回
if ( num == null ) {
return slice.call( this );
}
// Return just the one element from the set
// 区分索引值的情况
// 如果索引值为负数那么 返回this[index + this.length]
// 如果索引值> = 0 返回thus[index]
return num < 0 ? this[ num + this.length ] : this[ num ];
},
// Take an array of elements and push it onto the stack
// (returning the new matched element set)
// 维护堆栈集合 把传入的数据包裹成一个新的jQ对象,然后更新prevObject的值为上一个(this)对象
pushStack: function( elems ) {
// Build a new jQuery matched element set
// merge方法用于合并两个数组
// this.constructor() 其实就是this.jQuery(); 得到的是一个空的jQ实例对象
//
var ret = jQuery.merge( this.constructor(), elems );
// Add the old object onto the stack (as a reference)
//prevObject属性用于维护和记录前一个操作的jQ实例对象
//把当前对象设置为ret的prevObject属性,并返回
ret.prevObject = this;
// Return the newly-formed element set
return ret;
},
// Execute a callback for every element in the matched set.
//迭代jQuery实例对象的方法,该方法内部调用jQuery.each方法实现
each: function( callback ) {
return jQuery.each( this, callback );
},
// 数组映射方法,对jQuery.map方法做了额外的包装
map: function( callback ) {
return this.pushStack( jQuery.map( this, function( elem, i ) {
//使用当前的元素来调用callback方法,绑定this
return callback.call( elem, i, elem );
} ) );
},
// 截取对象中指定的键值对(元素)
//因为slice方法调用后返回的是一个新的对象集合,所以需要调用pushStack方法维护prevObject堆栈
slice: function() {
//核心实现:this => Array.prototype.slice(arguments) || this => [].slice(arguments)
return this.pushStack( slice.apply( this, arguments ) );
},
//获取jQuery实例对象中的第一个元素(第一个键值对中的value值,其实就是第一个DOM标签)包裹为jQuery对象返回
//内部通过调用jQuery.eq方法实现,
first: function() {
return this.eq( 0 );
},
//获取jQuery实例对象中的最后一个元素
//同first方法一致,eq方法传递-1表示倒着数,即倒数第一个(最后一个)元素
last: function() {
return this.eq( -1 );
},
//获取jQuery实例对象中指定索引对应的元素,拿到元素后包装为jQuery实例对象返回
//该方法的参数支持正整数或者是负数
eq: function( i ) {
var len = this.length,
j = +i + ( i < 0 ? len : 0 );
return this.pushStack( j >= 0 && j < len ? [ this[ j ] ] : [] );
//自己写的另外一种实现方案(在判断的时候需要考虑到越界的问题)
// var len = this.length;
// var arrM = [];
// if (i >= 0 && i < len)
// {
// arrM.push(this[i])
// }
// else if(i < 0 && (-i < len) )
// {
// arrM.push(this[i + len])
// }
// return this.pushStack(arrM)
},
//返回前一个操作的jQuery实例对象
end: function() {
//检查prevObject的属性值,如果优质那么就返回prevObject否则返回空的jQuery实例对象
return this.prevObject || this.constructor();
},
// For internal use only.
// 仅供内部使用的方法
// Behaves like an Array's method, not like a jQuery method.
// 这些方法的表现和数组一致,不完全像jQuery风格的方法
//往jQuery实例对象中添加数据
push: push,
//排序的方法
sort: arr.sort,
//添加|删除的方法,其实就是数组中的splice方法
splice: arr.splice
};
//......
// 设置jQuery.prototype.init方法
init = jQuery.fn.init = function( selector, context, root ) {
//...
};
// Give the init function the jQuery prototype for later instantiation
/ 让jQuery.prototype.init方法的原型对象指向jQuery原型对象
// (正因如此$("xx")得到的jQ实例对象才能访问jQuery原型对象上面的方法)
init.prototype = jQuery.fn;
//.....
window.jQuery = window.$ = jQuery;
} );

2.0 框架的整体结构

下面给出简化后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
//01 立即调用函数(闭包)
(function (window) {
//02 提供工厂函数
var jQuery = function (selector) {
return new jQuery.fn.init(selector);
}
//03 设置原型对象
jQuery.prototype = {
constructor:jQuery,
init:function (selector) {
//初始化处理...
}
}
//动态的添加fn属性
jQuery.fn = jQuery.prototype;
//04 原型对象赋值
jQuery.fn.init.prototype = jQuery.fn;
//........
//05 把jQuery和$暴露出来
window.$ = window.jQuery = jQuery;
})(window);

jQuery框架整体结构总结

❏   jQuery本身是闭包中的一个函数,该函数作工厂函数用。
❏   jQuery所有的代码都被放在一个即时调用函数中(闭包),拥有独立和安全的作用域。
❏   jQuery方法在调用的时候,获取的实例对象其本质上是jQuery.fn.init构造函数实例化的。
❏   jQuery函数调用的时候,参数实际上都传递给了init函数,init函数才是真正的入口函数。
❏   为了让实例对象访问jQuery原型对象的成员,设置了jQuery.fn.initjQuery原型对象共享。
❏   jQuery通过把自身赋值给window成为全局对象的属性来实现框架外的访问($ 和 jQuery访问)。