这篇文章比较繁杂,主要就是把jQuery源码从上到下列出来,看我的注释就好了。
jQuery源码对各种加载器做了处理。
//阅读这个源码是请先了解一下概念,即时函数,工厂模式 (function( global, factory ) { //这里之所以这样处理,是为了考虑CommonJS的环境,所以先检测是否有CommonJS的环境。 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 //有CommonJS环境则输出一个方法 module.exports = global.document ? //若全局环境有document,则工厂函数调用时传入第二个参数 factory( global, true ) : //若全局环境没有document,则返回给CommonJS加载器一个留待以后调用的函数。 function( w ) { if ( !w.document ) { throw new Error( "jQuery requires a window with a document" ); } return factory( w ); }; } else { //如果没有CommonJS环境,则直接执行函数获得全局jQuery方法。 factory( global ); } // Pass this if window is not defined yet //若全局环境里没有window(可能为NodeJS),则调用上下文传入this。 }(typeof window !== "undefined" ? window : this, function( window, noGlobal ) {
上面最后的匿名函数相当于初始化函数,下面是该初始化函数的内容
// Can't do this because several apps including ASP.NET trace // the stack via arguments.caller.callee and Firefox dies if // you try to trace through "use strict" call chains. (#13335) // Support: Firefox 18+ //这里我没看懂,补充链接 //http://developer.zdnet.com.cn/2007/0212/377947.shtml //http://blog.csdn.net/arwindgao/article/details/6592357 //缓存各个方法到当前作用域,因为避免了每次调用slice等函数时要进行更深层次的查找,这样可以提升性能,更多方式可看《高性能Javascript》 var deletedIds = []; var slice = deletedIds.slice; var concat = deletedIds.concat; var push = deletedIds.push; var indexOf = deletedIds.indexOf; //这个对象会在后面进行初始化,主要用来判断类型 var class2type = {}; var toString = class2type.toString; var hasOwn = class2type.hasOwnProperty; var trim = "".trim; //这个后面用来保存支持性信息 var support = {}; var version = "1.11.0", // Define a local copy of jQuery //jQuery函数即是$函数,第一个参数是选择符,第二个参数是使用选择符查找的上下文环境 // 比如要在父DOM里查找某子元素,$('body',document) 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 ); }, // Make sure we trim BOM and NBSP (here's looking at you, Safari 5.0 and IE) //此处的BOM是Byte Order Mark的缩写,字节序标记,为\uFEFF,必须去除 //\xA0则是latin01编码中的nbsp; rtrim = /^[\s\uFEFF\xA0]+|[\s\uFEFF\xA0]+$/g, // Matches dashed string for camelizing //后面这里主要负责把dash格式的变量名(webkit-transform)转换为驼峰格式(webkitTransform) rmsPrefix = /^-ms-/, rdashAlpha = /-([\da-z])/gi, // Used by jQuery.camelCase as callback to replace() //用来真正替换用的回调函数,后面真正用的时候的代码如下 //camelCase: function( string ) { // return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); //}, fcamelCase = function( all, letter ) { return letter.toUpperCase(); }; //须了解概念,原型和原型链。 //此处将jQuery工厂函数的原型设置为一个包含制定属性的对象。 jQuery.fn = jQuery.prototype = { // The current version of jQuery being used jquery: version, //覆盖工厂函数的原型时必须保证constructor属性依旧指向原工厂函数 constructor: jQuery, //最开始选择符为空 // Start with an empty selector selector: "", // The default length of a jQuery object is 0 length: 0, //调用数组的slice方法可以把this转换为数组。 //为什么要用这个方法?添加了自定义属性的jQuery对象还是数组,可以试试Array.isArray($('body')) toArray: function() { return slice.call( this ); }, // Get the Nth element in the matched element set OR // Get the whole matched element set as a clean array //传数区间为[-length,length] //和后面eq的区别是,get返回DOM元素,eq返回jQuery对象 get: function( num ) { return num != null ? // Return a 'clean' array //如果传入的num为负数,则变为取从队尾开始数第N个元素 ( num < 0 ? this[ num + this.length ] : this[ num ] ) : // Return just the object //之所以不调用toArray,是因为同样只需要一行代码,减轻耦合以及函数调用的开销 slice.call( this ); }, // Take an array of elements and push it onto the stack // (returning the new matched element set) //压栈函数,因为后面由部分工具需要改变 当前插找DOM元素的上下文,所以需要保存当前的环境。 //后续函数实现依赖于此函数,凡是需要改变当前匹配元素集的操作,都需要进行压栈操作 pushStack: function( elems ) { // Build a new jQuery matched element set //生成新的jQuery对象。 //为什么要用这种奇葩的方式生成,不能直接$(elems)生成? var ret = jQuery.merge( this.constructor(), elems ); // Add the old object onto the stack (as a reference) //prevObject指向之前的jQuery对象 ret.prevObject = this; ret.context = this.context; // Return the newly-formed element set return ret; }, // Execute a callback for every element in the matched set. // (You can seed the arguments with an array of args, but this is // only used internally.) //此处必须区分两个函数,jQuery.prototype.each和jQuery.each, //前者给由jQuery工厂函数生成的对象来使用,后者是以jQuery工厂函数为命名空间,把工具函数each绑定到jQuery工厂函数上,避免了全局污染。 //前者通过调用后者来实现。 //后面的很多函数实现都是采用这种方式。 each: function( callback, args ) { return jQuery.each( this, callback, args ); }, map: function( callback ) { //因为map会生成新的jQuery对象,所以要pushStack return this.pushStack( jQuery.map(this, function( elem, i ) { return callback.call( elem, i, elem ); })); }, slice: function() { return this.pushStack( slice.apply( this, arguments ) ); }, first: function() { return this.eq( 0 ); }, last: function() { return this.eq( -1 ); }, //eq取第几个元素然后生成jQuery对象 eq: function( i ) { var len = this.length, j = +i + ( i < 0 ? len : 0 ); //j = +i < 0 ? +i + len:+i 原写法比较好的是减少一次隐式转换 return this.pushStack( j >= 0 && j < len ? [ this[j] ] : [] ); }, //对于本次jQuery对象的操作结束,返回压栈前的jQuery对象。 end: function() { return this.prevObject || this.constructor(null); }, // For internal use only. // Behaves like an Array's method, not like a jQuery method. push: push, sort: deletedIds.sort, splice: deletedIds.splice }; //扩展对象函数 //接受三个参数,是否为深复制(可选),目标对象,源对象。 //之所以把深复制参数放在最前面,是因为方便增加源对象的个数。 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 //当传入的第一个参数是bool型 if ( typeof target === "boolean" ) { deep = target; // skip the boolean and the target //跳过深复制标识符,重新获得新的目标对象。 target = arguments[ i ] || {}; i++; } // Handle case when target is a string or something (possible in deep copy) //为什么target不能是Function?因为有可能覆盖Function的一些属性 if ( typeof target !== "object" && !jQuery.isFunction(target) ) { target = {}; } // extend jQuery itself if only one argument is passed //如果只传进来一个对象,则说明目标对象为本jQuery对象。 if ( i === length ) { target = this; i--; } //真正的复制开始了 for ( ; i < length; i++ ) { // Only deal with non-null/undefined values if ( (options = arguments[ i ]) != null ) { // Extend the base object for ( name in options ) { src = target[ name ]; copy = options[ name ];//待复制属性 // Prevent never-ending loop //如果待复制属性为目标对象,则不进行此次复制。 if ( target === copy ) { continue; } // Recurse if we're merging plain objects or arrays //如果进行深复制且待复制属性是朴素对象或队列 if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) { //深复制的时候要注意区分待复制的属性是对象还是数组 if ( copyIsArray ) { copyIsArray = false; //使用此变量的目的是为了减少一次isArray的调用 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 return target; }; //定义完扩展函数,就可以使用它了,添加一堆工具函数 jQuery.extend({ // Unique for each copy of jQuery on the page expando: "jQuery" + ( version + Math.random() ).replace( /\D/g, "" ), // Assume jQuery is ready without the ready module isReady: true, error: function( msg ) { throw new Error( msg ); }, noop: function() {}, // See test/unit/core.js for details concerning isFunction. // Since version 1.3, DOM methods and functions like alert // aren't supported. They return false on IE (#2968). //这个写法是处理之前遗留的bug isFunction: function( obj ) { return jQuery.type(obj) === "function"; }, //先检测是否有原生的isArray方法。 isArray: Array.isArray || function( obj ) { return jQuery.type(obj) === "array"; }, //通过检测对象下是否有window属性。。这个可以伪造。。 isWindow: function( obj ) { /* jshint eqeqeq: false */ return obj != null && obj == obj.window; }, //是否为数字 isNumeric: function( obj ) { // parseFloat NaNs numeric-cast false positives (null|true|false|"") // ...but misinterprets leading-number strings, particularly hex literals ("0x...") // subtraction forces infinities to NaN //当我们用typeof 来判断时,NaN和infinity都为number //这种方法可以检测字符串是否可以转换为数字。 return obj - parseFloat( obj ) >= 0; }, //检测空对象的方法 isEmptyObject: function( obj ) { var name; for ( name in obj ) { return false; } return true; }, //检测是否为朴素对象,不能为DOM和window,不能为由工厂函数生产出来的对象 //这里用了多种检测方式,值得学习 isPlainObject: function( obj ) { var key; // Must be an Object. // Because of IE, we also have to check the presence of the constructor property. // Make sure that DOM nodes and window objects don't pass through, as well if ( !obj || jQuery.type(obj) !== "object" || obj.nodeType || jQuery.isWindow( obj ) ) { return false; } try { // Not own constructor property must be Object //为什么这里要加两个叹号? if ( obj.constructor && !hasOwn.call(obj, "constructor") && !hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) { return false; } } catch ( e ) { // IE8,9 Will throw exceptions on certain host objects #9897 // In some very specific cases (ex: IE accessing window.location of another window) return false; } // Support: IE 0 && ( length - 1 ) in obj; }