這篇文章比較繁雜,主要就是把jQuery源碼從上到下列出來,看個人註釋就行了。javascript
jQuery源碼對各類加載器作了處理。html
//閱讀這個源碼是請先了解一下概念,即時函數,工廠模式 (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 ) {
上面最後的匿名函數至關於初始化函數,下面是該初始化函數的內容java
// 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; }