jQuery.extend方法是扩展jQuery本身的静态对象,各方法解释直接写在jQuery源码注释前:
jQuery.extend({ // 为了兼容别的javascript框架或者老版本的jQuery noConflict: function( deep ) { window.$ = _$; if ( deep ) { window.jQuery = _jQuery; } return jQuery; }, // Is the DOM ready to be used? Set to true once it occurs. isReady: false, // 常用的jQuery方法$.ready() // Handle when the DOM is ready ready: function() { // 通过jQuery.isReady这个标记位控制此方法只会被执行一次 // 因为jQuery绑了二个事件在DOM上,一是DOM就绪时,二是window.onload完成时,都会调用jQuery.ready方法 // Make sure that the DOM is not already loaded if ( !jQuery.isReady ) { // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). if ( !document.body ) { // 针对IE,延时13毫秒执行jQuery.ready()方法 return setTimeout( jQuery.ready, 13 ); } // Remember that the DOM is ready jQuery.isReady = true; // 遍历readyList数组(这是最外层的匿名闭包的内部局部变量),将数组内的方法逐个执行,参考jQuery.fn.ready方法 // If there are functions bound, to execute if ( readyList ) { // Execute all of them var fn, i = 0; while ( (fn = readyList[ i++ ]) ) { // 将方法fn作为document对象的方法进行调用,并将jQuery作为参数 fn.call( document, jQuery ); } // Reset the list of functions readyList = null; } // 执行触发器中自定义的ready事件 // Trigger any bound ready events if ( jQuery.fn.triggerHandler ) { jQuery( document ).triggerHandler( "ready" ); } } }, bindReady: function() { if ( readyBound ) { return; } readyBound = true; // IE8/safari/chrome中支持document.readyState属性,在文件加载完成后状态值为"complete" // 这个在IE中也可用于判断iframe的加载状态,IE中iframe.onload方法无效 // iframe.onreadystatechange = function(){ // if (iframe.readyState == 'complete') { // // doing something; // } // }; // Catch cases where $(document).ready() is called after the // browser event has already occurred. if ( document.readyState === "complete" ) { return jQuery.ready(); } // DOMContentLoaded方法因浏览器不同而不同 // Mozilla, Opera and webkit nightlies currently support this event if ( document.addEventListener ) { // Use the handy event callback document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false ); // 在Mozilla/Opera/webkit这几个浏览器上可以通过绑定window.onload事件,执行jQuery.ready方法 // 这是备用策略,避免DOMContentLoaded方法没有被执行时,可以在window.onload之后运行jQuery.ready方法 // A fallback to window.onload, that will always work window.addEventListener( "load", jQuery.ready, false ); // If IE event model is used } else if ( document.attachEvent ) { // ensure firing before onload, // maybe late but safe also for iframes document.attachEvent("onreadystatechange", DOMContentLoaded); // A fallback to window.onload, that will always work window.attachEvent( "onload", jQuery.ready ); // If IE and not a frame // continually check to see if the document is ready var toplevel = false; try { toplevel = window.frameElement == null; // 这句代码在浏览器中测试过,不知如何才能发生异常 } catch(e) {} if ( document.documentElement.doScroll && toplevel ) { doScrollCheck(); } } }, // 下面5个方法是为了测试javascript对象的类型 // Determine if the argument passed is a Javascript function object. // 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). isFunction: function( obj ) { return toString.call(obj) === "[object Function]"; }, // Determine whether the argument is an array. isArray: function( obj ) { return toString.call(obj) === "[object Array]"; }, // Check to see if an object is a plain object (created using "{}" or "new Object"). isPlainObject: function( obj ) { // 检查obj是否一个object直接量,用{}或者new Object生成 // 避免是DOM对象和window对象 // 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 || toString.call(obj) !== "[object Object]" || obj.nodeType || obj.setInterval ) { return false; } // Not own constructor property must be Object if ( obj.constructor && !hasOwnProperty.call(obj, "constructor") && !hasOwnProperty.call(obj.constructor.prototype, "isPrototypeOf") ) { return false; } // Own properties are enumerated firstly, so to speed up, // if last one is own, then all properties are own. var key; for ( key in obj ) {} // 如果obj={},那么key=undefined,要不然key就是obj对象的最后一个属性,符合条件返回true return key === undefined || hasOwnProperty.call( obj, key ); }, // 测试是否空对象,没有任何自身属性和继承属性 // Check to see if an object is empty (contains no properties). isEmptyObject: function( obj ) { for ( var name in obj ) { return false; } return true; }, error: function( msg ) { throw msg; }, // 将JSON字符串解析后返回一个合法的javascript Object // JSON字符串内的所有字符串需要用双引号括起来,不能用单引号,如'{"test": 1}',而不能是"{'test': 1}" // Takes a well-formed JSON string and returns the resulting JavaScript object. parseJSON: function( data ) { if ( typeof data !== "string" || !data ) { return null; } // Make sure leading/trailing whitespace is removed (IE can't handle it) data = jQuery.trim( data ); // For example, the following are all malformed JSON strings: // * {test: 1} (test does not have double quotes around it). // * {'test': 1} ('test' is using single quotes instead of double quotes). // Make sure the incoming data is actual JSON // Logic borrowed from http://json.org/json2.js if ( /^[\],:{}\s]*$/.test(data.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, "@") .replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, "]") .replace(/(?:^|:|,)(?:\s*\[)+/g, "")) ) { // 如果浏览器本身提供了window.JSON.parse方法,则使用之,否则构造匿名函数并调用之 // Try to use the native JSON parser first return window.JSON && window.JSON.parse ? window.JSON.parse( data ) : (new Function("return " + data))(); } else { jQuery.error( "Invalid JSON: " + data ); } }, // noop: 无操作,停止操作指令 // 一个空方法,这对想提供一个回调参数的插件开发者很有用,如果使用者没有传入callback时,可以用noop代替 noop: function() {}, // 在全局环境中执行data组成的javascript字符串,这个方法不同于javascript中的Global方法eval() // 可以通过这个方法跨域加载外部javascript文件,参考jQuery.ajax中type="script"的调用方式 globalEval: function( data ) { if ( data && rnotwhite.test(data) ) { // Inspired by code by Andrea Giammarchi // 这个方法在javascript权威指南中已经有出现 // http://webreflection.blogspot.com/2007/08/global-scope-evaluation-and-dom.html var head = document.getElementsByTagName("head")[0] || document.documentElement, script = document.createElement("script"); script.type = "text/javascript"; if ( jQuery.support.scriptEval ) { script.appendChild( document.createTextNode( data ) ); } else { script.text = data; } // 将script插入head的头部之后,浏览器会自动运行script标签中的javascript代码,在执行之后立即将此script对象从head中移除 // Use insertBefore instead of appendChild to circumvent an IE6 bug. // This arises when a base node is used (#2709). head.insertBefore( script, head.firstChild ); head.removeChild( script ); } }, // 判断元素的标签名称是否与传入的name一致 nodeName: function( elem, name ) { return elem.nodeName && elem.nodeName.toUpperCase() === name.toUpperCase(); }, // jQuery.each( collection, callback(indexInArray, valueOfElement) ) // collection: 可以是array,object,或者是NodeList等集合对象 // 非常重要的方法,用于遍历数组或者对象,回调方法中第一个参数是数组的index值或者对象的name属性,第二个参数是对应的Value值 // 另外此处需要注意的是,传给each方法的object是对方法外部对象的一个引用,如果在callback内对此object作了修改,会导致运行错误,如用此方法删除jQuery匹配的节点,因为object中的结点被删除,而导致object本身被修改,可以each方法中的i仍然在增加,最后就会部分节点未被删除,而且最后因尝试删除object中已经不存在的index对应的节点而抛错: // $.each(document.getElementsByTagName('div'), function(){this.parentNode.removeChild(this)}); // A generic iterator function, which can be used to seamlessly iterate over both objects and arrays. // Arrays and array-like objects with a length property (such as a function's arguments object) are iterated by numeric index, // from 0 to length-1. Other objects are iterated via their named properties. // args is for internal usage only each: function( object, callback, args ) { var name, i = 0, length = object.length, isObj = length === undefined || jQuery.isFunction(object); if ( args ) { if ( isObj ) { for ( name in object ) { // callback是作为object[name]对象的方法调用的,所以在callback中的this就是指向object[name]的 if ( callback.apply( object[ name ], args ) === false ) { break; } } } else { for ( ; i < length; ) { if ( callback.apply( object[ i++ ], args ) === false ) { break; } } } // A special, fast, case for the most common use of each } else { if ( isObj ) { for ( name in object ) { if ( callback.call( object[ name ], name, object[ name ] ) === false ) { break; } } } else { for ( var value = object[0]; i < length && callback.call( value, i, value ) !== false; value = object[++i] ) {} } } return object; }, // jQuery对字符串操作的一个功能扩写 // Remove the whitespace from the beginning and end of a string. trim: function( text ) { return (text || "").replace( rtrim, "" ); }, // 将一个类数组的对象转化为一个真正的javascript数组 // results is for internal usage only makeArray: function( array, results ) { var ret = results || []; if ( array != null ) { // The window, strings (and functions) also have 'length' // The extra typeof function check is to prevent crashes // in Safari 2 (See: #3039) if ( array.length == null || typeof array === "string" || jQuery.isFunction(array) || (typeof array !== "function" && array.setInterval) ) { push.call( ret, array ); } else { jQuery.merge( ret, array ); } } return ret; }, // 在一个数组中搜索指定的值elem,如果找到返回其位置,没找到返回-1 // 所以最后要用方法执行结果与-1比较,看此elem是否在此数组中 inArray: function( elem, array ) { if ( array.indexOf ) { return array.indexOf( elem ); } for ( var i = 0, length = array.length; i < length; i++ ) { if ( array[ i ] === elem ) { return i; } } return -1; }, // jQuery源码sizzle部分代码关于makeArray()方法有如下注释说明: // Perform a simple check to determine if the browser is capable of converting a NodeList to an array using builtin methods. // Also verifies that the returned array holds DOM nodes (which is not the case in the Blackberry browser) // 在IE中,NodeList集合并不是一个object,不能直接用Array.prototype.slice.call(NodeList)转化成一个真实的数组,必须使用for循环重新创建新数组 // In Internet Explorer it throws an error that it can't run Array.prototype.slice.call(nodes) because a DOM NodeList is not a JavaScript object. // 将第二个数组(对象)合并到第一个数组(对象)中,最后返回第一个数组(对象) merge: function( first, second ) { var i = first.length, j = 0; if ( typeof second.length === "number" ) { for ( var l = second.length; j < l; j++ ) { first[ i++ ] = second[ j ]; } } else { // 如果第2个参数不是数组,而是一个object,但有数字的key,并且key值从0开始找 // example: $.merge([], {a: 1, "0": "jquery", "2": "javascript"}); while ( second[j] !== undefined ) { first[ i++ ] = second[ j++ ]; } } first.length = i; return first; }, // function(elementOfArray, indexInArray) // The function to process each item against. The first argument to the function is the item, and the second argument is the index. // The function should return a Boolean value. this will be the global window object. // invert: If "invert" is false, or not provided, then the function returns an array consisting of all elements for which "callback" returns true. // If "invert" is true, then the function returns an array consisting of all elements for which "callback" returns false. // jQuery.grep( array, function(elementOfArray, indexInArray), [ invert ] ) // 从一个array中查找符合回调函数的元素,返回一个新的数组,原来的数组不会被影响 grep: function( elems, callback, inv ) { var ret = []; // Go through the array, only saving the items // that pass the validator function for ( var i = 0, length = elems.length; i < length; i++ ) { if ( !inv !== !callback( elems[ i ], i ) ) { ret.push( elems[ i ] ); } } return ret; }, // 将一个类数组的对象,用回调函数对每个元素进行map,返回一个新的对象 // arg is for internal usage only map: function( elems, callback, arg ) { var ret = [], value; // Go through the array, translating each of the items to their // new value (or values). for ( var i = 0, length = elems.length; i < length; i++ ) { value = callback( elems[ i ], i, arg ); if ( value != null ) { ret[ ret.length ] = value; } } return ret.concat.apply( [], ret ); }, // 在jQuery事件处理中会用到此值,每个事件处理器都有唯一的guid // A global GUID counter for objects guid: 1, // jQuery.proxy()方法有2个作用,因为javascript中没有重载,所以通过参数位数和类型在代码里区分这个方法要执行的功能 // 一个用法是,将指定的方法绑定到其他作用域上,并返回这个代理过的方法 // 参考Prototype中的Function.bind()方法,可以处理方法中this的引用问题 // 另一个用法是起到事件代理的作用,通过handler.guid控制 // jQuery.proxy( function, context ) // function: The function whose context will be changed. // context: The object to which the context (`this`) of the function should be set. // jQuery.proxy( context, name ) // context: The object to which the context of the function should be set. // name: The name of the function whose context will be changed (should be a property of the 'context' object. // This method is most useful for attaching event handlers to an element where the context is pointing back to a different object. // Additionally, jQuery makes sure that even if you bind the function returned from jQuery.proxy() it will still unbind the correct function, if passed the original. proxy: function( fn, proxy, thisObject ) { // 这个方法的使用一般是传入2个参数的 if ( arguments.length === 2 ) { // 传入2个参数,第二个参数为String时,则将第一个参数视为context,第二个参数则必须为其所属的方法名 if ( typeof proxy === "string" ) { // jQuery.proxy( context, name ) // context: The object to which the context of the function should be set. // name: The name of the function whose context will be changed (should be a property of the 'context' object). thisObject = fn; fn = thisObject[ proxy ]; proxy = undefined; } else if ( proxy && !jQuery.isFunction( proxy ) ) { // jQuery.proxy( function, context ) // function: The function whose context will be changed. // context: The object to which the context (`this`) of the function should be set. // 第一个参数是一个function,第二个参数是此function要执行的环境 thisObject = proxy; proxy = undefined; } // 还有一种情况,就是传入的二个参数都是function,这里暂时不作处理,后面会另外处理 } // 如果是传入了3个参数,并且第二个参数proxy的值取反为true,则将fn绑定作用域到第三个参数thisObject上,一般不会这么使用,这个if主要还是处理2个参数的情况,改变方法的执行作用域,但是jQuery的源码没有将这段代码放进前面的if块中,也可以用之处理只传进来1个参数的情况和3个参数的情况 // 如果是传入了2个参数,经上面的if处理之后,proxy为undefined // 如果只传了1个参数,proxy也是undefined,那就可以直接将之做为全局方法运行(this指向window) if ( !proxy && fn ) { // 重新设置proxy,这里和Prototype.js中的bind方法作用相类似 proxy = function() { // 将fn作为thisObject的方法调用,即fn中的this是指向thisObject的 // 如果没有thisObject,则this指向调用proxy方法的对象或者是window(如果在全局环境中直接调用proxy) return fn.apply( thisObject || this, arguments ); }; } // 如果当前方法是传入3个参数,并且proxy实际传入了一个对象(取反不为true,最后仍要返回这个proxy对象),则只是为了fn和proxy添加一个全局唯一的标识符后,返回proxy,这时就没有绑定作用域到thisObject上,也不会绑定到proxy对象上 // 如果当前方法是传入2个参数,并且二个参数都是function,则为fn和proxy添加一个全局唯一的标识符后,返回proxy // 在2个方法上绑定相同的guid时,当这2个方法都做为事件监听器时,移除其中任意一个监听器,会因为它们具有相同的guid,同时将另一个监听器一起移除,具体可查看jQuery.event.remove()方法 // 每个proxy方法都有一个全局唯一标识符对应,可以此标识符删除proxy // Set the guid of unique handler to the same of original handler, so it can be removed if ( fn ) { // 在原来的版本中,jQuery.event.proxy()方法只是用于事件处理器的代理,将多个事件处理器绑定相同的guid,这样可以同时被删除 proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++; } // So proxy can be declared as an argument return proxy; }, // userAgent匹配 // Use of jQuery.browser is frowned upon. 不建议使用jQuery.browser // More details: http://docs.jquery.com/Utilities/jQuery.browser uaMatch: function( ua ) { ua = ua.toLowerCase(); var match = /(webkit)[ \/]([\w.]+)/.exec( ua ) || /(opera)(?:.*version)?[ \/]([\w.]+)/.exec( ua ) || /(msie) ([\w.]+)/.exec( ua ) || !/compatible/.test( ua ) && /(mozilla)(?:.*? rv:([\w.]+))?/.exec( ua ) || []; return { browser: match[1] || "", version: match[2] || "0" }; }, browser: {}}); |
在bindReady方法中用到的DOMContentLoaded方法,是根据浏览器不同而设置的事件处理器:// Cleanup functions for the document ready methodif ( document.addEventListener ) { DOMContentLoaded = function() { document.removeEventListener( "DOMContentLoaded", DOMContentLoaded, false ); jQuery.ready(); };} else if ( document.attachEvent ) { DOMContentLoaded = function() { // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). if ( document.readyState === "complete" ) { document.detachEvent( "onreadystatechange", DOMContentLoaded ); jQuery.ready(); } };}