jQuery的整体架构
对架构有个清晰的认识,才不会一头雾水,漫无目的解读源代码。
jQuery的无new构建
|
|
这里涉及到两个问题(无new构建和链式调用)
- $(“XXX”)到底是怎样实现的,内部发生了什么使我们能够如此轻易获取到对象。
* $是什么鬼?
window 是对象,它有两个属性,分别为 jQuery 和 $,其值是一函数,此函数的名字是 jQuery。在函数
的定义实体中,其实是通过 jQuery.fn.init 函数来构造对象的,init 函数才是真正的构造函数。也就是说, 我们通过 ${ … ) 得到的其实就是 jQuery.fn.init 的实例。1234var jQuery = window.jQuery = window.$ = function( selector, context ) {// The jQuery object is actually just the init constructor 'enhanced'return new jQuery.fn.init( selector, context );};
*jQuery.fn?fn解释下,其实这个fn没有什么特殊意思,只是jQuery.prototype的引用
- 首先要明确,使用 $(‘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 对象。
链式调用
为什么DOM链式调用的处理:
- 节约JS代码.
- 所返回的都是同一个对象,可以提高代码的效率
- 实现链式的基本条件:就是实例this的存在,并且是同一个123456789101112131415aQuery.prototype = {init: function() {return this;},name: function() {return this}}--------------aQuery().init().name()分解a = aQuery();a.init()a.name()
插件接口
- jQuery的主体框架就是这样,但是根据一般设计者的习惯,如果要为jQuery或jQuery prototype添加属性方法,同样如要提供给开发者对方法的扩展,从封装的角度讲是不是应该提供一个接口才对,字面就能看懂是对函数扩展,而不是看上去直接修改prototype.友好的用户接口,
jQuery支持自己扩展属性,这个对外提供了一个接口,jQuery.fn.extend()来对对象增加方法
从jQuery的源码中可以看到,jQuery.extend和jQuery.fn.extend其实是同指向同一方法的不同引用 - jQuery.extend和jQuery.fn.extend区别:
- jQuery.extend(object) 为扩展 jQuery 类本身,为类添加新的静态方法;
- jQuery.fn.extend(object) 给 jQuery 对象添加实例方法,也就是通过这个 extend 添加的新方法,实例化的 jQuery 对象都能使用,因为它是挂载在 jQuery.fn 上的方法(上文有提到,jQuery.fn = jQuery.prototype )。
它们的官方解释是:
1)jQuery.extend(): 把两个或者更多的对象合并到第一个当中,
2)jQuery.fn.extend():把对象挂载到 jQuery 的 prototype 属性,来扩展一个新的 jQuery 实例方法。 - 也就是说,使用 jQuery.extend() 拓展的静态方法,我们可以直接使用 $.xxx 进行调用(xxx是拓展的方法名),
- 而使用 jQuery.fn.extend() 拓展的实例方法,需要使用 $().xxx 调用。
|
|
通过extend()函数可以方便快速的扩展功能,不会破坏jQuery的原型结构
jQuery.extend = jQuery.fn.extend = function(){…}; 这个是连等,也就是2个指向同一个函数,怎么会实现不同的功能呢?这就是this 力量了!
针对fn与jQuery其实是2个不同的对象,在之前有讲述:
- jQuery.extend 调用的时候,this是指向jQuery对象的(jQuery是函数,也是对象!),所以这里扩展在jQuery上。
- 而jQuery.fn.extend 调用的时候,this指向fn对象,jQuery.fn 和jQuery.prototype指向同一对象,扩展fn就是扩展jQuery.prototype原型对象。
- 这里增加的是原型方法,也就是对象方法了。所以jQuery的api中提供了以上2中扩展函数。1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798// 扩展合并函数// 合并两个或更多对象的属性到第一个对象中,jQuery 后续的大部分功能都通过该函数扩展// 虽然实现方式一样,但是要注意区分用法的不一样,那么为什么两个方法指向同一个函数实现,但是却实现不同的功能呢,// 阅读源码就能发现这归功于 this 的强大力量// 如果传入两个或多个对象,所有对象的属性会被添加到第一个对象 target// 如果只传入一个对象,则将对象的属性添加到 jQuery 对象中,也就是添加静态方法// 用这种方式,我们可以为 jQuery 命名空间增加新的方法,可以用于编写 jQuery 插件// 如果不想改变传入的对象,可以传入一个空对象:$.extend({}, object1, object2);// 默认合并操作是不迭代的,即便 target 的某个属性是对象或属性,也会被完全覆盖而不是合并// 如果第一个参数是 true,则是深拷贝// 从 object 原型继承的属性会被拷贝,值为 undefined 的属性不会被拷贝// 因为性能原因,JavaScript 自带类型的属性不会合并jQuery.extend = jQuery.fn.extend = function() {var src, copyIsArray, copy, name, options, clone,target = arguments[0] || {},i = 1,length = arguments.length,deep = false;// Handle a deep copy situation// target 是传入的第一个参数// 如果第一个参数是布尔类型,则表示是否要深递归,if (typeof target === "boolean") {deep = target;target = arguments[1] || {};// skip the boolean and the target// 如果传了类型为 boolean 的第一个参数,i 则从 2 开始i = 2;}// Handle case when target is a string or something (possible in deep copy)// 如果传入的第一个参数是 字符串或者其他if (typeof target !== "object" && !jQuery.isFunction(target)) {target = {};}// extend jQuery itself if only one argument is passed// 如果参数的长度为 1 ,表示是 jQuery 静态方法if (length === i) {target = this;--i;}// 可以传入多个复制源// i 是从 1或2 开始的for (; i < length; i++) {// Only deal with non-null/undefined values// 将每个源的属性全部复制到 target 上if ((options = arguments[i]) != null) {// Extend the base objectfor (name in options) {// src 是源(即本身)的值// copy 是即将要复制过去的值src = target[name];copy = options[name];// Prevent never-ending loop// 防止有环,例如 extend(true, target, {'target':target});if (target === copy) {continue;}// Recurse if we're merging plain objects or arrays// 这里是递归调用,最终都会到下面的 else if 分支// jQuery.isPlainObject 用于测试是否为纯粹的对象// 纯粹的对象指的是 通过 "{}" 或者 "new Object" 创建的// 如果是深复制if (deep && copy && (jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)))) {// 数组if (copyIsArray) {copyIsArray = false;clone = src && jQuery.isArray(src) ? src : [];// 对象} else {clone = src && jQuery.isPlainObject(src) ? src : {};}// Never move original objects, clone them// 递归target[name] = jQuery.extend(deep, clone, copy);// Don't bring in undefined values// 最终都会到这条分支// 简单的值覆盖} else if (copy !== undefined) {target[name] = copy;}}}}// Return the modified object// 返回新的 target// 如果 i < length ,是直接返回没经过处理的 target,也就是 arguments[0]// 也就是如果不传需要覆盖的源,调用 $.extend 其实是增加 jQuery 的静态方法return target;};
需要注意的是这一句 jQuery.extend = jQuery.fn.extend = function() {} ,也就是 jQuery.extend 的实现和 jQuery.fn.extend 的实现共用了同一个方法,但是为什么能够实现不同的功能了,这就要归功于 Javascript 强大(怪异?)的 this 了。
1)在 jQuery.extend() 中,this 的指向是 jQuery 对象(或者说是 jQuery 类),所以这里扩展在 jQuery 上;
2)在 jQuery.fn.extend() 中,this 的指向是 fn 对象,前面有提到 jQuery.fn = jQuery.prototype ,也就是这里增加的是原型方法,也就是对象方法。