前言
阅读本文前可能需要熟悉JavaScript
的原型和原型链知识,如果你还没有掌握的话,请自行传送。
jQuery概览
本概览使用的就jQuery
版本是2.0.3
版本,一个比较老的版本,但是基本功能与新版类似。此外,2.X相较于1.X版本,去除了对IE6/7/8的支持,这也使得其中减少了很多有关于兼容性的代码,便于我们去理解。
jQuery
首先是一个 IIFE( 立即调用函数表达式),英文全称 Immediately Invoked Function Expression,这是一个被称为 自执行匿名函数 的设计模式,主要包含两部分。第一部分是包围在 圆括号运算符 () 里的一个匿名函数,这个匿名函数拥有独立的词法作用域。这不仅避免了外界访问此 IIFE 中的变量,而且又不会污染全局作用域。
(function( window, undefined ) {
(21 , 94) 定义了一些变量和函数 jQuery = function(){};
(96 , 283) 给JQ对象,添加一些方法和属性
(285 , 347) extend : JQ的继承方法
(349 , 817) jQuery.extend() : 扩展一些工具方法
(877 , 2856) Sizzle : 复杂选择器的实现
(2880 , 3042) Callbacks : 回调对象 : 对函数的统一管理
(3043 , 3183) Deferred : 延迟对象 : 对异步的统一管理
(3184 , 3295) support : 功能检测
(3308 , 3652) data() : 数据缓存
(3653 , 3797) queue() : 队列方法 : 执行顺序的管理
(3803 , 4299) attr() prop() val() addClass()等 : 对元素属性的操作
(4300 , 5128) on() trigger() : 事件操作的相关方法
(5140 , 6057) DOM操作 : 添加 删除 获取 包装 DOM筛选
(6058 , 6620) css() : 样式的操作
(6621 , 7854) 提交的数据和ajax() : ajax() load() getJSON()
(7855 , 8584) animate() : 运动的方法
(8585 , 8792) offset() : 位置和尺寸的方法
(8804 , 8821) JQ支持模块化的模式
(8826) window.jQuery = window.$ = jQuery;
})(window);
以上就是整个jQuery
概览,接下来我们看下jQuery
的核心部分。
jQuery核心代码提炼
以下代码我个人觉得是jQuery
最核心的部分,也是最难理解的部分。
(function( window, undefined ) {
// 这里为什么参undefined呢?这是为了防止undefined在外部被修改
var jQuery = function(selector) {
return new jQuery.fn.init(selector);
}
// 下面语句是重写了函数jQuery的原型prototype,故需要将constructor
// 指向函数jQuery。
jQuery.fn = jQuery.prototype = {
wQuery: '2.0.3',
constructor: jQuery,
init: function(selector) {
return this;
},
// 其它jQuery原型上的方法
}
jQuery.fn.init.prototype = jQuery.fn;
jQuery.extend = jQuery.fn.extend = function() {
// ...
}
window.jQuery = window.$ = jQuery;
})(window);
下面我们就来解析下jQuery
的各部分实现。
jQuery无 new 构造
当我们使用jQuery
的时候,是如何实例化jQuery
构造函数的呢?
// 无 new 构造
$('#test').text('Test');
// 当然也可以使用 new
// 也许你会疑问 $===jQuery函数 不是本身就返回一个实例化后的对象
// (return new jQuery.fn.init())吗?为什么还可以 new $() 呢?
// 这个问题查下 new 操作符的实现就是到原因了,这里就不详述。
var test = new $('#test');
test.text('Test');
大部分人使用jQuery
的时候都是使用第一种无new
的实例化方式,直接$('')
实例化jQuery
,这也是jQuery
十分便捷的一个地方。当我们使用第一种无new
实例化的时候,其本质就是相当于new jQuery()
,那么在jQuery
内部是如何实现的呢?看看:
(function(window, undefined) {
var
// ...
jQuery = function(selector, context) {
// The jQuery object is actually just the init constructor 'enhanced'
// 看这里,实例化方法 jQuery() 实际上是调用了其拓展的原型方法 jQuery.fn.init
return new jQuery.fn.init(selector, context, rootjQuery);
},
// jQuery.prototype 即是 jQuery 的原型,挂载在上面的方法,即可让所有生成的 jQuery 对象使用
jQuery.fn = jQuery.prototype = {
// 实例化化方法,这个方法可以称作 jQuery 对象构造器
init: function(selector, context, rootjQuery) {
// ...
}
}
// 这一句很关键,也很绕
// jQuery 没有使用 new 运算符将 jQuery 实例化,而是直接调用其函数
// 要实现这样,那么 jQuery 就要看成一个类,且返回一个正确的实例
// 且实例还要能正确访问 jQuery 类原型上的属性与方法
// jQuery 的方式是通过原型传递解决问题,把 jQuery 的原型传递给jQuery.prototype.init.prototype
// 所以通过这个方法生成的实例 this 所指向的仍然是 jQuery.fn,所以能正确访问 jQuery 类原型上的属性与方法
jQuery.fn.init.prototype = jQuery.fn;
})(window);
大部分人初看jQuery.fn.init.prototype = jQuery.fn
这一句都会被卡主,很是不解。但是这句真的算是jQuery
的绝妙之处。理解这几句很重要,分点解析一下:
- 首先要明确,使用
$('xxx')
这种实例化方式,其内部调用的是return new jQuery.fn.init(selector, context, rootjQuery)
这一句话,也就是构造实例是交给了jQuery.fn.init()
方法去完成。 - 将
jQuery.fn.init
的prototype
属性设置为jQuery.fn
,那么使用new jQuery.fn.init()
生成的对象的原型对象就是jQuery.fn
,所以挂载到jQuery.fn
上面的函数就相当于挂载到jQuery.fn.init()
生成的jQuery
对象上,所有使用new jQuery.fn.init()
生成的对象也能够访问到jQuery.fn
上的所有原型方法。 - 最终实例化方法形成了一个关系链
- jQuery.fn.init.prototype = jQuery.fn = jQuery.prototype;
- new jQuery.fn.init() 相当于 new jQuery();
- jQuery() 返回的是 new jQuery.fn.init(),而 var obj = new jQuery(),所以这 2 者是相当的,所以我们可以无 new 实例化 jQuery 对象。
jQuery的链式操作
另一个让大家喜爱使用jQuery
的原因是它的链式调用,这一点的实现其实很简单,只需要在要实现链式调用的方法的返回结果里,返回this
,就能够实现链式调用了。当然这样实现的链式操作是为单纯的实现函数的调用而不需要返回其他的值的情况下,如果你还要返回其他值的话就不能单纯的返回this这样来实现链式操作了。
我们看下这段代码:
$('div#box').find('span').eq(2);
这段代码的意思是找到div#box
中的所有span
的第三个,$('div#box').find('span')
既要返回所有的span
又要可以实现后面的eq(2)
的链式操作。
我们来看看jQuery
怎么实现的吧!
(function( window, undefined ) {
// ...
jQuery.fn = jQuery.prototype = {
wQuery: '2.0.3',
constructor: jQuery,
init: function(selector) {
return this;
},
getName: function() {
// this.constructor()实例化了一个新的jQuery对象,继承了jQuery
// 原型上所有方法和属性以实现链式操作,在新的示例对象上添加属性和
// 方法时不会改变当前jQuery原型上的属性和方法,这也是不直接用this
// 而用this.constructor()来实例化一个新的对象返回的原因。
let all = this.constructor();
all.jack = 'jack';
all.lucy = 'lucy';
return all;
},
sayName: function(who) {
console.log(this[who]);
}
}
jQuery.fn.init.prototype = jQuery.fn;
// ...
window.jQuery = window.$ = jQuery;
})(window);
jQuery().getName().sayName('lucy'); // 'lucy'
jQuery.fn.extend 与 jQuery.extend
extend
方法在jQuery
中是一个很重要的方法,jQuey
内部用它来扩展静态方法或实例方法,而且我们开发jQuery
插件开发的时候也会用到它。但是在内部,是存在jQuery.fn.extend
和jQuery.extend
两个extend
方法的,而区分这两个extend
方法是理解jQuery
的很关键的一部分。先看结论:
-
jQuery.extend(object)
为扩展jQuery
类本身,为类添加新的静态方法; -
jQuery.fn.extend(object)
给jQuery
对象添加实例方法,也就是通过这个extend
添加的新方法,实例化的jQuery
对象都能使用,因为它是挂载在jQuery.fn
上的方法(上文有提到,jQuery.fn = jQuery.prototype
)。
它们的官方解释是:
-
jQuery.extend()
: 把两个或者更多的对象合并到第一个当中, -
jQuery.fn.extend()
:把对象挂载到jQuery
的prototype
属性,来扩展一个新的jQuery
实例方法。
也就是说,使用 jQuery.extend()
拓展的静态方法,我们可以直接使用 $.xxx
进行调用(xxx是拓展的方法名),
而使用 jQuery.fn.extend()
拓展的实例方法,需要使用 $().xxx
调用。
需要注意的是这一句 jQuery.extend = jQuery.fn.extend = function() {}
,也就是 jQuery.extend
的实现和 jQuery.fn.extend
的实现共用了同一个方法,但是为什么能够实现不同的功能了,这就要归功于 Javascript 强大(怪异?)的 this
了。
-
在
jQuery.extend()
中,this
的指向是jQuery
对象(或者说是jQuery
类),所以这里扩展在jQuery
上; -
在
jQuery.fn.extend()
中,this
的指向是fn
对象,前面有提到jQuery.fn = jQuery.prototype
,也就是这里增加的是原型方法,也就是对象方法。
文章最后就到这里了
最后感谢【深入浅出jQuery】源码浅析–整体架构提供技术支持。