在15年之前H5及三大框架流行起来之前,jQuery 是前端不可或缺的技能之一, 据不完全统计至今为止世界上至少还有80%的网站由jQuery构成。jQuery对于现在来说也很有借鉴意义,所以就在网上搜集一些资料整理学习一下。
*本文略有增减*
一、整体架构
jQuery 整体框架的结构十分清晰,按代码行文大致分为如下所示。
1 | (function(window, undefined){ |
二、jQuery闭包结构
// 用一个函数域包起来,就是所谓的沙箱
// 在这里边 var 定义的变量,属于这个函数域内的局部变量,避免污染全局
// 把当前沙箱需要的外部变量通过函数参数引入进来
// 只要保证参数对内提供的接口的一致性,你还可以随意替换传进来的这个参数
1 | (function(window, undefined) { |
jQuery 具体的实现,都被包含在了一个立即执行函数构造的闭包里面,为了不污染全局作用域,只在后面暴露 $ 和 jQuery 这 2 个变量给外界,尽量的避开变量冲突。常用的还有另一种写法:
1 | (function(window) { |
比较推崇的的第一种写法,也就是 jQuery 的写法。二者有何不同呢,当我们的代码运行在更早期的环境当中(pre-ES5,eg. Internet Explorer 8),undefined 仅是一个变量且它的值是可以被覆盖的。意味着你可以做这样的操作:undefined = 42;console.log(undefined) // 42
当使用第一种方式,可以确保你需要的 undefined 确实就是 undefined。这也是 undefined 和 null 的区别之一
另外不得不提出的是,jQuery 在这里有一个针对压缩优化细节,使用第一种方式,在代码压缩的时候,window 和 undefined 都可以压缩为 1 个字母并且确保它们就是 window 和 undefined。
// 压缩策略
1 | // w -> windwow , u -> undefined |
三、jQuery无new构造
嘿,回想一下使用 jQuery 的时候,实例化一个 jQuery 对象的方法:
1 | // 无 new 构造 |
大部分人使用 jQuery 的时候都是使用第一种无 new 的构造方式,直接$('')
进行构造,这也是 jQuery 十分便捷的一个地方。当我们使用第一种无 new 构造方式的时候,其本质就是相当于 new jQuery()
,那么在 jQuery 内部是如何实现的呢?看看:
1 | (function(window, undefined) { |
首先要明确,使用 $(‘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 源码晦涩难读的另一个原因是,使用了大量的方法重载,但是用起来却很方便:
1 | // 获取 title 属性的值 |
方法的重载即是一个方法实现多种功能,经常又是 get 又是 set,虽然阅读起来十分不易,但是从实用性的角度考虑,这也是为什么 jQuery 如此受欢迎的原因,大多数人使用 jQuery() 构造方法使用的最多的就是直接实例化一个 jQuery 对象,但其实在它的内部实现中,有着 9 种不同的方法重载场景:
1 | // 接受一个字符串,其中包含了用于匹配元素集合的 CSS 选择器 |
五、jQuery.(fn.)extend
extend
方法在jQuery中是一个很重要的方法,jQuey内部用它来扩展静态方法或实例方法,而且我们开发jQuery插件开发的时候也会用到它。但是在内部,是存在jQuery.fn.extend
和 jQuery.extend
两个 extend 方法的,而区分这两个 extend 方法是理解 jQuery 的很关键的一部分。先看结论:
1)jQuery.extend
(object) 为扩展jQuery类本身,为类添加新的静态方法
;
2)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
进行调用。
使用 jQuery.fn.extend() 拓展的实例方法,需要使用 $().xxx
调用。
六、链式调用及回溯
这一点的实现相对来说比较简单,只需要在要实现链式调用的方法的返回结果里,返回this,就能够实现链式调用
了。
除了链式调用,jQuery 甚至还允许回溯
:
1 | // 通过 end() 方法终止在当前链的最新过滤操作,返回上一个对象集合 |
当选择了 (‘div’).eq(0) 之后使用 end() 可以回溯到上一步选中的 jQuery 对象 $(‘div’),其内部实现其实是依靠添加了 prevObject
这个属性。
jQuery 完整的链式调用、增栈、回溯
通过 return this
、 return this.pushStack()
、return this.prevObject
实现。
总体来说:
1)end()
方法返回 prevObject
属性,这个属性记录了上一步操作的 jQuery
对象合集;
2)而 prevObject
属性由 pushStack()
方法生成,该方法将一个 DOM
元素集合加入到 jQuery
内部管理的一个栈中,通过改变 jQuery
对象的 prevObject
属性来跟踪链式调用中前一个方法返回的 DOM
结果集合;
3)当我们在链式调用 end()
方法后,内部就返回当前 jQuery
对象的 prevObject
属性,完成回溯。
七、jQuery正则与细节优化
不得不提 jQuery 在细节优化上做的很好,也存在很多值得学习的小技巧。
然后想谈谈正则表达式,jQuery 当中用了大量的正则表达式,我觉得如果研读 jQuery ,正则水平一定能够大大提升,如果是个正则小白,我建议在阅读之前先去了解以下几点:
1)了解并尝试使用 Javascript 正则相关 API,包括了 test() 、replace() 、match() 、exec() 的用法;
2)区分上面 4 个方法,哪个是 RegExp
对象方法,哪个是 String
对象方法;
3)了解简单的零宽断言
,了解什么是匹配不捕获
以及匹配且捕获
。
八、jQuery 变量冲突处理
最后想提一提 jQuery 变量的冲突处理,通过一开始保存全局变量的 window.jQuery 以及 windw.$ 。
当需要处理冲突的时候,调用静态方法 noConflict(),让出变量的控制权,源码如下:
1 | (function(window, undefined) { |
那么让出了这两个符号之后,是否就不能在我们的代码中使用 jQuery 或者呢 $ 呢?莫慌,还是可以使用的:
1 | // 让出 jQuery 、$ 的控制权不代表不能使用 jQuery 和 $ ,方法如下: |
文章转载: