自己根据自己的理解去尝试分析下大名鼎鼎的jquery的源码,一来提高自己使用jqueryAPI的使用能力,最重要的是提高自己javascript的能力,加油!
下载的是官网的http://code.jquery.com/jquery-2.1.1.js,2.1.1版本。
(function( global, factory ) { if ( typeof module === "object" && typeof module.exports === "object" ) { // For CommonJS and CommonJS-like environments where a proper window is present, // execute the factory and get jQuery // For environments that do not inherently posses a window with a document // (such as Node.js), expose a jQuery-making factory as module.exports // This accentuates the need for the creation of a real window // e.g. var jQuery = require("jquery")(window); // See ticket #14549 for more info module.exports = global.document ? factory( global, true ) : function( w ) { if ( !w.document ) { throw new Error( "jQuery requires a window with a document" ); } return factory( w ); }; } else { factory( global ); } // Pass this if window is not defined yet }(typeof window !== "undefined" ? window : this, function( window, noGlobal )
一.第一行先定义了一个匿名函数并且执行,这是很多库的标准用法,在一个匿名函数闭包中运行,例如jquery,匿名函数里面的变量不会与外界冲突,只在9182行调用
window.jQuery = window.$ = jQuery;将jQuery和$挂载到window下,从而使用jQuery和$符来调用匿名函数中的各种方法和属性。
二.接着进行判断 if ( typeof module === "object" && typeof module.exports === "object" )。根据我的理解,是在之前如果应用到了符合CommonJS规范的js库,例如
Node.js,做一些冲突的协调,这个需要node.js等的一些知识。这个先不去理他。
三.如果没引用那些库,直接调用factory( global );factory和global正是这个大的匿名函数(function( global, factory )的两个参数,看一下这个匿名函数传的实参
(1)第一个参数window,首先判断 typeof window !== "undefined" ? window : this,如果window的类型不等于undefined,则传window参数,等于undefined,则传this,为什么
这么用,因为window是可以改变的,到后面可能会用到
(2)第二个参数是一个匿名函数function(window,noGlobal)。考虑到一般情况,function( global, factory ) {factiory(global)}(window,function...);这样这个匿名函数就容易理解了, 其实就是调用定义并且调用function(window,noGlobal),或者是 function(this,noGlobal)。之所以要这么用,因为这样会使作用域链变短,并且防止与其他库的冲突。
四.旧版的jquery好像没有上面的这个功能,是直接调用的function(window,noGlobal),下面开始漫长之路了,开始分析jquery的主函数 function(window,noGlobal)。
var arr = []; var slice = arr.slice; var concat = arr.concat; var push = arr.push; var indexOf = arr.indexOf; var class2type = {}; var toString = class2type.toString; var hasOwn = class2type.hasOwnProperty; var support = {}; var // Use the correct document accordingly with window argument (sandbox) document = window.document, version = "2.1.1", // Define a local copy of jQuery jQuery = function( selector, context ) { // The jQuery object is actually just the init constructor ‘enhanced‘ // Need init if jQuery is called (just allow error to be thrown if not included) return new jQuery.fn.init( selector, context ); }, // Support: Android<4.1 // Make sure we trim BOM and NBSP rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, // Matches dashed string for camelizing rmsPrefix = /^-ms-/, rdashAlpha = /-([\da-z])/gi, // Used by jQuery.camelCase as callback to replace() fcamelCase = function( all, letter ) { return letter.toUpperCase(); };
39~72行,定义了一些变量,在后面会用到,用到了再说
73~77行,
1 jQuery = function( selector, context ) { 2 // The jQuery object is actually just the init constructor ‘enhanced‘ 3 // Need init if jQuery is called (just allow error to be thrown if not included) 4 return new jQuery.fn.init( selector, context ); 5 }
定义了最重要的jQuery符号,例如我们调用$("a")即jQuery("a")相当于创建了jquery的一个实例,可以调用该实例的方法和属性,即new jQuery.fn.init("a");
78~86行,定义了一些正则表达式规则,在以后会用到。
rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g 表示不同序列编码的空格开始或结尾
rmsPrefix = /^-ms-/,以-ms-开头
rdashAlpha = /-([\da-z])/gi 以-后跟数字字母的前缀并且不区分大小写,这是为了将那些用-连接的字符串弄成驼峰的形式。
88~90行
fcamelCase = function( all, letter ) { return letter.toUpperCase(); };
定义了一个简单的方法,就是返回参数的大写形式。
五.jQuery的原型
1 jQuery.fn = jQuery.prototype = { 2 // The current version of jQuery being used 3 jquery: version, 4 5 constructor: jQuery, 6 7 // Start with an empty selector 8 selector: "", 9 10 // The default length of a jQuery object is 0 11 length: 0, 12 13 toArray: function() { 14 return slice.call( this ); 15 }, 16 17 // Get the Nth element in the matched element set OR 18 // Get the whole matched element set as a clean array 19 get: function( num ) { 20 return num != null ? 21 22 // Return just the one element from the set 23 ( num < 0 ? this[ num + this.length ] : this[ num ] ) : 24 25 // Return all the elements in a clean array 26 slice.call( this ); 27 }, 28 29 // Take an array of elements and push it onto the stack 30 // (returning the new matched element set) 31 pushStack: function( elems ) { 32 33 // Build a new jQuery matched element set 34 var ret = jQuery.merge( this.constructor(), elems ); 35 36 // Add the old object onto the stack (as a reference) 37 ret.prevObject = this; 38 ret.context = this.context; 39 40 // Return the newly-formed element set 41 return ret; 42 }, 43 44 // Execute a callback for every element in the matched set. 45 // (You can seed the arguments with an array of args, but this is 46 // only used internally.) 47 each: function( callback, args ) { 48 return jQuery.each( this, callback, args ); 49 }, 50 51 map: function( callback ) { 52 return this.pushStack( jQuery.map(this, function( elem, i ) { 53 return callback.call( elem, i, elem ); 54 })); 55 }, 56 57 slice: function() { 58 return this.pushStack( slice.apply( this, arguments ) ); 59 }, 60 61 first: function() { 62 return this.eq( 0 ); 63 }, 64 65 last: function() { 66 return this.eq( -1 ); 67 }, 68 69 eq: function( i ) { 70 var len = this.length, 71 j = +i + ( i < 0 ? len : 0 ); 72 return this.pushStack( j >= 0 && j < len ? [ this[j] ] : [] ); 73 }, 74 75 end: function() { 76 return this.prevObject || this.constructor(null); 77 }, 78 79 // For internal use only. 80 // Behaves like an Array‘s method, not like a jQuery method. 81 push: push, 82 sort: arr.sort, 83 splice: arr.splice 84 };
这一片就是原型的属性和方法。jQuery.fn = jQuery.prototype是为了以后写的方便
比如直接可以用$.jquery就是调用jQuery.jquery,返回的是version,即在之前定义的常量,juqery的版本号。
93~103,定义了几个属性,比如version版本号,constructor构造器,selector选择器,length长度,因为等于说是初始化,都是空或者0。
104~168,接下来是原型上的方法,挨个分析下,并且举出一些例子。
后来发现jquery重写了slice方法,因此先来看slice方法
slice: function() { return this.pushStack( slice.apply( this, arguments ) ); }
比较坑,又用到了pushStack方法,再来看
pushStack: function( elems ) { // Build a new jQuery matched element set var ret = jQuery.merge( this.constructor(), elems ); // Add the old object onto the stack (as a reference) ret.prevObject = this; ret.context = this.context; // Return the newly-formed element set return ret; }
比较坑+1,用到了merge方法,继续找,在424行找到,先看下
merge: function( first, second ) { var len = +second.length, j = 0, i = first.length; for ( ; j < len; j++ ) { first[ i++ ] = second[ j ]; } first.length = i; return first; }
函数比较好看懂,该方法传两个参数,参数应该都为数组,len为第二个数组长度,i为第一个数组的长度,从[0,len)开始循环,达到的效果是两个数组进行合并到第一个参数数组,然后
再把第一个参数的长度设置成两个数组长度的和。返回第一个数组。
例如$.merge( [0,1,2], [2,3,4] )得到[0, 1, 2, 2, 3, 4]
往前捋,看pushStack方法
toArray: function() { return slice.call( this ); },
就是类似于将字符串的slice方法应用,只不过是改了个名字罢了,一个是jquery对象上的方法,一个是字符串的方法而已。比如$("a").toArray(),就是将所有的匹配到的a标签组成的
jquery对象集合转化为数组
get: function( num ) { return num != null ? // Return just the one element from the set ( num < 0 ? this[ num + this.length ] : this[ num ] ) : // Return all the elements in a clean array slice.call( this ); },
get方法,先判断参数num是否为null,如果是null,直接调用jquery的slice方法,即与toArray方法相同,如果不为null,再进行判断是否小于零,小于零,则从匹配到的jquery对象集合中返回
倒数第-num个对象,如果大于等于零,则返回对象集合中第num+1个jquery对象。
例如$("a").get(null)相当于调用$("a").toArray(),返回数组形式的jquery对象集合;
$("a").get(1)返回jquery对象集合中第二个匹配到的jquery对象
$("a").get(-1)返回jquery对象集合中倒数第一个匹配到的jquery对象。
pushStack: function( elems ) { // Build a new jQuery matched element set var ret = jQuery.merge( this.constructor(), elems ); // Add the old object onto the stack (as a reference) ret.prevObject = this; ret.context = this.context; // Return the newly-formed element set return ret; }
pushStack方法这个里面调用了jQuery对象的merge方法,比较蛋疼,等看到了merge方法再补充。