最近一直在研读 jQuery 源码初看源码一头雾水毫无头绪真正静下心来细看写的真是精妙让你感叹代码之美。其结构明晰高内聚、低耦合兼具优秀的性能与便利的扩展性在浏览器的兼容性功能缺陷、渐进增强优雅的处理能力以及 Ajax 等方面周到而强大的定制功能无不令人惊叹。另外阅读源码让我接触到了大量底层的知识。对原生JS 、框架设计、代码优化有了全新的认识接下来将会写一系列关于 jQuery 解析的文章。我在 github 上关于 jQuery 源码的全文注解感兴趣的可以围观一下。jQuery v1.10.2 源码注解 。系列第二篇【深入浅出jQuery】源码浅析2--奇技淫巧网上已经有很多解读 jQuery 源码的文章了作为系列开篇的第一篇思前想去起了个【深入浅出jQuery】的标题资历尚浅无法对 jQuery 分析的头头是道但是 jQuery 源码当中确实有着大量巧妙的设计不同层次水平的阅读者都能有收获所以打算厚着脸皮将自己从中学到的一些知识点共享出来。打算从整体及分支分章节剖析。本篇主要讲 jQuery 的整体架构及一些前期准备先来看看 jQuery 的整体结构jQuery 整体架构不同于 jQuery 代码各个模块细节实现的晦涩难懂jQuery 整体框架的结构十分清晰按代码行文大致分为如上图所示的模块。初看 jQuery 源码可能很容易一头雾水因为 9000 行的代码感觉没有尽头所以了解作者的行文思路十分重要。整体而言我觉得 jQuery 采用的是总--分的结构虽然JavaScript有着作用域的提升机制但是 9000 多行的代码为了相互的关联性并不代表所有的变量都要定义在最顶部。在 jQuery 中只有全局都会用到的变量、正则表达式定义在了代码最开头而每个模块一开始又会定义一些只在本模块会使用到的变量、正则、方法等。所以在一开始的阅读的过程中会有很多看不懂其作用的变量正则方法。所以我觉得阅读源码很重要的一点是摒弃面向过程的思维方式不要刻意去追求从上至下每一句都要在一开始弄明白。很有可能一开始你在一个奇怪的方法或者变量处卡壳了很想知道这个方法或变量的作用然而可能它要到几千行处才被调用到。如果去追求这种逐字逐句弄清楚的方式很有可能在碰壁几次之后阅读的积极性大受打击。道理说了很多接来下进入真正的正文对 jQurey 的一些前期准备小的细节进行分析jQuery 闭包结构1234567// 用一个函数域包起来就是所谓的沙箱// 在这里边 var 定义的变量属于这个函数域内的局部变量避免污染全局// 把当前沙箱需要的外部变量通过函数参数引入进来// 只要保证参数对内提供的接口的一致性你还可以随意替换传进来的这个参数(function(window, undefined) {// jQuery 代码})(window);jQuery 具体的实现都被包含在了一个立即执行函数构造的闭包里面为了不污染全局作用域只在后面暴露 $ 和 jQuery 这 2 个变量给外界尽量的避开变量冲突。常用的还有另一种写法123(function(window) {// JS代码})(window, undefined);比较推崇的的第一种写法也就是 jQuery 的写法。二者有何不同呢当我们的代码运行在更早期的环境当中pre-ES5eg. Internet Explorer 8undefined 仅是一个变量且它的值是可以被覆盖的。意味着你可以做这样的操作12undefined 42console.log(undefined)// 42当使用第一种方式可以确保你需要的 undefined 确实就是 undefined。另外不得不提出的是jQuery 在这里有一个针对压缩优化细节使用第一种方式在代码压缩的时候window 和 undefined 都可以压缩为 1 个字母并且确保它们就是 window 和 undefined。12345// 压缩策略// w - windwow , u - undefined(function(w, u) {})(window);jQuery 无 new 构造嘿回想一下使用 jQuery 的时候实例化一个 jQuery 对象的方法123456// 无 new 构造$(#test).text(Test);// 当然也可以使用 newvartest new$(#test);test.text(Test);大部分人使用 jQuery 的时候都是使用第一种无 new 的构造方式直接 $() 进行构造这也是 jQuery 十分便捷的一个地方。当我们使用第一种无 new 构造方式的时候其本质就是相当于 new jQuery()那么在 jQuery 内部是如何实现的呢看看12345678910111213141516171819202122232425(function(window, undefined) {var// ...jQuery function(selector, context) {// The jQuery object is actually just the init constructor enhanced// 看这里实例化方法 jQuery() 实际上是调用了其拓展的原型方法 jQuery.fn.initreturnnewjQuery.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 的绝妙之处。理解这几句很重要分点解析一下1首先要明确使用 $(xxx) 这种实例化方式其内部调用的是 return new jQuery.fn.init(selector, context, rootjQuery) 这一句话也就是构造实例是交给了 jQuery.fn.init() 方法去完成。2将 jQuery.fn.init 的 prototype 属性设置为 jQuery.fn那么使用 new jQuery.fn.init() 生成的对象的原型对象就是 jQuery.fn 所以挂载到 jQuery.fn 上面的函数就相当于挂载到 jQuery.fn.init() 生成的 jQuery 对象上所有使用 new jQuery.fn.init() 生成的对象也能够访问到 jQuery.fn 上的所有原型方法。3也就是实例化方法存在这么一个关系链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 源码晦涩难读的另一个原因是使用了大量的方法重载但是用起来却很方便123456789// 获取 title 属性的值$(#id).attr(title);// 设置 title 属性的值$(#id).attr(title,jQuery);// 获取 css 某个属性的值$(#id).css(title);// 设置 css 某个属性的值$(#id).css(width,200px);方法的重载即是一个方法实现多种功能经常又是 get 又是 set虽然阅读起来十分不易但是从实用性的角度考虑这也是为什么 jQuery 如此受欢迎的原因大多数人使用 jQuery() 构造方法使用的最多的就是直接实例化一个 jQuery 对象但其实在它的内部实现中有着 9 种不同的方法重载场景1234567891011121314151617// 接受一个字符串其中包含了用于匹配元素集合的 CSS 选择器jQuery([selector,[context]])// 传入单个 DOMjQuery(element)// 传入 DOM 数组jQuery(elementArray)// 传入 JS 对象jQuery(object)// 传入 jQuery 对象jQuery(jQuery object)// 传入原始 HTML 的字符串来创建 DOM 元素jQuery(html,[ownerDocument])jQuery(html,[attributes])// 传入空参数jQuery()// 绑定一个在 DOM 文档载入完成后执行的函数jQuery(callback)所以读源码的时候很重要的一点是结合 jQuery API 进行阅读去了解方法重载了多少种功能同时我想说的是jQuery 源码有些方法的实现特别长且繁琐因为 jQuery 本身作为一个通用性特别强的框架一个方法兼容了许多情况也允许用户传入各种不同的参数导致内部处理的逻辑十分复杂所以当解读一个方法的时候感觉到了明显的困难尝试着跳出卡壳的那段代码本身站在更高的维度去思考这些复杂的逻辑是为了处理或兼容什么是否是重载为什么要这样写一定会有不一样的收获。其次也是因为这个原因jQuery 源码存在许多兼容低版本的 HACK 或者逻辑十分晦涩繁琐的代码片段浏览器兼容这样的大坑极其容易让一个前端工程师不能学到编程的精髓所以不要太执着于一些边角料即使兼容性很重要也应该适度学习理解适可而止。