jquery源码学习
本系列笔记是根据妙味课堂的jquery源码解析视频和高云大佬的著作jquery技术内幕总结而来,主要学习对象还是jquery2.0.3,那些繁琐的兼容问题实在不想面对。前者是的jquery是2.0.3版本的,后者是1.7.1版本的,虽然现在jquery已经发布到3.4.1了,但并没有找到较新的学习资料,只能按照这些古董来学习,希望学习完后有能力写一些自己的东西出来,最好根据最新的jquery版本写一个源码解读。目标已立,希望自己能做到。
一 总体架构
jquery的模块可以分为三部分:入口模块,底层支持模块和功能模块(高云大佬对1.7版本的分类,2.0依然适用)入口模块就是构造jquery对象,功能模块主要有属性操作,事件系统,dom操作和样式操作等,底层支持模块主要是Sizzle选择器,数据缓存和队列等。在视频中老师对jquery源码是这样划分的:
(function(){ (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; })();
因为主要研究对象是jquery2.0,就按照视频上老师划分的结构来学习。
首先在jquery外面包裹的是一个立即执行的匿名函数:
(function(window,undefined){ var rootjQuery // 声明一些全局变量 // 。。。 if ( typeof window === "object" && typeof window.document === "object" ) { window.jQuery = window.$ = jQuery; } }(window)
这种模式有一个术语:IIFE,叫做立即执行的函数表达式。在函数外包裹一个()使之成为一个表达式,再在末尾添加一个()就可以立即执行该函数。IIFE还有另外一种形式:(function(){...}()),两种形式功能上是一致的。上面这个自调用的匿名函数,创建了一个特殊的函数作用域,该作用域中的方法,变量都是私有的,不会冲突。而把jquery对象挂载到window上,是之成为公开的全局变量,也就是对外提供了一个可以引用jquery对象的一个接口。那个if判断也是防止window对象和document对象被人可以更改了。至于传入window对象,高云大佬是这样解释的:一是使window对象变更为局部变量(即把函数参数作为局部变量使用),在jquery代码内部访问window对象时,不需要将作用域链回退到顶层,从而更快的访问window对象;二是可以在压缩代码时对其进行优化。匿名函数传入的第二个参数undefined,这也是window对象的一个属性,高云大佬对其这样解释的:一是把同window一样,缩短查找undefined的作用域链,压缩时可以优化;二是防止undefined值被更改。
二 jquery构造函数
首先看下源码:
jQuery = function( selector, context ) { return new jQuery.fn.init( selector, context, rootjQuery ); } jQuery.fn = jQuery.prototype = {...}; jQuery.fn.init.prototype = jQuery.fn;
当我们调用$()或者jQuery()时,实际上返回的是一个 new jQuery.fn.init()的一个实例,也就是说真正的构造函数是jQuery.fn.init(),而下面又将jQuery的原型指向jQuery.fn,也就是说fn就是jQuery的原型,然后又将jQuery的原型指向真正构造函数的原型,这样做是相当绕的,视频里老师讲这样做主要是为了我们能在调用$()时能够直接完成构造函数的初始化,而不是我们平常用的先$().init()初始化,再调用$()上的一些方法。虽然源码很绕,但实际使用过程中,还是简洁了很多。而高云大佬是这样解释的,通常我们创建对象或者实例的方式是在运算符new后面紧跟一个构造函数,如果构造函数有返回值,运算符所创建的对象就会被抛弃,返回值将作为new表达式的值。这样在我们创建jQuery对象时不需要new,直接些jQuery()。
下面来看一些jQuery的原型,源码架构如下:
jQuery.fn = jQuery.prototype = { jquery: core_version, // 版本号 constructor: jQuery, // 防止原型被覆盖 init: function( selector, context, rootjQuery ) {...}, // 初始化 selector: "", length: 0, toArray: function() {...}, //转数组 get: function( num ) {...}, //转原生集合 pushStack: function( elems ) {...}, //jq对象入栈 each: function( callback, args ) {...}, //遍历集合 ready: function( fn ) {...}, //dom加载的接口 slice: function() {...}, //集合的截取 first: function() {...}, //集合的第一项 last: function() {...}, //集合的最后一项 eq: function( i ) {...}, //集合指定项 map: function( callback ) {...}, //返回辛几何 end: function() {...}, //返回集合前一个状态 push: core_push, sort: [].sort, splice: [].splice };
这里使用了一个对象对jQuery的原型进行了覆盖,接下来对该对象进行一个详细的剖析。
首先这个对象里定义了一些属性和方法。这里着重看一下init这个初始化方法,该方法接受三个参数,selector是选择元素的字符串,context是被选择元素的上下文,rootjQuery就是document对象。接下来就对selector进行了一些简单的分配。判断selector的类型,是空值、字符串、还是数组,然后对相应的类型做一些初始化操作,这里面判断selector类型的操作还是相当琐碎的,各个方面都考虑的很周全。