/*! * jQuery JavaScript Library v1.7.2 * http://jquery.com/ * * Copyright 2011, John Resig * Dual licensed under the MIT or GPL Version 2 licenses. * http://jquery.org/license * * Includes Sizzle.js * http://sizzlejs.com/ * Copyright 2011, The Dojo Foundation * Released under the MIT, BSD, and GPL Licenses. * * Date: Wed Mar 21 12:46:34 2012 -0700 */ (function( window, undefined ) { // Use the correct document accordingly with window argument (sandbox) var document = window.document, navigator = window.navigator, location = window.location; var jQuery = (function() { // Define a local copy of jQuery var jQuery = function( selector, context ) { // The jQuery object is actually just the init constructor 'enhanced' return new jQuery.fn.init( selector, context, rootjQuery ); }, // Map over jQuery in case of overwrite _jQuery = window.jQuery, // Map over the $ in case of overwrite _$ = window.$, // A central reference to the root jQuery(document) rootjQuery, // A simple way to check for HTML strings or ID strings // Prioritize #id over to avoid XSS via location.hash (#9521) quickExpr = /^(?:[^#<]*(<[\w\W]+>)[^>]*$|#([\w\-]*)$)/, // Check if a string has a non-whitespace character in it rnotwhite = /\S/, // Used for trimming whitespace trimLeft = /^\s+/, trimRight = /\s+$/, // Match a standalone tag rsingleTag = /^<(\w+)\s*\/?>(?:<\/\1>)?$/, // JSON RegExp rvalidchars = /^[\],:{}\s]*$/, rvalidescape = /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, rvalidtokens = /"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, rvalidbraces = /(?:^|:|,)(?:\s*\[)+/g, // Useragent RegExp rwebkit = /(webkit)[ \/]([\w.]+)/, ropera = /(opera)(?:.*version)?[ \/]([\w.]+)/, rmsie = /(msie) ([\w.]+)/, rmozilla = /(mozilla)(?:.*? rv:([\w.]+))?/, // Matches dashed string for camelizing rdashAlpha = /-([a-z]|[0-9])/ig, rmsPrefix = /^-ms-/, // Used by jQuery.camelCase as callback to replace() fcamelCase = function( all, letter ) { return ( letter + "" ).toUpperCase(); }, // Keep a UserAgent string for use with jQuery.browser userAgent = navigator.userAgent, // For matching the engine and version of the browser browserMatch, // The deferred used on DOM ready readyList, // The ready event handler DOMContentLoaded, // Save a reference to some core methods toString = Object.prototype.toString, hasOwn = Object.prototype.hasOwnProperty, push = Array.prototype.push, slice = Array.prototype.slice, trim = String.prototype.trim, indexOf = Array.prototype.indexOf, // [[Class]] -> type pairs class2type = {}; jQuery.fn = jQuery.prototype = { constructor: jQuery, init: function( selector, context, rootjQuery ) { var match, elem, ret, doc; // Handle $(""), $(null), or $(undefined) if ( !selector ) { return this; } // Handle $(DOMElement) if ( selector.nodeType ) { this.context = this[0] = selector; this.length = 1; return this; } // The body element only exists once, optimize finding it if ( selector === "body" && !context && document.body ) { this.context = document; this[0] = document.body; this.selector = selector; this.length = 1; return this; } // Handle HTML strings if ( typeof selector === "string" ) { // Are we dealing with HTML string or an ID? if ( selector.charAt(0) === "<" && selector.charAt( selector.length - 1 ) === ">" && selector.length >= 3 ) { // Assume that strings that start and end with <> are HTML and skip the regex check match = [ null, selector, null ]; } else { match = quickExpr.exec( selector ); } // Verify a match, and that no context was specified for #id if ( match && (match[1] || !context) ) { // HANDLE: $(html) -> $(array) if ( match[1] ) { context = context instanceof jQuery ? context[0] : context; doc = ( context ? context.ownerDocument || context : document ); // If a single string is passed in and it's a single tag // just do a createElement and skip the rest ret = rsingleTag.exec( selector ); if ( ret ) { if ( jQuery.isPlainObject( context ) ) { selector = [ document.createElement( ret[1] ) ]; jQuery.fn.attr.call( selector, context, true ); } else { selector = [ doc.createElement( ret[1] ) ]; } } else { ret = jQuery.buildFragment( [ match[1] ], [ doc ] ); selector = ( ret.cacheable ? jQuery.clone(ret.fragment) : ret.fragment ).childNodes; } return jQuery.merge( this, selector ); // HANDLE: $("#id") } else { elem = document.getElementById( match[2] ); // Check parentNode to catch when Blackberry 4.6 returns // nodes that are no longer in the document #6963 if ( elem && elem.parentNode ) { // Handle the case where IE and Opera return items // by name instead of ID if ( elem.id !== match[2] ) { return rootjQuery.find( selector ); } // Otherwise, we inject the element directly into the jQuery object this.length = 1; this[0] = elem; } this.context = document; this.selector = selector; return this; } // HANDLE: $(expr, $(...)) } else if ( !context || context.jquery ) { return ( context || rootjQuery ).find( selector ); // HANDLE: $(expr, context) // (which is just equivalent to: $(context).find(expr) } else { return this.constructor( context ).find( selector ); } // HANDLE: $(function) // Shortcut for document ready } else if ( jQuery.isFunction( selector ) ) { return rootjQuery.ready( selector ); } if ( selector.selector !== undefined ) { this.selector = selector.selector; this.context = selector.context; } return jQuery.makeArray( selector, this ); }, // Start with an empty selector selector: "", // The current version of jQuery being used jquery: "1.7.2", // The default length of a jQuery object is 0 length: 0, // The number of elements contained in the matched element set size: function() { return this.length; }, toArray: function() { return slice.call( this, 0 ); }, // Get the Nth element in the matched element set OR // Get the whole matched element set as a clean array get: function( num ) { return num == null ? // Return a 'clean' array this.toArray() : // Return just the object ( num < 0 ? this[ this.length + num ] : this[ num ] ); }, // Take an array of elements and push it onto the stack // (returning the new matched element set) pushStack: function( elems, name, selector ) { // Build a new jQuery matched element set var ret = this.constructor(); if ( jQuery.isArray( elems ) ) { push.apply( ret, elems ); } else { jQuery.merge( ret, elems ); } // Add the old object onto the stack (as a reference) ret.prevObject = this; ret.context = this.context; if ( name === "find" ) { ret.selector = this.selector + ( this.selector ? " " : "" ) + selector; } else if ( name ) { ret.selector = this.selector + "." + name + "(" + selector + ")"; } // 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.) each: function( callback, args ) { return jQuery.each( this, callback, args ); }, ready: function( fn ) { // Attach the listeners jQuery.bindReady(); // Add the callback readyList.add( fn ); return this; }, eq: function( i ) { i = +i; return i === -1 ? this.slice( i ) : this.slice( i, i + 1 ); }, first: function() { return this.eq( 0 ); }, last: function() { return this.eq( -1 ); }, slice: function() { return this.pushStack( slice.apply( this, arguments ), "slice", slice.call(arguments).join(",") ); }, map: function( callback ) { return this.pushStack( jQuery.map(this, function( elem, i ) { return callback.call( elem, i, elem ); })); }, 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: [].sort, splice: [].splice }; // Give the init function the jQuery prototype for later instantiation jQuery.fn.init.prototype = jQuery.fn; jQuery.extend = jQuery.fn.extend = function() { var options, name, src, copy, copyIsArray, clone, target = arguments[0] || {}, i = 1, length = arguments.length, deep = false; // Handle a deep copy situation if ( typeof target === "boolean" ) { deep = target; target = arguments[1] || {}; // skip the boolean and the target i = 2; } // Handle case when target is a string or something (possible in deep copy) if ( typeof target !== "object" && !jQuery.isFunction(target) ) { target = {}; } // extend jQuery itself if only one argument is passed if ( length === i ) { 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; 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({ noConflict: function( deep ) { if ( window.$ === jQuery ) { window.$ = _$; } if ( deep && window.jQuery === jQuery ) { window.jQuery = _jQuery; } return jQuery; }, // Is the DOM ready to be used? Set to true once it occurs. isReady: false, // A counter to track how many items to wait for before // the ready event fires. See #6781 readyWait: 1, // Hold (or release) the ready event holdReady: function( hold ) { if ( hold ) { jQuery.readyWait++; } else { jQuery.ready( true ); } }, // Handle when the DOM is ready ready: function( wait ) { // Either a released hold or an DOMready/load event and not yet ready if ( (wait === true && !--jQuery.readyWait) || (wait !== true && !jQuery.isReady) ) { // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). if ( !document.body ) { return setTimeout( jQuery.ready, 1 ); } // Remember that the DOM is ready jQuery.isReady = true; // If a normal DOM Ready event fired, decrement, and wait if need be if ( wait !== true && --jQuery.readyWait > 0 ) { return; } // If there are functions bound, to execute readyList.fireWith( document, [ jQuery ] ); // Trigger any bound ready events if ( jQuery.fn.trigger ) { jQuery( document ).trigger( "ready" ).off( "ready" ); } } }, bindReady: function() { if ( readyList ) { return; } readyList = jQuery.Callbacks( "once memory" ); // Catch cases where $(document).ready() is called after the // browser event has already occurred. if ( document.readyState === "complete" ) { // Handle it asynchronously to allow scripts the opportunity to delay ready return setTimeout( jQuery.ready, 1 ); } // Mozilla, Opera and webkit nightlies currently support this event if ( document.addEventListener ) { // Use the handy event callback document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false ); // 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(); } } }, // 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 jQuery.type(obj) === "function"; }, isArray: Array.isArray || function( obj ) { return jQuery.type(obj) === "array"; }, isWindow: function( obj ) { return obj != null && obj == obj.window; }, isNumeric: function( obj ) { return !isNaN( parseFloat(obj) ) && isFinite( obj ); }, type: function( obj ) { return obj == null ? String( obj ) : class2type[ toString.call(obj) ] || "object"; }, isPlainObject: function( obj ) { // 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 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 ) {} return key === undefined || hasOwn.call( obj, key ); }, isEmptyObject: function( obj ) { for ( var name in obj ) { return false; } return true; }, error: function( msg ) { throw new Error( msg ); }, 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 ); // Attempt to parse using the native JSON parser first if ( window.JSON && window.JSON.parse ) { return window.JSON.parse( data ); } // Make sure the incoming data is actual JSON // Logic borrowed from http://json.org/json2.js if ( rvalidchars.test( data.replace( rvalidescape, "@" ) .replace( rvalidtokens, "]" ) .replace( rvalidbraces, "")) ) { return ( new Function( "return " + data ) )(); } jQuery.error( "Invalid JSON: " + data ); }, // Cross-browser xml parsing parseXML: function( data ) { if ( typeof data !== "string" || !data ) { return null; } var xml, tmp; try { if ( window.DOMParser ) { // Standard tmp = new DOMParser(); xml = tmp.parseFromString( data , "text/xml" ); } else { // IE xml = new ActiveXObject( "Microsoft.XMLDOM" ); xml.async = "false"; xml.loadXML( data ); } } catch( e ) { xml = undefined; } if ( !xml || !xml.documentElement || xml.getElementsByTagName( "parsererror" ).length ) { jQuery.error( "Invalid XML: " + data ); } return xml; }, noop: function() {}, // Evaluates a script in a global context // Workarounds based on findings by Jim Driscoll // http://weblogs.java.net/blog/driscoll/archive/2009/09/08/eval-javascript-global-context globalEval: function( data ) { if ( data && rnotwhite.test( data ) ) { // We use execScript on Internet Explorer // We use an anonymous function so that context is window // rather than jQuery in Firefox ( window.execScript || function( data ) { window[ "eval" ].call( window, data ); } )( data ); } }, // Convert dashed to camelCase; used by the css and data modules // Microsoft forgot to hump their vendor prefix (#9572) camelCase: function( string ) { return string.replace( rmsPrefix, "ms-" ).replace( rdashAlpha, fcamelCase ); }, nodeName: function( elem, name ) { return elem.nodeName && elem.nodeName.toUpperCase() === name.toUpperCase(); }, // 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 ) { 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 ( ; i < length; ) { if ( callback.call( object[ i ], i, object[ i++ ] ) === false ) { break; } } } } return object; }, // Use native String.trim function wherever possible trim: trim ? function( text ) { return text == null ? "" : trim.call( text ); } : // Otherwise use our own trimming functionality function( text ) { return text == null ? "" : text.toString().replace( trimLeft, "" ).replace( trimRight, "" ); }, // results is for internal usage only makeArray: function( array, results ) { var ret = results || []; if ( array != null ) { // The window, strings (and functions) also have 'length' // Tweaked logic slightly to handle Blackberry 4.7 RegExp issues #6930 var type = jQuery.type( array ); if ( array.length == null || type === "string" || type === "function" || type === "regexp" || jQuery.isWindow( array ) ) { push.call( ret, array ); } else { jQuery.merge( ret, array ); } } return ret; }, inArray: function( elem, array, i ) { var len; if ( array ) { if ( indexOf ) { return indexOf.call( array, elem, i ); } len = array.length; i = i ? i < 0 ? Math.max( 0, len + i ) : i : 0; for ( ; i < len; i++ ) { // Skip accessing in sparse arrays if ( i in array && array[ i ] === elem ) { return i; } } } return -1; }, 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 { while ( second[j] !== undefined ) { first[ i++ ] = second[ j++ ]; } } first.length = i; return first; }, grep: function( elems, callback, inv ) { var ret = [], retVal; inv = !!inv; // Go through the array, only saving the items // that pass the validator function for ( var i = 0, length = elems.length; i < length; i++ ) { retVal = !!callback( elems[ i ], i ); if ( inv !== retVal ) { ret.push( elems[ i ] ); } } return ret; }, // arg is for internal usage only map: function( elems, callback, arg ) { var value, key, ret = [], i = 0, length = elems.length, // jquery objects are treated as arrays isArray = elems instanceof jQuery || length !== undefined && typeof length === "number" && ( ( length > 0 && elems[ 0 ] && elems[ length -1 ] ) || length === 0 || jQuery.isArray( elems ) ) ; // Go through the array, translating each of the items to their if ( isArray ) { for ( ; i < length; i++ ) { value = callback( elems[ i ], i, arg ); if ( value != null ) { ret[ ret.length ] = value; } } // Go through every key on the object, } else { for ( key in elems ) { value = callback( elems[ key ], key, arg ); if ( value != null ) { ret[ ret.length ] = value; } } } // Flatten any nested arrays return ret.concat.apply( [], ret ); }, // A global GUID counter for objects guid: 1, // Bind a function to a context, optionally partially applying any // arguments. proxy: function( fn, context ) { if ( typeof context === "string" ) { var tmp = fn[ context ]; context = fn; fn = tmp; } // Quick check to determine if target is callable, in the spec // this throws a TypeError, but we will just return undefined. if ( !jQuery.isFunction( fn ) ) { return undefined; } // Simulated bind var args = slice.call( arguments, 2 ), proxy = function() { return fn.apply( context, args.concat( slice.call( arguments ) ) ); }; // Set the guid of unique handler to the same of original handler, so it can be removed proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++; return proxy; }, // Mutifunctional method to get and set values to a collection // The value/s can optionally be executed if it's a function access: function( elems, fn, key, value, chainable, emptyGet, pass ) { var exec, bulk = key == null, i = 0, length = elems.length; // Sets many values if ( key && typeof key === "object" ) { for ( i in key ) { jQuery.access( elems, fn, i, key[i], 1, emptyGet, value ); } chainable = 1; // Sets one value } else if ( value !== undefined ) { // Optionally, function values get executed if exec is true exec = pass === undefined && jQuery.isFunction( value ); if ( bulk ) { // Bulk operations only iterate when executing function values if ( exec ) { exec = fn; fn = function( elem, key, value ) { return exec.call( jQuery( elem ), value ); }; // Otherwise they run against the entire set } else { fn.call( elems, value ); fn = null; } } if ( fn ) { for (; i < length; i++ ) { fn( elems[i], key, exec ? value.call( elems[i], i, fn( elems[i], key ) ) : value, pass ); } } chainable = 1; } return chainable ? elems : // Gets bulk ? fn.call( elems ) : length ? fn( elems[0], key ) : emptyGet; }, now: function() { return ( new Date() ).getTime(); }, // Use of jQuery.browser is frowned upon. // More details: http://docs.jquery.com/Utilities/jQuery.browser uaMatch: function( ua ) { ua = ua.toLowerCase(); var match = rwebkit.exec( ua ) || ropera.exec( ua ) || rmsie.exec( ua ) || ua.indexOf("compatible") < 0 && rmozilla.exec( ua ) || []; return { browser: match[1] || "", version: match[2] || "0" }; }, sub: function() { function jQuerySub( selector, context ) { return new jQuerySub.fn.init( selector, context ); } jQuery.extend( true, jQuerySub, this ); jQuerySub.superclass = this; jQuerySub.fn = jQuerySub.prototype = this(); jQuerySub.fn.constructor = jQuerySub; jQuerySub.sub = this.sub; jQuerySub.fn.init = function init( selector, context ) { if ( context && context instanceof jQuery && !(context instanceof jQuerySub) ) { context = jQuerySub( context ); } return jQuery.fn.init.call( this, selector, context, rootjQuerySub ); }; jQuerySub.fn.init.prototype = jQuerySub.fn; var rootjQuerySub = jQuerySub(document); return jQuerySub; }, browser: {} }); // Populate the class2type map jQuery.each("Boolean Number String Function Array Date RegExp Object".split(" "), function(i, name) { class2type[ "[object " + name + "]" ] = name.toLowerCase(); }); browserMatch = jQuery.uaMatch( userAgent ); if ( browserMatch.browser ) { jQuery.browser[ browserMatch.browser ] = true; jQuery.browser.version = browserMatch.version; } // Deprecated, use jQuery.browser.webkit instead if ( jQuery.browser.webkit ) { jQuery.browser.safari = true; } // IE doesn't match non-breaking spaces with \s if ( rnotwhite.test( "\xA0" ) ) { trimLeft = /^[\s\xA0]+/; trimRight = /[\s\xA0]+$/; } // All jQuery objects should point back to these rootjQuery = jQuery(document); // Cleanup functions for the document ready method if ( 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(); } }; } // The DOM ready check for Internet Explorer function doScrollCheck() { if ( jQuery.isReady ) { return; } try { // If IE is used, use the trick by Diego Perini // http://javascript.nwbox.com/IEContentLoaded/ document.documentElement.doScroll("left"); } catch(e) { setTimeout( doScrollCheck, 1 ); return; } // and execute any waiting functions jQuery.ready(); } return jQuery; })(); // String to Object flags format cache var flagsCache = {}; // Convert String-formatted flags into Object-formatted ones and store in cache function createFlags( flags ) { var object = flagsCache[ flags ] = {}, i, length; flags = flags.split( /\s+/ ); for ( i = 0, length = flags.length; i < length; i++ ) { object[ flags[i] ] = true; } return object; } /* * Create a callback list using the following parameters: * * flags: an optional list of space-separated flags that will change how * the callback list behaves * * By default a callback list will act like an event callback list and can be * "fired" multiple times. * * Possible flags: * * once: will ensure the callback list can only be fired once (like a Deferred) * * memory: will keep track of previous values and will call any callback added * after the list has been fired right away with the latest "memorized" * values (like a Deferred) * * unique: will ensure a callback can only be added once (no duplicate in the list) * * stopOnFalse: interrupt callings when a callback returns false * */ jQuery.Callbacks = function( flags ) { // Convert flags from String-formatted to Object-formatted // (we check in cache first) flags = flags ? ( flagsCache[ flags ] || createFlags( flags ) ) : {}; var // Actual callback list list = [], // Stack of fire calls for repeatable lists stack = [], // Last fire value (for non-forgettable lists) memory, // Flag to know if list was already fired fired, // Flag to know if list is currently firing firing, // First callback to fire (used internally by add and fireWith) firingStart, // End of the loop when firing firingLength, // Index of currently firing callback (modified by remove if needed) firingIndex, // Add one or several callbacks to the list add = function( args ) { var i, length, elem, type, actual; for ( i = 0, length = args.length; i < length; i++ ) { elem = args[ i ]; type = jQuery.type( elem ); if ( type === "array" ) { // Inspect recursively add( elem ); } else if ( type === "function" ) { // Add if not in unique mode and callback is not in if ( !flags.unique || !self.has( elem ) ) { list.push( elem ); } } } }, // Fire callbacks fire = function( context, args ) { args = args || []; memory = !flags.memory || [ context, args ]; fired = true; firing = true; firingIndex = firingStart || 0; firingStart = 0; firingLength = list.length; for ( ; list && firingIndex < firingLength; firingIndex++ ) { if ( list[ firingIndex ].apply( context, args ) === false && flags.stopOnFalse ) { memory = true; // Mark as halted break; } } firing = false; if ( list ) { if ( !flags.once ) { if ( stack && stack.length ) { memory = stack.shift(); self.fireWith( memory[ 0 ], memory[ 1 ] ); } } else if ( memory === true ) { self.disable(); } else { list = []; } } }, // Actual Callbacks object self = { // Add a callback or a collection of callbacks to the list add: function() { if ( list ) { var length = list.length; add( arguments ); // Do we need to add the callbacks to the // current firing batch? if ( firing ) { firingLength = list.length; // With memory, if we're not firing then // we should call right away, unless previous // firing was halted (stopOnFalse) } else if ( memory && memory !== true ) { firingStart = length; fire( memory[ 0 ], memory[ 1 ] ); } } return this; }, // Remove a callback from the list remove: function() { if ( list ) { var args = arguments, argIndex = 0, argLength = args.length; for ( ; argIndex < argLength ; argIndex++ ) { for ( var i = 0; i < list.length; i++ ) { if ( args[ argIndex ] === list[ i ] ) { // Handle firingIndex and firingLength if ( firing ) { if ( i <= firingLength ) { firingLength--; if ( i <= firingIndex ) { firingIndex--; } } } // Remove the element list.splice( i--, 1 ); // If we have some unicity property then // we only need to do this once if ( flags.unique ) { break; } } } } } return this; }, // Control if a given callback is in the list has: function( fn ) { if ( list ) { var i = 0, length = list.length; for ( ; i < length; i++ ) { if ( fn === list[ i ] ) { return true; } } } return false; }, // Remove all callbacks from the list empty: function() { list = []; return this; }, // Have the list do nothing anymore disable: function() { list = stack = memory = undefined; return this; }, // Is it disabled? disabled: function() { return !list; }, // Lock the list in its current state lock: function() { stack = undefined; if ( !memory || memory === true ) { self.disable(); } return this; }, // Is it locked? locked: function() { return !stack; }, // Call all callbacks with the given context and arguments fireWith: function( context, args ) { if ( stack ) { if ( firing ) { if ( !flags.once ) { stack.push( [ context, args ] ); } } else if ( !( flags.once && memory ) ) { fire( context, args ); } } return this; }, // Call all the callbacks with the given arguments fire: function() { self.fireWith( this, arguments ); return this; }, // To know if the callbacks have already been called at least once fired: function() { return !!fired; } }; return self; }; var // Static reference to slice sliceDeferred = [].slice; jQuery.extend({ Deferred: function( func ) { var doneList = jQuery.Callbacks( "once memory" ), failList = jQuery.Callbacks( "once memory" ), progressList = jQuery.Callbacks( "memory" ), state = "pending", lists = { resolve: doneList, reject: failList, notify: progressList }, promise = { done: doneList.add, fail: failList.add, progress: progressList.add, state: function() { return state; }, // Deprecated isResolved: doneList.fired, isRejected: failList.fired, then: function( doneCallbacks, failCallbacks, progressCallbacks ) { deferred.done( doneCallbacks ).fail( failCallbacks ).progress( progressCallbacks ); return this; }, always: function() { deferred.done.apply( deferred, arguments ).fail.apply( deferred, arguments ); return this; }, pipe: function( fnDone, fnFail, fnProgress ) { return jQuery.Deferred(function( newDefer ) { jQuery.each( { done: [ fnDone, "resolve" ], fail: [ fnFail, "reject" ], progress: [ fnProgress, "notify" ] }, function( handler, data ) { var fn = data[ 0 ], action = data[ 1 ], returned; if ( jQuery.isFunction( fn ) ) { deferred[ handler ](function() { returned = fn.apply( this, arguments ); if ( returned && jQuery.isFunction( returned.promise ) ) { returned.promise().then( newDefer.resolve, newDefer.reject, newDefer.notify ); } else { newDefer[ action + "With" ]( this === deferred ? newDefer : this, [ returned ] ); } }); } else { deferred[ handler ]( newDefer[ action ] ); } }); }).promise(); }, // Get a promise for this deferred // If obj is provided, the promise aspect is added to the object promise: function( obj ) { if ( obj == null ) { obj = promise; } else { for ( var key in promise ) { obj[ key ] = promise[ key ]; } } return obj; } }, deferred = promise.promise({}), key; for ( key in lists ) { deferred[ key ] = lists[ key ].fire; deferred[ key + "With" ] = lists[ key ].fireWith; } // Handle state deferred.done( function() { state = "resolved"; }, failList.disable, progressList.lock ).fail( function() { state = "rejected"; }, doneList.disable, progressList.lock ); // Call given func if any if ( func ) { func.call( deferred, deferred ); } // All done! return deferred; }, // Deferred helper when: function( firstParam ) { var args = sliceDeferred.call( arguments, 0 ), i = 0, length = args.length, pValues = new Array( length ), count = length, pCount = length, deferred = length <= 1 && firstParam && jQuery.isFunction( firstParam.promise ) ? firstParam : jQuery.Deferred(), promise = deferred.promise(); function resolveFunc( i ) { return function( value ) { args[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value; if ( !( --count ) ) { deferred.resolveWith( deferred, args ); } }; } function progressFunc( i ) { return function( value ) { pValues[ i ] = arguments.length > 1 ? sliceDeferred.call( arguments, 0 ) : value; deferred.notifyWith( promise, pValues ); }; } if ( length > 1 ) { for ( ; i < length; i++ ) { if ( args[ i ] && args[ i ].promise && jQuery.isFunction( args[ i ].promise ) ) { args[ i ].promise().then( resolveFunc(i), deferred.reject, progressFunc(i) ); } else { --count; } } if ( !count ) { deferred.resolveWith( deferred, args ); } } else if ( deferred !== firstParam ) { deferred.resolveWith( deferred, length ? [ firstParam ] : [] ); } return promise; } }); jQuery.support = (function() { var support, all, a, select, opt, input, fragment, tds, events, eventName, i, isSupported, div = document.createElement( "div" ), documentElement = document.documentElement; // Preliminary tests div.setAttribute("className", "t"); div.innerHTML = "
a"; all = div.getElementsByTagName( "*" ); a = div.getElementsByTagName( "a" )[ 0 ]; // Can't get basic test support if ( !all || !all.length || !a ) { return {}; } // First batch of supports tests select = document.createElement( "select" ); opt = select.appendChild( document.createElement("option") ); input = div.getElementsByTagName( "input" )[ 0 ]; support = { // IE strips leading whitespace when .innerHTML is used leadingWhitespace: ( div.firstChild.nodeType === 3 ), // Make sure that tbody elements aren't automatically inserted // IE will insert them into empty tables tbody: !div.getElementsByTagName("tbody").length, // Make sure that link elements get serialized correctly by innerHTML // This requires a wrapper element in IE htmlSerialize: !!div.getElementsByTagName("link").length, // Get the style information from getAttribute // (IE uses .cssText instead) style: /top/.test( a.getAttribute("style") ), // Make sure that URLs aren't manipulated // (IE normalizes it by default) hrefNormalized: ( a.getAttribute("href") === "/a" ), // Make sure that element opacity exists // (IE uses filter instead) // Use a regex to work around a WebKit issue. See #5145 opacity: /^0.55/.test( a.style.opacity ), // Verify style float existence // (IE uses styleFloat instead of cssFloat) cssFloat: !!a.style.cssFloat, // Make sure that if no value is specified for a checkbox // that it defaults to "on". // (WebKit defaults to "" instead) checkOn: ( input.value === "on" ), // Make sure that a selected-by-default option has a working selected property. // (WebKit defaults to false instead of true, IE too, if it's in an optgroup) optSelected: opt.selected, // Test setAttribute on camelCase class. If it works, we need attrFixes when doing get/setAttribute (ie6/7) getSetAttribute: div.className !== "t", // Tests for enctype support on a form(#6743) enctype: !!document.createElement("form").enctype, // Makes sure cloning an html5 element does not cause problems // Where outerHTML is undefined, this still works html5Clone: document.createElement("nav").cloneNode( true ).outerHTML !== "<:nav>", // Will be defined later submitBubbles: true, changeBubbles: true, focusinBubbles: false, deleteExpando: true, noCloneEvent: true, inlineBlockNeedsLayout: false, shrinkWrapBlocks: false, reliableMarginRight: true, pixelMargin: true }; // jQuery.boxModel DEPRECATED in 1.3, use jQuery.support.boxModel instead jQuery.boxModel = support.boxModel = (document.compatMode === "CSS1Compat"); // Make sure checked status is properly cloned input.checked = true; support.noCloneChecked = input.cloneNode( true ).checked; // Make sure that the options inside disabled selects aren't marked as disabled // (WebKit marks them as disabled) select.disabled = true; support.optDisabled = !opt.disabled; // Test to see if it's possible to delete an expando from an element // Fails in Internet Explorer try { delete div.test; } catch( e ) { support.deleteExpando = false; } if ( !div.addEventListener && div.attachEvent && div.fireEvent ) { div.attachEvent( "onclick", function() { // Cloning a node shouldn't copy over any // bound event handlers (IE does this) support.noCloneEvent = false; }); div.cloneNode( true ).fireEvent( "onclick" ); } // Check if a radio maintains its value // after being appended to the DOM input = document.createElement("input"); input.value = "t"; input.setAttribute("type", "radio"); support.radioValue = input.value === "t"; input.setAttribute("checked", "checked"); // #11217 - WebKit loses check when the name is after the checked attribute input.setAttribute( "name", "t" ); div.appendChild( input ); fragment = document.createDocumentFragment(); fragment.appendChild( div.lastChild ); // WebKit doesn't clone checked state correctly in fragments support.checkClone = fragment.cloneNode( true ).cloneNode( true ).lastChild.checked; // Check if a disconnected checkbox will retain its checked // value of true after appended to the DOM (IE6/7) support.appendChecked = input.checked; fragment.removeChild( input ); fragment.appendChild( div ); // Technique from Juriy Zaytsev // http://perfectionkills.com/detecting-event-support-without-browser-sniffing/ // We only care about the case where non-standard event systems // are used, namely in IE. Short-circuiting here helps us to // avoid an eval call (in setAttribute) which can cause CSP // to go haywire. See: https://developer.mozilla.org/en/Security/CSP if ( div.attachEvent ) { for ( i in { submit: 1, change: 1, focusin: 1 }) { eventName = "on" + i; isSupported = ( eventName in div ); if ( !isSupported ) { div.setAttribute( eventName, "return;" ); isSupported = ( typeof div[ eventName ] === "function" ); } support[ i + "Bubbles" ] = isSupported; } } fragment.removeChild( div ); // Null elements to avoid leaks in IE fragment = select = opt = div = input = null; // Run tests that need a body at doc ready jQuery(function() { var container, outer, inner, table, td, offsetSupport, marginDiv, conMarginTop, style, html, positionTopLeftWidthHeight, paddingMarginBorderVisibility, paddingMarginBorder, body = document.getElementsByTagName("body")[0]; if ( !body ) { // Return for frameset docs that don't have a body return; } conMarginTop = 1; paddingMarginBorder = "padding:0;margin:0;border:"; positionTopLeftWidthHeight = "position:absolute;top:0;left:0;width:1px;height:1px;"; paddingMarginBorderVisibility = paddingMarginBorder + "0;visibility:hidden;"; style = "style='" + positionTopLeftWidthHeight + paddingMarginBorder + "5px solid #000;"; html = "
" + "" + "
"; container = document.createElement("div"); container.style.cssText = paddingMarginBorderVisibility + "width:0;height:0;position:static;top:0;margin-top:" + conMarginTop + "px"; body.insertBefore( container, body.firstChild ); // Construct the test element div = document.createElement("div"); container.appendChild( div ); // Check if table cells still have offsetWidth/Height when they are set // to display:none and there are still other visible table cells in a // table row; if so, offsetWidth/Height are not reliable for use when // determining if an element has been hidden directly using // display:none (it is still safe to use offsets if a parent element is // hidden; don safety goggles and see bug #4512 for more information). // (only IE 8 fails this test) div.innerHTML = "
t
"; tds = div.getElementsByTagName( "td" ); isSupported = ( tds[ 0 ].offsetHeight === 0 ); tds[ 0 ].style.display = ""; tds[ 1 ].style.display = "none"; // Check if empty table cells still have offsetWidth/Height // (IE <= 8 fail this test) support.reliableHiddenOffsets = isSupported && ( tds[ 0 ].offsetHeight === 0 ); // Check if div with explicit width and no margin-right incorrectly // gets computed margin-right based on width of container. For more // info see bug #3333 // Fails in WebKit before Feb 2011 nightlies // WebKit Bug 13343 - getComputedStyle returns wrong value for margin-right if ( window.getComputedStyle ) { div.innerHTML = ""; marginDiv = document.createElement( "div" ); marginDiv.style.width = "0"; marginDiv.style.marginRight = "0"; div.style.width = "2px"; div.appendChild( marginDiv ); support.reliableMarginRight = ( parseInt( ( window.getComputedStyle( marginDiv, null ) || { marginRight: 0 } ).marginRight, 10 ) || 0 ) === 0; } if ( typeof div.style.zoom !== "undefined" ) { // Check if natively block-level elements act like inline-block // elements when setting their display to 'inline' and giving // them layout // (IE < 8 does this) div.innerHTML = ""; div.style.width = div.style.padding = "1px"; div.style.border = 0; div.style.overflow = "hidden"; div.style.display = "inline"; div.style.zoom = 1; support.inlineBlockNeedsLayout = ( div.offsetWidth === 3 ); // Check if elements with layout shrink-wrap their children // (IE 6 does this) div.style.display = "block"; div.style.overflow = "visible"; div.innerHTML = "
"; support.shrinkWrapBlocks = ( div.offsetWidth !== 3 ); } div.style.cssText = positionTopLeftWidthHeight + paddingMarginBorderVisibility; div.innerHTML = html; outer = div.firstChild; inner = outer.firstChild; td = outer.nextSibling.firstChild.firstChild; offsetSupport = { doesNotAddBorder: ( inner.offsetTop !== 5 ), doesAddBorderForTableAndCells: ( td.offsetTop === 5 ) }; inner.style.position = "fixed"; inner.style.top = "20px"; // safari subtracts parent border width here which is 5px offsetSupport.fixedPosition = ( inner.offsetTop === 20 || inner.offsetTop === 15 ); inner.style.position = inner.style.top = ""; outer.style.overflow = "hidden"; outer.style.position = "relative"; offsetSupport.subtractsBorderForOverflowNotVisible = ( inner.offsetTop === -5 ); offsetSupport.doesNotIncludeMarginInBodyOffset = ( body.offsetTop !== conMarginTop ); if ( window.getComputedStyle ) { div.style.marginTop = "1%"; support.pixelMargin = ( window.getComputedStyle( div, null ) || { marginTop: 0 } ).marginTop !== "1%"; } if ( typeof container.style.zoom !== "undefined" ) { container.style.zoom = 1; } body.removeChild( container ); marginDiv = div = container = null; jQuery.extend( support, offsetSupport ); }); return support; })(); var rbrace = /^(?:\{.*\}|\[.*\])$/, rmultiDash = /([A-Z])/g; jQuery.extend({ cache: {}, // Please use with caution uuid: 0, // Unique for each copy of jQuery on the page // Non-digits removed to match rinlinejQuery expando: "jQuery" + ( jQuery.fn.jquery + Math.random() ).replace( /\D/g, "" ), // The following elements throw uncatchable exceptions if you // attempt to add expando properties to them. noData: { "embed": true, // Ban all objects except for Flash (which handle expandos) "object": "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000", "applet": true }, hasData: function( elem ) { elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ]; return !!elem && !isEmptyDataObject( elem ); }, data: function( elem, name, data, pvt /* Internal Use Only */ ) { if ( !jQuery.acceptData( elem ) ) { return; } var privateCache, thisCache, ret, internalKey = jQuery.expando, getByName = typeof name === "string", // We have to handle DOM nodes and JS objects differently because IE6-7 // can't GC object references properly across the DOM-JS boundary isNode = elem.nodeType, // Only DOM nodes need the global jQuery cache; JS object data is // attached directly to the object so GC can occur automatically cache = isNode ? jQuery.cache : elem, // Only defining an ID for JS objects if its cache already exists allows // the code to shortcut on the same path as a DOM node with no cache id = isNode ? elem[ internalKey ] : elem[ internalKey ] && internalKey, isEvents = name === "events"; // Avoid doing any more work than we need to when trying to get data on an // object that has no data at all if ( (!id || !cache[id] || (!isEvents && !pvt && !cache[id].data)) && getByName && data === undefined ) { return; } if ( !id ) { // Only DOM nodes need a new unique ID for each element since their data // ends up in the global cache if ( isNode ) { elem[ internalKey ] = id = ++jQuery.uuid; } else { id = internalKey; } } if ( !cache[ id ] ) { cache[ id ] = {}; // Avoids exposing jQuery metadata on plain JS objects when the object // is serialized using JSON.stringify if ( !isNode ) { cache[ id ].toJSON = jQuery.noop; } } // An object can be passed to jQuery.data instead of a key/value pair; this gets // shallow copied over onto the existing cache if ( typeof name === "object" || typeof name === "function" ) { if ( pvt ) { cache[ id ] = jQuery.extend( cache[ id ], name ); } else { cache[ id ].data = jQuery.extend( cache[ id ].data, name ); } } privateCache = thisCache = cache[ id ]; // jQuery data() is stored in a separate object inside the object's internal data // cache in order to avoid key collisions between internal data and user-defined // data. if ( !pvt ) { if ( !thisCache.data ) { thisCache.data = {}; } thisCache = thisCache.data; } if ( data !== undefined ) { thisCache[ jQuery.camelCase( name ) ] = data; } // Users should not attempt to inspect the internal events object using jQuery.data, // it is undocumented and subject to change. But does anyone listen? No. if ( isEvents && !thisCache[ name ] ) { return privateCache.events; } // Check for both converted-to-camel and non-converted data property names // If a data property was specified if ( getByName ) { // First Try to find as-is property data ret = thisCache[ name ]; // Test for null|undefined property data if ( ret == null ) { // Try to find the camelCased property ret = thisCache[ jQuery.camelCase( name ) ]; } } else { ret = thisCache; } return ret; }, removeData: function( elem, name, pvt /* Internal Use Only */ ) { if ( !jQuery.acceptData( elem ) ) { return; } var thisCache, i, l, // Reference to internal data cache key internalKey = jQuery.expando, isNode = elem.nodeType, // See jQuery.data for more information cache = isNode ? jQuery.cache : elem, // See jQuery.data for more information id = isNode ? elem[ internalKey ] : internalKey; // If there is already no cache entry for this object, there is no // purpose in continuing if ( !cache[ id ] ) { return; } if ( name ) { thisCache = pvt ? cache[ id ] : cache[ id ].data; if ( thisCache ) { // Support array or space separated string names for data keys if ( !jQuery.isArray( name ) ) { // try the string as a key before any manipulation if ( name in thisCache ) { name = [ name ]; } else { // split the camel cased version by spaces unless a key with the spaces exists name = jQuery.camelCase( name ); if ( name in thisCache ) { name = [ name ]; } else { name = name.split( " " ); } } } for ( i = 0, l = name.length; i < l; i++ ) { delete thisCache[ name[i] ]; } // If there is no data left in the cache, we want to continue // and let the cache object itself get destroyed if ( !( pvt ? isEmptyDataObject : jQuery.isEmptyObject )( thisCache ) ) { return; } } } // See jQuery.data for more information if ( !pvt ) { delete cache[ id ].data; // Don't destroy the parent cache unless the internal data object // had been the only thing left in it if ( !isEmptyDataObject(cache[ id ]) ) { return; } } // Browsers that fail expando deletion also refuse to delete expandos on // the window, but it will allow it on all other JS objects; other browsers // don't care // Ensure that `cache` is not a window object #10080 if ( jQuery.support.deleteExpando || !cache.setInterval ) { delete cache[ id ]; } else { cache[ id ] = null; } // We destroyed the cache and need to eliminate the expando on the node to avoid // false lookups in the cache for entries that no longer exist if ( isNode ) { // IE does not allow us to delete expando properties from nodes, // nor does it have a removeAttribute function on Document nodes; // we must handle all of these cases if ( jQuery.support.deleteExpando ) { delete elem[ internalKey ]; } else if ( elem.removeAttribute ) { elem.removeAttribute( internalKey ); } else { elem[ internalKey ] = null; } } }, // For internal use only. _data: function( elem, name, data ) { return jQuery.data( elem, name, data, true ); }, // A method for determining if a DOM node can handle the data expando acceptData: function( elem ) { if ( elem.nodeName ) { var match = jQuery.noData[ elem.nodeName.toLowerCase() ]; if ( match ) { return !(match === true || elem.getAttribute("classid") !== match); } } return true; } }); jQuery.fn.extend({ data: function( key, value ) { var parts, part, attr, name, l, elem = this[0], i = 0, data = null; // Gets all values if ( key === undefined ) { if ( this.length ) { data = jQuery.data( elem ); if ( elem.nodeType === 1 && !jQuery._data( elem, "parsedAttrs" ) ) { attr = elem.attributes; for ( l = attr.length; i < l; i++ ) { name = attr[i].name; if ( name.indexOf( "data-" ) === 0 ) { name = jQuery.camelCase( name.substring(5) ); dataAttr( elem, name, data[ name ] ); } } jQuery._data( elem, "parsedAttrs", true ); } } return data; } // Sets multiple values if ( typeof key === "object" ) { return this.each(function() { jQuery.data( this, key ); }); } parts = key.split( ".", 2 ); parts[1] = parts[1] ? "." + parts[1] : ""; part = parts[1] + "!"; return jQuery.access( this, function( value ) { if ( value === undefined ) { data = this.triggerHandler( "getData" + part, [ parts[0] ] ); // Try to fetch any internally stored data first if ( data === undefined && elem ) { data = jQuery.data( elem, key ); data = dataAttr( elem, key, data ); } return data === undefined && parts[1] ? this.data( parts[0] ) : data; } parts[1] = value; this.each(function() { var self = jQuery( this ); self.triggerHandler( "setData" + part, parts ); jQuery.data( this, key, value ); self.triggerHandler( "changeData" + part, parts ); }); }, null, value, arguments.length > 1, null, false ); }, removeData: function( key ) { return this.each(function() { jQuery.removeData( this, key ); }); } }); function dataAttr( elem, key, data ) { // If nothing was found internally, try to fetch any // data from the HTML5 data-* attribute if ( data === undefined && elem.nodeType === 1 ) { var name = "data-" + key.replace( rmultiDash, "-$1" ).toLowerCase(); data = elem.getAttribute( name ); if ( typeof data === "string" ) { try { data = data === "true" ? true : data === "false" ? false : data === "null" ? null : jQuery.isNumeric( data ) ? +data : rbrace.test( data ) ? jQuery.parseJSON( data ) : data; } catch( e ) {} // Make sure we set the data so it isn't changed later jQuery.data( elem, key, data ); } else { data = undefined; } } return data; } // checks a cache object for emptiness function isEmptyDataObject( obj ) { for ( var name in obj ) { // if the public data object is empty, the private is still empty if ( name === "data" && jQuery.isEmptyObject( obj[name] ) ) { continue; } if ( name !== "toJSON" ) { return false; } } return true; } function handleQueueMarkDefer( elem, type, src ) { var deferDataKey = type + "defer", queueDataKey = type + "queue", markDataKey = type + "mark", defer = jQuery._data( elem, deferDataKey ); if ( defer && ( src === "queue" || !jQuery._data(elem, queueDataKey) ) && ( src === "mark" || !jQuery._data(elem, markDataKey) ) ) { // Give room for hard-coded callbacks to fire first // and eventually mark/queue something else on the element setTimeout( function() { if ( !jQuery._data( elem, queueDataKey ) && !jQuery._data( elem, markDataKey ) ) { jQuery.removeData( elem, deferDataKey, true ); defer.fire(); } }, 0 ); } } jQuery.extend({ _mark: function( elem, type ) { if ( elem ) { type = ( type || "fx" ) + "mark"; jQuery._data( elem, type, (jQuery._data( elem, type ) || 0) + 1 ); } }, _unmark: function( force, elem, type ) { if ( force !== true ) { type = elem; elem = force; force = false; } if ( elem ) { type = type || "fx"; var key = type + "mark", count = force ? 0 : ( (jQuery._data( elem, key ) || 1) - 1 ); if ( count ) { jQuery._data( elem, key, count ); } else { jQuery.removeData( elem, key, true ); handleQueueMarkDefer( elem, type, "mark" ); } } }, queue: function( elem, type, data ) { var q; if ( elem ) { type = ( type || "fx" ) + "queue"; q = jQuery._data( elem, type ); // Speed up dequeue by getting out quickly if this is just a lookup if ( data ) { if ( !q || jQuery.isArray(data) ) { q = jQuery._data( elem, type, jQuery.makeArray(data) ); } else { q.push( data ); } } return q || []; } }, dequeue: function( elem, type ) { type = type || "fx"; var queue = jQuery.queue( elem, type ), fn = queue.shift(), hooks = {}; // If the fx queue is dequeued, always remove the progress sentinel if ( fn === "inprogress" ) { fn = queue.shift(); } if ( fn ) { // Add a progress sentinel to prevent the fx queue from being // automatically dequeued if ( type === "fx" ) { queue.unshift( "inprogress" ); } jQuery._data( elem, type + ".run", hooks ); fn.call( elem, function() { jQuery.dequeue( elem, type ); }, hooks ); } if ( !queue.length ) { jQuery.removeData( elem, type + "queue " + type + ".run", true ); handleQueueMarkDefer( elem, type, "queue" ); } } }); // 扩展jQuery对象的函数 jQuery.fn.extend({ // queue函数,用于处理元素队列 queue: function( type, data ) { var setter = 2; // 如果type不是字符串,则将data和type的值互换,并将setter减1 if ( typeof type !== "string" ) { data = type; type = "fx"; setter--; } // 如果参数不足setter个数,则返回指定元素的队列 if ( arguments.length < setter ) { return jQuery.queue( this[0], type ); } // 如果data未定义,则直接返回this,否则遍历每个元素 return data === undefined ? this : this.each(function() { var queue = jQuery.queue( this, type, data ); // 如果type是"fx"且队列的第一个元素不是"inprogress",则执行dequeue if ( type === "fx" && queue[0] !== "inprogress" ) { jQuery.dequeue( this, type ); } }); }, // dequeue函数,用于从队列中移除下一个待执行的函数 dequeue: function( type ) { return this.each(function() { jQuery.dequeue( this, type ); }); }, // delay函数,用于在队列中添加一个延时函数 delay: function( time, type ) { time = jQuery.fx ? jQuery.fx.speeds[ time ] || time : time; type = type || "fx"; // 在队列中添加一个延时函数 return this.queue( type, function( next, hooks ) { var timeout = setTimeout( next, time ); hooks.stop = function() { clearTimeout( timeout ); }; }); }, // clearQueue函数,用于清空指定类型的队列 clearQueue: function( type ) { return this.queue( type || "fx", [] ); }, // promise函数,用于获取一个在队列清空时解决的承诺 promise: function( type, object ) { if ( typeof type !== "string" ) { object = type; type = undefined; } type = type || "fx"; var defer = jQuery.Deferred(), elements = this, i = elements.length, count = 1, deferDataKey = type + "defer", queueDataKey = type + "queue", markDataKey = type + "mark", tmp; function resolve() { if ( !( --count ) ) { defer.resolveWith( elements, [ elements ] ); } } while( i-- ) { if (( tmp = jQuery.data( elements[ i ], deferDataKey, undefined, true ) || ( jQuery.data( elements[ i ], queueDataKey, undefined, true ) || jQuery.data( elements[ i ], markDataKey, undefined, true ) ) && jQuery.data( elements[ i ], deferDataKey, jQuery.Callbacks( "once memory" ), true ) )) { count++; tmp.add( resolve ); } } resolve(); return defer.promise( object ); } }); // 正则表达式,用于去除字符串中的换行、制表符和回车符 var rclass = /[\n\t\r]/g, // 正则表达式,用于匹配一个或多个空格 rspace = /\s+/, // 正则表达式,用于匹配回车符 rreturn = /\r/g, // 正则表达式,用于匹配button或input元素 rtype = /^(?:button|input)$/i, // 正则表达式,用于匹配button、input、object、select或textarea元素 rfocusable = /^(?:button|input|object|select|textarea)$/i, // 正则表达式,用于匹配a或area元素 rclickable = /^a(?:rea)?$/i, // 正则表达式,用于匹配布尔属性 rboolean = /^(?:autofocus|autoplay|async|checked|controls|defer|disabled|hidden|loop|multiple|open|readonly|required|scoped|selected)$/i, // 检查是否支持getAttribute和setAttribute getSetAttribute = jQuery.support.getSetAttribute, nodeHook, boolHook, fixSpecified; // 扩展jQuery对象的函数,用于属性和属性值的操作 jQuery.fn.extend({ // attr函数,用于获取或设置元素的属性值 attr: function( name, value ) { return jQuery.access( this, jQuery.attr, name, value, arguments.length > 1 ); }, // removeAttr函数,用于移除元素的属性 removeAttr: function( name ) { return this.each(function() { jQuery.removeAttr( this, name ); }); }, // prop函数,用于获取或设置元素的属性值(适用于标准属性) prop: function( name, value ) { return jQuery.access( this, jQuery.prop, name, value, arguments.length > 1 ); }, // removeProp函数,用于移除元素的属性(适用于标准属性) removeProp: function( name ) { name = jQuery.propFix[ name ] || name; return this.each(function() { // try/catch处理IE浏览器中删除属性时可能抛出的异常 try { this[ name ] = undefined; delete this[ name ]; } catch( e ) {} }); }, // addClass函数,用于给元素添加类名 addClass: function( value ) { var classNames, i, l, elem, setClass, c, cl; // 如果value是函数,则为每个元素调用该函数,并添加返回的类名 if ( jQuery.isFunction( value ) ) { return this.each(function( j ) { jQuery( this ).addClass( value.call(this, j, this.className) ); }); } // 如果value是字符串,则按空格分割类名,并为每个元素添加这些类名 if ( value && typeof value === "string" ) { classNames = value.split( rspace ); for ( i = 0, l = this.length; i < l; i++ ) { elem = this[ i ]; // 如果元素是元素节点且没有类名,则直接设置类名 if ( elem.nodeType === 1 ) { if ( !elem.className && classNames.length === 1 ) { elem.className = value; } else { // 如果元素已经有类名,则在已有类名基础上添加新类名 setClass = " " + elem.className + " "; for ( c = 0, cl = classNames.length; c < cl; c++ ) { if ( !~setClass.indexOf( " " + classNames[ c ] + " " ) ) { setClass += classNames[ c ] + " "; } } elem.className = jQuery.trim( setClass ); } } } } // 返回this,以支持链式调用 return this; }, // removeClass函数,用于从元素中移除类名 removeClass: function( value ) { var classNames, i, l, elem, className, c, cl; // 如果value是函数,则为每个元素调用该函数,并移除返回的类名 if ( jQuery.isFunction( value ) ) { return this.each(function( j ) { jQuery( this ).removeClass( value.call(this, j, this.className) ); }); } // 如果value是字符串或undefined,则按空格分割类名,并为每个元素移除这些类名 if ( (value && typeof value === "string") || value === undefined ) { classNames = ( value || "" ).split( rspace ); // 遍历每个元素,对每个元素执行以下操作 for ( i = 0, l = this.length; i < l; i++ ) { elem = this[ i ]; // 获取当前遍历的元素 // 如果元素是元素节点并且有类名 if ( elem.nodeType === 1 && elem.className ) { if ( value ) { // 如果value存在,则创建一个包含当前类名和新类名的字符串 className = (" " + elem.className + " ").replace( rclass, " " ); // 遍历所有要移除的类名 for ( c = 0, cl = classNames.length; c < cl; c++ ) { // 移除类名 className = className.replace(" " + classNames[ c ] + " ", " "); } // 更新元素的类名 elem.className = jQuery.trim( className ); } else { // 如果value不存在,则清空元素的类名 elem.className = ""; } } } // 返回this,以支持链式调用 return this; }, // toggleClass函数,用于根据条件切换元素的类名 toggleClass: function( value, stateVal ) { var type = typeof value, isBool = typeof stateVal === "boolean"; // 如果value是函数,则对每个元素执行该函数,并根据返回值切换类名 if ( jQuery.isFunction( value ) ) { return this.each(function( i ) { jQuery( this ).toggleClass( value.call(this, i, this.className, stateVal), stateVal ); }); } // 对每个元素执行类名切换操作 return this.each(function() { if ( type === "string" ) { // 切换单个类名 var className, i = 0, self = jQuery( this ), state = stateVal, classNames = value.split( rspace ); while ( (className = classNames[ i++ ]) ) { // 检查每个给定的类名,空格分隔的列表 state = isBool ? state : !self.hasClass( className ); self[ state ? "addClass" : "removeClass" ]( className ); } } else if ( type === "undefined" || type === "boolean" ) { if ( this.className ) { // 如果设置了类名,则存储类名 jQuery._data( this, "__className__", this.className ); } // 切换整个类名 this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || ""; } }); }, // hasClass函数,用于检查元素是否包含指定的类名 hasClass: function( selector ) { var className = " " + selector + " ", i = 0, l = this.length; for ( ; i < l; i++ ) { if ( this[i].nodeType === 1 && (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) > -1 ) { return true; } } return false; }, // val函数,用于获取或设置表单元素的值 val: function( value ) { var hooks, ret, isFunction, elem = this[0]; // 如果没有参数,则获取第一个元素的值 if ( !arguments.length ) { if ( elem ) { hooks = jQuery.valHooks[ elem.type ] || jQuery.valHooks[ elem.nodeName.toLowerCase() ]; if ( hooks && "get" in hooks && (ret = hooks.get( elem, "value" )) !== undefined ) { return ret; } ret = elem.value; return typeof ret === "string" ? // 处理最常见的字符串情况 ret.replace(rreturn, "") : // 处理值是null/undef或数字的情况 ret == null ? "" : ret; } return; } isFunction = jQuery.isFunction( value ); // 对每个元素执行值设置操作 return this.each(function( i ) { var self = jQuery(this), val; if ( this.nodeType !== 1 ) { return; } if ( isFunction ) { val = value.call( this, i, self.val() ); } else { val = value; } // 将null/undefined视为"";将数字转换为字符串 if ( val == null ) { val = ""; } else if ( typeof val === "number" ) { val += ""; } else if ( jQuery.isArray( val ) ) { val = jQuery.map(val, function ( value ) { return value == null ? "" : value + ""; }); } hooks = jQuery.valHooks[ this.type ] || jQuery.valHooks[ this.nodeName.toLowerCase() ]; // 如果set返回undefined,则回退到正常设置 if ( !hooks || !("set" in hooks) || hooks.set( this, val, "value" ) === undefined ) { this.value = val; } }); } jQuery.extend({ valHooks: { // option元素的值钩子 option: { get: function( elem ) { // 在Blackberry 4.7中attributes.value是undefined,但使用.value var val = elem.attributes.value; return !val || val.specified ? elem.value : elem.text; } }, // select元素的值钩子 select: { get: function( elem ) { var value, i, max, option, index = elem.selectedIndex, values = [], options = elem.options, one = elem.type === "select-one"; // 没有选中任何选项 if ( index < 0 ) { return null; } // 遍历所有选中的选项 i = one ? index : 0; max = one ? index + 1 : options.length; for ( ; i < max; i++ ) { option = options[ i ]; // 不返回禁用的选项或在禁用的optgroup中的选项 if ( option.selected && (jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null) && (!(option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" )) ) ) { // 获取选项的特定值 value = jQuery( option ).val(); // 单选不需要数组 if ( one ) { return value; } // 多选返回数组 values.push( value ); } } // 修复Bug #2551 -- 在表单重置后select.val()在IE中被破坏 if ( one && !values.length && options.length ) { return jQuery( options[ index ] ).val(); } return values; }, set: function( elem, value ) { var values = jQuery.makeArray( value ); jQuery(elem).find("option").each(function() { this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0; }); if ( !values.length ) { elem.selectedIndex = -1; } return values; } } }, // 定义一个对象,包含那些可以通过jQuery.attr和jQuery.prop直接访问的属性和方法 attrFn: { val: true, // 值属性 css: true, // CSS样式 html: true, // 内部HTML text: true, // 文本内容 data: true, // 数据属性 width: true, // 宽度 height: true, // 高度 offset: true // 偏移位置 }, // attr函数,用于获取或设置元素的属性值 attr: function( elem, name, value, pass ) { var ret, hooks, notxml, nType = elem.nodeType; // 不对文本、注释和属性节点进行属性的获取和设置 if ( !elem || nType === 3 || nType === 8 || nType === 2 ) { return; } if ( pass && name in jQuery.attrFn ) { return jQuery( elem )[ name ]( value ); } // 当元素不支持属性时,回退到prop if ( typeof elem.getAttribute === "undefined" ) { return jQuery.prop( elem, name, value ); } notxml = nType !== 1 || !jQuery.isXMLDoc( elem ); // 所有属性都转换为小写 // 如果定义了必要的钩子,则获取它 if ( notxml ) { name = name.toLowerCase(); hooks = jQuery.attrHooks[ name ] || ( rboolean.test( name ) ? boolHook : nodeHook ); } if ( value !== undefined ) { if ( value === null ) { jQuery.removeAttr( elem, name ); } else if ( hooks && "set" in hooks && notxml && (ret = hooks.set( elem, value, name )) !== undefined ) { return ret; } else { elem.setAttribute( name, "" + value ); return value; } } else if ( hooks && "get" in hooks && notxml && (ret = hooks.get( elem, name )) !== null ) { return ret; } else { ret = elem.getAttribute( name ); // 非存在的属性返回null,我们将其标准化为undefined return ret === null ? undefined : ret; } }, // removeAttr函数,用于移除元素的一个或多个属性 removeAttr: function( elem, value ) { var propName, attrNames, name, l, isBool, i = 0; if ( value && elem.nodeType === 1 ) { attrNames = value.toLowerCase().split( rspace ); l = attrNames.length; for ( ; i < l; i++ ) { name = attrNames[ i ]; if ( name ) { propName = jQuery.propFix[ name ] || name; isBool = rboolean.test( name ); // 见#9699的解释(先设置,然后移除) // 对于布尔属性不要这样做(见#10870) if ( !isBool ) { jQuery.attr( elem, name, "" ); } elem.removeAttribute( getSetAttribute ? name : propName ); // 对于布尔属性,将对应的属性设置为false if ( isBool && propName in elem ) { elem[ propName ] = false; } } } } }, // attrHooks对象,包含特定属性的获取和设置钩子 attrHooks: { type: { set: function( elem, value ) { // 我们不允许改变type属性(因为这在IE中会导致问题) if ( rtype.test( elem.nodeName ) && elem.parentNode ) { jQuery.error( "type property can't be changed" ); } else if ( !jQuery.support.radioValue && value === "radio" && jQuery.nodeName(elem, "input") ) { // 在IE6-9中,设置radio按钮的type属性后,value会重置 // 如果type属性在value属性之后设置,则重置value为其默认值 // 这是为了元素创建 var val = elem.value; elem.setAttribute( "type", value ); if ( val ) { elem.value = val; } return value; } } }, // 为了向后兼容,使用value属性 // 在IE6/7中使用nodeHook处理按钮元素(#1954) value: { get: function( elem, name ) { if ( nodeHook && jQuery.nodeName( elem, "button" ) ) { return nodeHook.get( elem, name ); } return name in elem ? elem.value : null; }, set: function( elem, value, name ) { if ( nodeHook && jQuery.nodeName( elem, "button" ) ) { return nodeHook.set( elem, value, name ); } // 不返回,以便也使用setAttribute elem.value = value; } } }, // propFix对象,包含属性名的修正 propFix: { tabindex: "tabIndex", readonly: "readOnly", "for": "htmlFor", "class": "className", maxlength: "maxLength", cellspacing: "cellSpacing", cellpadding: "cellPadding", rowspan: "rowSpan", colspan: "colSpan", usemap: "useMap", frameborder: "frameBorder", contenteditable: "contentEditable" }, // prop函数,用于获取或设置元素的标准属性值 prop: function( elem, name, value ) { var ret, hooks, notxml, nType = elem.nodeType; // 不对文本、注释和属性节点进行属性的获取和设置 if ( !elem || nType === 3 || nType === 8 || nType === 2 ) { return; } notxml = nType !== 1 || !jQuery.isXMLDoc( elem ); if ( notxml ) { // 修正名称并附加钩子 name = jQuery.propFix[ name ] || name; hooks = jQuery.propHooks[ name ]; } if ( value !== undefined ) { if ( hooks && "set" in hooks && (ret = hooks.set( elem, value, name )) !== undefined ) { return ret; } else { return ( elem[ name ] = value ); } } else { if ( hooks && "get" in hooks && (ret = hooks.get( elem, name )) !== null ) { return ret; } else { return elem[ name ]; } } }, // propHooks对象,包含特定属性的获取和设置钩子 propHooks: { tabIndex: { get: function( elem ) { // elem.tabIndex并不总是返回正确的值,当它没有被显式设置时 // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ var attributeNode = elem.getAttributeNode("tabindex"); return attributeNode && attributeNode.specified ? parseInt( attributeNode.value, 10 ) : rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ? 0 : undefined; } } } }); // Add the tabIndex propHook to attrHooks for back-compat (different case is intentional) jQuery.attrHooks.tabindex = jQuery.propHooks.tabIndex; // Hook for boolean attributes // 定义布尔属性的获取和设置钩子 boolHook = { get: function( elem, name ) { // 将布尔属性与相应的属性对齐 // 在一些布尔属性不受支持的地方,回退到属性存在性检查 var attrNode, property = jQuery.prop( elem, name ); return property === true || typeof property !== "boolean" && ( attrNode = elem.getAttributeNode(name) ) && attrNode.nodeValue !== false ? name.toLowerCase() : undefined; }, set: function( elem, value, name ) { var propName; if ( value === false ) { // 当设置为false时,移除布尔属性 jQuery.removeAttr( elem, name ); } else { // 我们知道此时value为true,因为它是布尔类型且不是false // 设置布尔属性为相同名称,并设置DOM属性 propName = jQuery.propFix[ name ] || name; if ( propName in elem ) { // 仅当IDL已经存在于元素上时,才设置IDL elem[ propName ] = true; } elem.setAttribute( name, name.toLowerCase() ); } return name; } }; // IE6/7不支持通过get/setAttribute获取/设置某些属性 if ( !getSetAttribute ) { fixSpecified = { name: true, id: true, coords: true }; // 用于IE6/7中的任何属性 // 这几乎修复了所有IE6/7的问题 nodeHook = jQuery.valHooks.button = { get: function( elem, name ) { var ret; ret = elem.getAttributeNode( name ); return ret && ( fixSpecified[ name ] ? ret.nodeValue !== "" : ret.specified ) ? ret.nodeValue : undefined; }, set: function( elem, value, name ) { // 设置现有的或创建一个新的属性节点 var ret = elem.getAttributeNode( name ); if ( !ret ) { ret = document.createAttribute( name ); elem.setAttributeNode( ret ); } return ( ret.nodeValue = value + "" ); } }; // 将nodeHook应用于tabindex jQuery.attrHooks.tabindex.set = nodeHook.set; // 在空字符串时将width和height设置为auto而不是0(Bug #8150) // 这是为了移除操作 jQuery.each([ "width", "height" ], function( i, name ) { jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], { set: function( elem, value ) { if ( value === "" ) { elem.setAttribute( name, "auto" ); return value; } } }); }); // 在移除时将contenteditable设置为false(#10429) // 设置为空字符串会因为无效值而抛出错误 jQuery.attrHooks.contenteditable = { get: nodeHook.get, set: function( elem, value, name ) { if ( value === "" ) { value = "false"; } nodeHook.set( elem, value, name ); } }; } // IE不支持href属性的规范化 if ( !jQuery.support.hrefNormalized ) { jQuery.each([ "href", "src", "width", "height" ], function( i, name ) { jQuery.attrHooks[ name ] = jQuery.extend( jQuery.attrHooks[ name ], { get: function( elem ) { var ret = elem.getAttribute( name, 2 ); return ret === null ? undefined : ret; } }); }); } // IE不支持style属性 if ( !jQuery.support.style ) { jQuery.attrHooks.style = { get: function( elem ) { // 在空字符串的情况下返回undefined // 将IE中的css属性名统一为小写 return elem.style.cssText.toLowerCase() || undefined; }, set: function( elem, value ) { return ( elem.style.cssText = "" + value ); } }; } // Safari错误地报告了option的默认selected属性 // 访问父元素的selectedIndex属性可以修复它 if ( !jQuery.support.optSelected ) { jQuery.propHooks.selected = jQuery.extend( jQuery.propHooks.selected, { get: function( elem ) { var parent = elem.parentNode; if ( parent ) { parent.selectedIndex; // 确保这也适用于optgroup,见#5701 if ( parent.parentNode ) { parent.parentNode.selectedIndex; } } return null; } }); } // IE6/7调用enctype编码 if ( !jQuery.support.enctype ) { jQuery.propFix.enctype = "encoding"; } // 单选按钮和复选框的获取/设置 if ( !jQuery.support.checkOn ) { jQuery.each([ "radio", "checkbox" ], function() { jQuery.valHooks[ this ] = { get: function( elem ) { // 处理Webkit返回""而不是"on"的情况,如果没有指定值 return elem.getAttribute("value") === null ? "on" : elem.value; } }; }); } jQuery.each([ "radio", "checkbox" ], function() { jQuery.valHooks[ this ] = jQuery.extend( jQuery.valHooks[ this ], { set: function( elem, value ) { if ( jQuery.isArray( value ) ) { return ( elem.checked = jQuery.inArray( jQuery(elem).val(), value ) >= 0 ); } } }); }); // 定义用于匹配表单元素的正则表达式 var rformElems = /^(?:textarea|input|select)$/i, // 定义用于匹配命名空间的正则表达式 rtypenamespace = /^([^\.]*)?(?:\.(.+))?$/, // 定义用于处理hover事件hack的正则表达式 rhoverHack = /(?:^|\s)hover(\.\S+)?\b/, // 定义用于匹配键盘事件的正则表达式 rkeyEvent = /^key/, // 定义用于匹配鼠标事件的正则表达式 rmouseEvent = /^(?:mouse|contextmenu)|click/, // 定义用于匹配focus事件的正则表达式 rfocusMorph = /^(?:focusinfocus|focusoutblur)$/, // 定义用于快速解析选择器的正则表达式 rquickIs = /^(\w*)(?:#([\w\-]+))?(?:\.([\w\-]+))?$/, // 快速解析选择器的辅助函数 quickParse = function( selector ) { var quick = rquickIs.exec( selector ); if ( quick ) { // 0 1 2 3 // [ _, tag, id, class ] quick[1] = ( quick[1] || "" ).toLowerCase(); quick[3] = quick[3] && new RegExp( "(?:^|\\s)" + quick[3] + "(?:\\s|$)" ); } return quick; }, // 快速检查元素是否匹配选择器的函数 quickIs = function( elem, m ) { var attrs = elem.attributes || {}; return ( (!m[1] || elem.nodeName.toLowerCase() === m[1]) && (!m[2] || (attrs.id || {}).value === m[2]) && (!m[3] || m[3].test( (attrs[ "class" ] || {}).value )) ); }, // 处理hover事件hack的函数 hoverHack = function( events ) { return jQuery.event.special.hover ? events : events.replace( rhoverHack, "mouseenter$1 mouseleave$1" ); }; /* * 事件管理的帮助函数——不是公共接口的一部分 * 许多想法来自Dean Edwards' addEvent库。 */ jQuery.event = { add: function( elem, types, handler, data, selector ) { var elemData, eventHandle, events, t, tns, type, namespaces, handleObj, handleObjIn, quick, handlers, special; // 不要为noData或文本/注释节点附加事件(允许纯对象) if ( elem.nodeType === 3 || elem.nodeType === 8 || !types || !handler || !(elemData = jQuery._data( elem )) ) { return; } // 调用者可以传入一个自定义数据对象代替处理器 if ( handler.handler ) { handleObjIn = handler; handler = handleObjIn.handler; selector = handleObjIn.selector; } // 确保处理器有一个唯一的ID,用于稍后查找/移除它 if ( !handler.guid ) { handler.guid = jQuery.guid++; } // 初始化元素的事件结构和主处理器,如果这是第一个 events = elemData.events; if ( !events ) { elemData.events = events = {}; } eventHandle = elemData.handle; if ( !eventHandle ) { elemData.handle = eventHandle = function( e ) { // 丢弃jQuery.event.trigger()的第二个事件 // 当一个页面卸载后调用事件 return typeof jQuery !== "undefined" && (!e || jQuery.event.triggered !== e.type) ? jQuery.event.dispatch.apply( eventHandle.elem, arguments ) : undefined; }; // 将elem作为handle函数的属性添加,以防止IE非原生事件的内存泄漏 eventHandle.elem = elem; } } }; // Handle multiple events separated by a space // jQuery(...).bind("mouseover mouseout", fn); // 将types字符串修剪并分割成数组 types = jQuery.trim( hoverHack(types) ).split( " " ); for ( t = 0; t < types.length; t++ ) { // 通过正则表达式获取事件类型和命名空间 tns = rtypenamespace.exec( types[t] ) || []; type = tns[1]; namespaces = ( tns[2] || "" ).split( "." ).sort(); // 如果事件类型发生变化,使用变化后的类型的特定事件处理器 special = jQuery.event.special[ type ] || {}; // 如果定义了选择器,确定特殊事件API类型,否则使用给定的类型 type = ( selector ? special.delegateType : special.bindType ) || type; // 根据新重置的类型更新special special = jQuery.event.special[ type ] || {}; // handleObj将被传递给所有事件处理器 handleObj = jQuery.extend({ type: type, origType: tns[1], data: data, handler: handler, guid: handler.guid, selector: selector, quick: selector && quickParse( selector ), namespace: namespaces.join(".") }, handleObjIn ); // 如果我们是第一个,初始化事件处理器队列 handlers = events[ type ]; if ( !handlers ) { handlers = events[ type ] = []; handlers.delegateCount = 0; // 只有当特殊事件处理器返回false时,才使用addEventListener/attachEvent if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { // 将全局事件处理器绑定到元素上 if ( elem.addEventListener ) { elem.addEventListener( type, eventHandle, false ); } else if ( elem.attachEvent ) { elem.attachEvent( "on" + type, eventHandle ); } } } if ( special.add ) { special.add.call( elem, handleObj ); if ( !handleObj.handler.guid ) { handleObj.handler.guid = handler.guid; } } // 添加到元素的处理器列表中,代表在前面 if ( selector ) { handlers.splice( handlers.delegateCount++, 0, handleObj ); } else { handlers.push( handleObj ); } // 跟踪哪些事件曾经被使用过,用于事件优化 jQuery.event.global[ type ] = true; } // 将elem置空以防止IE中的内存泄漏 elem = null; }, // 从元素上移除一个或一组事件 remove: function( elem, types, handler, selector, mappedTypes ) { var elemData = jQuery.hasData( elem ) && jQuery._data( elem ), t, tns, type, origType, namespaces, origCount, j, events, special, handle, eventType, handleObj; if ( !elemData || !(events = elemData.events) ) { return; } // 为types的每个类型.namespace;类型可能被省略 types = jQuery.trim( hoverHack( types || "" ) ).split(" "); for ( t = 0; t < types.length; t++ ) { tns = rtypenamespace.exec( types[t] ) || []; type = origType = tns[1]; namespaces = tns[2]; // 如果没有类型,为元素解除所有事件(在此命名空间下,如果提供了) if ( !type ) { for ( type in events ) { jQuery.event.remove( elem, type + types[ t ], handler, selector, true ); } continue; } special = jQuery.event.special[ type ] || {}; type = ( selector? special.delegateType : special.bindType ) || type; eventType = events[ type ] || []; origCount = eventType.length; namespaces = namespaces ? new RegExp("(^|\\.)" + namespaces.split(".").sort().join("\\.(?:.*\\.)?") + "(\\.|$)") : null; // 移除匹配的事件 for ( j = 0; j < eventType.length; j++ ) { handleObj = eventType[ j ]; if ( ( mappedTypes || origType === handleObj.origType ) && ( !handler || handler.guid === handleObj.guid ) && ( !namespaces || namespaces.test( handleObj.namespace ) ) && ( !selector || selector === handleObj.selector || selector === "**" && handleObj.selector ) ) { eventType.splice( j--, 1 ); if ( handleObj.selector ) { eventType.delegateCount--; } if ( special.remove ) { special.remove.call( elem, handleObj ); } } } // 如果eventType被移除且没有更多处理器存在,则移除通用事件处理器 // (避免在移除特殊事件处理器时出现潜在的无限递归) if ( eventType.length === 0 && origCount !== eventType.length ) { if ( !special.teardown || special.teardown.call( elem, namespaces ) === false ) { jQuery.removeEvent( elem, type, elemData.handle ); } delete events[ type ]; } } // 如果它不再被使用,则移除expando if ( jQuery.isEmptyObject( events ) ) { handle = elemData.handle; if ( handle ) { handle.elem = null; } // removeData也检查空并清除expando如果为空 // 因此使用它代替delete jQuery.removeData( elem, [ "events", "handle" ], true ); } }, // 如果没有附加处理器,则可以安全地短路的事件。 // 原生DOM事件不应被添加,它们可能有内联处理器。 customEvent: { "getData": true, "setData": true, "changeData": true }, // 触发事件 trigger: function( event, data, elem, onlyHandlers ) { // 不要在文本和注释节点上做事件 if ( elem && (elem.nodeType === 3 || elem.nodeType === 8) ) { return; } // 事件对象或事件类型 var type = event.type || event, namespaces = [], cache, exclusive, i, cur, old, ontype, special, handle, eventPath, bubbleType; // focus/blur变为focusin/out;确保我们不是在现在触发它们 if ( rfocusMorph.test( type + jQuery.event.triggered ) ) { return; } if ( type.indexOf( "!" ) >= 0 ) { // 独家事件只触发确切的事件(无命名空间) type = type.slice(0, -1); exclusive = true; } if ( type.indexOf( "." ) >= 0 ) { // 命名空间触发;创建一个正则表达式来匹配handle()中的事件类型 namespaces = type.split("."); type = namespaces.shift(); namespaces.sort(); } if ( (!elem || jQuery.event.customEvent[ type ]) && !jQuery.event.global[ type ] ) { // 没有jQuery处理器用于此事件类型,它不能有内联处理器 return; } // 调用者可以传入一个Event对象、对象或只是一个事件类型字符串 event = typeof event === "object" ? // jQuery.Event对象 event[ jQuery.expando ] ? event : // 对象字面量 new jQuery.Event( type, event ) : // 只是事件类型(字符串) new jQuery.Event( type ); event.type = type; event.isTrigger = true; event.exclusive = exclusive; event.namespace = namespaces.join( "." ); event.namespace_re = event.namespace? new RegExp("(^|\\.)" + namespaces.join("\\.(?:.*\\.)?") + "(\\.|$)") : null; ontype = type.indexOf( ":" ) < 0 ? "on" + type : ""; // 处理全局触发 if ( !elem ) { // TODO: 停止嘲笑数据缓存;移除全局事件并总是附加到文档 cache = jQuery.cache; for ( i in cache ) { if ( cache[ i ].events && cache[ i ].events[ type ] ) { jQuery.event.trigger( event, data, cache[ i ].handle.elem, true ); } } return; } // 清理事件以防它被重用 event.result = undefined; if ( !event.target ) { event.target = elem; } // 克隆任何传入的数据并在前面添加事件,创建处理器参数列表 data = data != null ? jQuery.makeArray( data ) : []; data.unshift( event ); // 允许特殊事件超越常规 special = jQuery.event.special[ type ] || {}; if ( special.trigger && special.trigger.apply( elem, data ) === false ) { return; } // 根据W3C事件规范预先确定事件传播路径 // 冒泡到文档,然后到窗口;注意全局ownerDocument变量(#9724) eventPath = [[ elem, special.bindType || type ]]; if ( !only Handlers && !special.noBubble && !jQuery.isWindow( elem ) ) { bubbleType = special.delegateType || type; cur = rfocusMorph.test( bubbleType + type ) ? elem : elem.parentNode; old = null; for ( ; cur; cur = cur.parentNode ) { eventPath.push([ cur, bubbleType ]); old = cur; } // 只有在到达文档时才添加窗口(例如,不是普通对象或脱离DOM) if ( old && old === elem.ownerDocument ) { eventPath.push([ old.defaultView || old.parentWindow || window, bubbleType ]); } } // 在事件路径上触发处理器 for ( i = 0; i < eventPath.length && !event.isPropagationStopped(); i++ ) { cur = eventPath[i][0]; event.type = eventPath[i][1]; handle = ( jQuery._data( cur, "events" ) || {} )[ event.type ] && jQuery._data( cur, "handle" ); if ( handle ) { handle.apply( cur, data ); } // 注意这是一个裸JS函数而不是jQuery处理器 handle = ontype && cur[ ontype ]; if ( handle && jQuery.acceptData( cur ) && handle.apply( cur, data ) === false ) { event.preventDefault(); } } event.type = type; // If nobody prevented the default action, do it now if ( !onlyHandlers && !event.isDefaultPrevented() ) { if ( (!special._default || special._default.apply( elem.ownerDocument, data ) === false) && !(type === "click" && jQuery.nodeName( elem, "a" )) && jQuery.acceptData( elem ) ) { // Call a native DOM method on the target with the same name name as the event. // Can't use an .isFunction() check here because IE6/7 fails that test. // Don't do default actions on window, that's where global variables be (#6170) // IE<9 dies on focus/blur to hidden element (#1486) if ( ontype && elem[ type ] && ((type !== "focus" && type !== "blur") || event.target.offsetWidth !== 0) && !jQuery.isWindow( elem ) ) { // Don't re-trigger an onFOO event when we call its FOO() method old = elem[ ontype ]; if ( old ) { elem[ ontype ] = null; } // Prevent re-triggering of the same event, since we already bubbled it above jQuery.event.triggered = type; elem[ type ](); jQuery.event.triggered = undefined; if ( old ) { elem[ ontype ] = old; } } } } return event.result; }, // dispatch 函数负责触发事件,并执行相应的事件处理程序 dispatch: function( event ) { // 将原生事件对象转换为可写的 jQuery.Event 对象 event = jQuery.event.fix( event || window.event ); // 获取与事件类型相关联的处理程序数组 var handlers = ( (jQuery._data( this, "events" ) || {} )[ event.type ] || []), delegateCount = handlers.delegateCount, args = [].slice.call( arguments, 0 ), run_all = !event.exclusive && !event.namespace, special = jQuery.event.special[ event.type ] || {}, handlerQueue = [], i, j, cur, jqcur, ret, selMatch, matched, matches, handleObj, sel, related; // 用修正后的 jQuery.Event 替换原生事件对象 args[0] = event; event.delegateTarget = this; // 调用映射类型的 preDispatch 钩子,如果需要可以终止事件 if ( special.preDispatch && special.preDispatch.call( this, event ) === false ) { return; } // 确定如果有委托事件应该运行的处理程序 if ( delegateCount && !(event.button && event.type === "click") ) { // 为 .is() 重用预生成的单个 jQuery 对象 jqcur = jQuery(this); jqcur.context = this.ownerDocument || this; for ( cur = event.target; cur != this; cur = cur.parentNode || this ) { // 不处理禁用元素上的事件(#6911, #8165) if ( cur.disabled !== true ) { selMatch = {}; matches = []; jqcur[0] = cur; for ( i = 0; i < delegateCount; i++ ) { handleObj = handlers[ i ]; sel = handleObj.selector; if ( selMatch[ sel ] === undefined ) { selMatch[ sel ] = ( handleObj.quick ? quickIs( cur, handleObj.quick ) : jqcur.is( sel ) ); } if ( selMatch[ sel ] ) { matches.push( handleObj ); } } if ( matches.length ) { handlerQueue.push({ elem: cur, matches: matches }); } } } } // 添加剩余的(直接绑定的)处理程序 if ( handlers.length > delegateCount ) { handlerQueue.push({ elem: this, matches: handlers.slice( delegateCount ) }); } // 首先运行委托;它们可能希望停止我们的传播 for ( i = 0; i < handlerQueue.length && !event.isPropagationStopped(); i++ ) { matched = handlerQueue[ i ]; event.currentTarget = matched.elem; for ( j = 0; j < matched.matches.length && !event.isImmediatePropagationStopped(); j++ ) { handleObj = matched.matches[ j ]; if ( run_all || (!event.namespace && !handleObj.namespace) || event.namespace_re && event.namespace_re.test( handleObj.namespace ) ) { event.data = handleObj.data; event.handleObj = handleObj; ret = ( (jQuery.event.special[ handleObj.origType ] || {}).handle || handleObj.handler ) .apply( matched.elem, args ); if ( ret !== undefined ) { event.result = ret; if ( ret === false ) { event.preventDefault(); event.stopPropagation(); } } } } } // 调用映射类型的 postDispatch 钩子 if ( special.postDispatch ) { special.postDispatch.call( this, event ); } return event.result; }, // 包括一些 KeyEvent 和 MouseEvent 共享的事件属性 props: "attrChange attrName relatedNode srcElement altKey bubbles cancelable ctrlKey currentTarget eventPhase metaKey relatedTarget shiftKey target timeStamp view which".split(" "), fixHooks: {}, // 处理键盘事件的钩子 keyHooks: { props: "char charCode key keyCode".split(" "), filter: function( event, original ) { if ( event.which == null ) { event.which = original.charCode != null ? original.charCode : original.keyCode; } return event; } }, // 处理鼠标事件的钩子 mouseHooks: { props: "button buttons clientX clientY fromElement offsetX offsetY pageX pageY screenX screenY toElement".split(" "), filter: function( event, original ) { var eventDoc, doc, body, button = original.button, fromElement = original.fromElement; if ( event.pageX == null && original.clientX != null ) { eventDoc = event.target.ownerDocument || document; doc = eventDoc.documentElement; body = eventDoc.body; event.pageX = original.clientX + ( doc && doc.scrollLeft || body && body.scrollLeft || 0 ) - ( doc && doc.clientLeft || body && body.clientLeft || 0 ); event.pageY = original.clientY + ( doc && doc.scrollTop || body && body.scrollTop || 0 ) - ( doc && doc.clientTop || body && body.clientTop || 0 ); } if ( !event.relatedTarget && fromElement ) { event.relatedTarget = fromElement === event.target ? original.toElement : fromElement; } if ( !event.which && button !== undefined ) { event.which = ( button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ) ); } return event; } }, // 修正事件对象的函数 fix: function( event ) { if ( event[ jQuery.expando ] ) { return event; } var i, prop, originalEvent = event, fixHook = jQuery.event.fixHooks[ event.type ] || {}, copy = fixHook.props ? this.props.concat( fixHook.props ) : this.props; event = jQuery.Event( originalEvent ); for ( i = copy.length; i; ) { prop = copy[ --i ]; event[ prop ] = originalEvent[ prop ]; } if ( !event.target ) { event.target = originalEvent.srcElement || document; } if ( event.target.nodeType === 3 ) { event.target = event.target.parentNode; } if ( event.metaKey === undefined ) { event.metaKey = event.ctrlKey; } return fixHook.filter? fixHook.filter( event, originalEvent ) : event; }, // 特殊事件的处理 special: { ready: { setup: jQuery.bindReady }, load: { noBubble: true }, focus: { delegateType: "focusin" }, blur: { delegateType: "focusout" }, beforeunload: { setup: function( data, namespaces, eventHandle ) { if ( jQuery.isWindow( this ) ) { this.onbeforeunload = eventHandle; } }, teardown: function( namespaces, eventHandle ) { if ( this.onbeforeunload === eventHandle ) { this.onbeforeunload = null; } } } }, // 模拟事件的函数 simulate: function( type, elem, event, bubble ) { var e = jQuery.extend( new jQuery.Event(), event, { type: type, isSimulated: true, originalEvent: {} } ); if ( bubble ) { jQuery.event.trigger( e, null, elem ); } else { jQuery.event.dispatch.call( elem, e ); } if ( e.isDefaultPrevented() ) { event.preventDefault(); } }, // 处理事件的脱绑和绑定 jQuery.event.handle = jQuery.event.dispatch; jQuery.removeEvent = document.removeEventListener ? function( elem, type, handle ) { if ( elem.removeEventListener ) { elem.removeEventListener( type, handle, false ); } } : function( elem, type, handle ) { if ( elem.detachEvent ) { elem.detachEvent( "on" + type, handle ); } }; // jQuery.Event 构造函数 jQuery.Event = function( src, props ) { if ( !(this instanceof jQuery.Event) ) { return new jQuery.Event( src, props ); } if ( src && src.type ) { this.originalEvent = src; this.type = src.type; this.isDefaultPrevented = ( src.defaultPrevented || src.returnValue === false || src.getPreventDefault && src.getPreventDefault() ) ? returnTrue : returnFalse; } else { this.type = src; } if ( props ) { jQuery.extend( this, props ); } this.timeStamp = src && src.timeStamp || jQuery.now(); this[ jQuery.expando ] = true; }; function returnFalse() { return false; } function returnTrue() { return true; } // jQuery.Event 原型,提供 preventDefault, stopPropagation 等方法 jQuery.Event.prototype = { preventDefault: function() { this.isDefaultPrevented = returnTrue; var e = this.originalEvent; if ( !e ) { return; } // if preventDefault exists run it on the original event if ( e.preventDefault ) { e.preventDefault(); // otherwise set the returnValue property of the original event to false (IE) } else { e.returnValue = false; } }, stopPropagation: function() { this.isPropagationStopped = returnTrue; var e = this.originalEvent; if ( !e ) { return; } // if stopPropagation exists run it on the original event if ( e.stopPropagation ) { e.stopPropagation(); } // otherwise set the cancelBubble property of the original event to true (IE) e.cancelBubble = true; }, stopImmediatePropagation: function() { this.isImmediatePropagationStopped = returnTrue; this.stopPropagation(); }, isDefaultPrevented: returnFalse, isPropagationStopped: returnFalse, isImmediatePropagationStopped: returnFalse }; // 创建 mouseenter/leave 事件 jQuery.each({ mouseenter: "mouseover", mouseleave: "mouseout" }, function( orig, fix ) { jQuery.event.special[ orig ] = { delegateType: fix, bindType: fix, handle: function( event ) { var target = this, related = event.relatedTarget, handleObj = event.handleObj, selector = handleObj.selector, ret; if ( !related || (related !== target && !jQuery.contains( target, related )) ) { event.type = handleObj.origType; ret = handleObj.handler.apply( this, arguments ); event.type = fix; } return ret; } }; }); // IE submit delegation if ( !jQuery.support.submitBubbles ) { jQuery.event.special.submit = { setup: function() { // Only need this for delegated form submit events if ( jQuery.nodeName( this, "form" ) ) { return false; } // Lazy-add a submit handler when a descendant form may potentially be submitted jQuery.event.add( this, "click._submit keypress._submit", function( e ) { // Node name check avoids a VML-related crash in IE (#9807) var elem = e.target, form = jQuery.nodeName( elem, "input" ) || jQuery.nodeName( elem, "button" ) ? elem.form : undefined; if ( form && !form._submit_attached ) { jQuery.event.add( form, "submit._submit", function( event ) { event._submit_bubble = true; }); form._submit_attached = true; } }); // return undefined since we don't need an event listener }, postDispatch: function( event ) { // If form was submitted by the user, bubble the event up the tree if ( event._submit_bubble ) { delete event._submit_bubble; if ( this.parentNode && !event.isTrigger ) { jQuery.event.simulate( "submit", this.parentNode, event, true ); } } }, teardown: function() { // Only need this for delegated form submit events if ( jQuery.nodeName( this, "form" ) ) { return false; } // Remove delegated handlers; cleanData eventually reaps submit handlers attached above jQuery.event.remove( this, "._submit" ); } }; } // IE change delegation and checkbox/radio fix // 判断jQuery是否不支持change事件冒泡(如果!jQuery.support.changeBubbles为真,表示不支持) if (!jQuery.support.changeBubbles ) { // 为jQuery的事件系统中的'special'对象添加名为'change'的自定义事件相关配置 jQuery.event.special.change = { // 'setup'函数会在绑定事件时被调用,用于进行一些初始化设置 setup: function() { // 测试当前节点的节点名是否匹配特定的表单元素相关的正则表达式(rformElems应该是在别处定义的用于匹配表单元素节点名的正则) if ( rformElems.test( this.nodeName ) ) { // 在IE浏览器中,复选框(checkbox)和单选框(radio)在失去焦点(blur)时才触发'change'事件,这里在点击(click)后且发生属性改变(propertychange)时触发它。 // 并且在'special.change.handle'中处理掉因失去焦点导致的重复的'change'事件触发情况。 // 但这样对于复选框和单选框在失去焦点后还是会第二次触发'onchange'事件。 if ( this.type === "checkbox" || this.type === "radio" ) { // 为当前元素添加名为'propertychange._change'的事件监听器,当属性改变事件发生时执行下面的回调函数 jQuery.event.add( this, "propertychange._change", function( event ) { // 如果属性改变事件的原始事件中属性名为'checked'(也就是复选框或单选框的选中状态改变了) if ( event.originalEvent.propertyName === "checked" ) { // 标记当前元素刚刚发生了改变 this._just_changed = true; } }); // 为当前元素添加名为'click._change'的事件监听器,当点击事件发生时执行下面的回调函数 jQuery.event.add( this, "click._change", function( event ) { // 如果当前元素刚刚发生了改变且不是通过代码手动触发的事件(!event.isTrigger) if ( this._just_changed &&!event.isTrigger ) { // 重置刚刚改变的标记 this._just_changed = false; // 模拟触发'change'事件,传递当前元素、原始点击事件以及一些其他相关参数(最后一个参数true可能有特定含义,比如冒泡相关等) jQuery.event.simulate( "change", this, event, true ); } }); } // 表示不需要执行默认的事件绑定逻辑了(可能有特定的jQuery内部机制相关含义) return false; } // 如果是委托事件(意味着事件是绑定在父元素上,等待子元素触发的情况) // 延迟添加一个针对后代输入元素的'change'事件处理程序 jQuery.event.add( this, "beforeactivate._change", function( e ) { // 获取实际触发事件的目标元素 var elem = e.target; // 再次测试目标元素的节点名是否匹配特定表单元素正则,并且该元素还没有添加过'_change'相关的事件处理程序(!elem._change_attached) if ( rformElems.test( elem.nodeName ) &&!elem._change_attached ) { // 为目标元素添加名为'change._change'的事件监听器,当'change'事件发生时执行下面的回调函数 jQuery.event.add( elem, "change._change", function( event ) { // 如果目标元素有父元素,并且事件不是模拟触发的(!event.isSimulated)也不是通过代码手动触发的(!event.isTrigger) if ( this.parentNode &&!event.isSimulated &&!event.isTrigger ) { // 模拟触发父元素的'change'事件,传递父元素、当前事件以及相关参数(同样最后一个参数true可能和冒泡等有关) jQuery.event.simulate( "change", this.parentNode, event, true ); } }); // 标记该元素已经添加过'_change'相关的事件处理程序了 elem._change_attached = true; } }); }, // 'handle'函数用于处理实际触发的事件,决定是否执行默认的事件处理逻辑等 handle: function( event ) { // 获取实际触发事件的目标元素 var elem = event.target; // 如果当前处理事件的对象不是目标元素本身(可能是冒泡上来的情况等),或者事件是模拟触发的(event.isSimulated),或者是通过代码手动触发的(event.isTrigger),或者目标元素不是单选框和复选框类型 if ( this!== elem || event.isSimulated || event.isTrigger || (elem.type!== "radio" && elem.type!== "checkbox") ) { // 执行默认的事件处理程序,也就是调用原本绑定的事件处理函数,传递相关参数(arguments包含了事件相关的参数等) return event.handleObj.handler.apply( this, arguments ); } }, // 'teardown'函数会在解绑事件时被调用,用于清理相关的事件绑定等操作 teardown: function() { // 移除当前元素上所有名称包含'._change'的事件监听器 jQuery.event.remove( this, "._change" ); // 返回当前节点的节点名是否匹配特定表单元素的正则表达式的测试结果(可能用于判断是否还有相关清理工作要做等) return rformElems.test( this.nodeName ); } }; } // 判断jQuery是否不支持焦点进入(focusin)和焦点离开(focusout)事件冒泡(如果!jQuery.support.focusinBubbles为真,表示不支持) if (!jQuery.support.focusinBubbles ) { // 遍历包含'focus'和'blur'的对象,将'focus'映射为'focusin','blur'映射为'focusout',进行相关操作 jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) { // 用于记录当前有多少个地方想要使用'focusin'或'focusout'事件,初始化为0 var attaches = 0, // 定义事件处理函数,用于模拟触发对应的'fix'(也就是'focusin'或'focusout')事件 handler = function( event ) { jQuery.event.simulate( fix, event.target, jQuery.event.fix( event ), true ); }; // 为jQuery的事件系统中的'special'对象添加名为'fix'(也就是'focusin'或'focusout')的自定义事件相关配置 jQuery.event.special[ fix ] = { //'setup'函数会在绑定'focusin'或'focusout'事件时被调用,用于进行初始化设置 setup: function() { // 当第一次有地方绑定该事件时(attaches初始为0,自增后为1) if ( attaches++ === 0 ) { // 在文档对象上添加原生的'orig'(也就是'focus'或'blur')事件的捕获阶段监听器,绑定上面定义的'handler'函数 document.addEventListener( orig, handler, true ); } }, // 'teardown'函数会在解绑'focusin'或'focusout'事件时被调用,用于清理相关操作 teardown: function() { // 当所有绑定该事件的地方都解绑了(attaches自减后为0) if ( --attaches === 0 ) { // 移除文档对象上对应的原生'orig'(也就是'focus'或'blur')事件的捕获阶段监听器 document.removeEventListener( orig, handler, true ); } } }; }); } // 使用jQuery.fn.extend方法来扩展jQuery的原型对象,添加一系列与事件处理相关的方法 jQuery.fn.extend({ // on方法用于绑定事件,可以有多种参数形式来处理不同的绑定场景 on: function( types, selector, data, fn, /*INTERNAL*/ one ) { var origFn, type; // 第一种情况:如果types参数是一个对象,意味着可以传入多个事件类型及其对应的处理函数的映射形式 if ( typeof types === "object" ) { // 如果selector参数不是字符串类型(且不为null,这里原注释中虽然注释掉了!= null判断,但可能实际逻辑中有此考虑),说明可能传入的参数形式是( types-Object, data )这种,没有selector参数 if ( typeof selector!== "string" ) { // && selector!= null // 将data参数赋值为原本的selector(这里selector可能传了实际的数据对象等),并将selector设为undefined,符合( types-Object, data )的参数预期 data = data || selector; selector = undefined; } // 遍历传入的types对象的每个属性(也就是每个事件类型) for ( type in types ) { // 递归调用on方法,逐个绑定每个事件类型及其对应的处理函数,实现对多个事件的绑定 this.on( type, selector, data, types[ type ], one ); } // 返回当前的jQuery对象实例,以便支持链式调用 return this; } // 第二种情况:如果data和fn都为null,说明传入的参数形式可能是( types, fn )这种,将fn赋值为原本的selector,然后将data和selector都设为undefined来符合预期的参数格式 if ( data == null && fn == null ) { fn = selector; data = selector = undefined; } else if ( fn == null ) { // 如果fn为null,进一步判断selector的类型,如果是字符串类型,说明参数形式可能是( types, selector, fn ),将fn赋值为原本的data,data设为undefined if ( typeof selector === "string" ) { fn = data; data = undefined; } else { // 如果selector不是字符串类型,参数形式可能是( types, data, fn ),将fn赋值为原本的data,将data赋值为原本的selector,再将selector设为undefined fn = data; data = selector; selector = undefined; } } // 如果fn的值为false,将fn指向一个名为returnFalse的函数(这里returnFalse应该是在别处定义的,可能返回false的函数,用于特定的事件处理逻辑) if ( fn === false ) { fn = returnFalse; } else if (!fn ) { // 如果fn为假值(比如undefined、null等),直接返回当前的jQuery对象实例,不进行事件绑定操作 return this; } // 如果one参数的值为1,表示只希望事件触发一次 if ( one === 1 ) { origFn = fn; // 重新定义fn函数,在事件触发时,先移除对应的事件绑定(通过off方法,这里的jQuery()创建了一个空的jQuery对象集合,但不影响off方法的调用逻辑,因为off方法内部可以处理这种情况,根据event中的信息来确定要移除的事件),然后再执行原本的事件处理函数(origFn) fn = function( event ) { // Can use an empty set, since event contains the info jQuery().off( event ); return origFn.apply( this, arguments ); }; // 让新的fn函数继承原本origFn函数的唯一标识符(guid),如果origFn没有guid,则生成一个新的guid,保证调用者可以通过origFn来移除这个只触发一次的事件绑定 fn.guid = origFn.guid || ( origFn.guid = jQuery.guid++ ); } // 遍历当前jQuery对象集合中的每个元素,调用jQuery.event.add方法为每个元素添加指定的事件绑定,传入事件类型、处理函数、数据以及选择器等参数 return this.each( function() { jQuery.event.add( this, types, fn, data, selector ); }); }, // one方法是对on方法的一个简单封装,用于方便地绑定只触发一次的事件,直接调用on方法并传入参数1表示只触发一次 one: function( types, selector, data, fn ) { return this.on( types, selector, data, fn, 1 ); }, // off方法用于解绑事件,同样有多种参数形式来应对不同的解绑场景 off: function( types, selector, fn ) { // 如果types参数是一个已经触发过的jQuery.Event对象(它有preventDefault、handleObj等属性),说明是基于已触发事件对象来解绑对应的事件绑定 if ( types && types.preventDefault && types.handleObj ) { // 获取事件对象中的handleObj属性,它包含了事件相关的原始类型、命名空间、选择器以及处理函数等关键信息 var handleObj = types.handleObj; // 通过事件对象中的delegateTarget属性找到对应的元素(可能是委托事件绑定的目标元素),然后调用off方法来解绑该元素上符合条件的事件,条件包括原始事件类型、命名空间、选择器以及处理函数等信息 jQuery( types.delegateTarget ).off( handleObj.namespace? handleObj.origType + "." + handleObj.namespace : handleObj.origType, handleObj.selector, handleObj.handler ); return this; } // 如果types参数是一个对象,意味着可以传入多个事件类型及其对应的处理函数的映射形式来批量解绑事件 if ( typeof types === "object" ) { // 遍历传入的types对象的每个属性(也就是每个事件类型) for ( var type in types ) { // 递归调用off方法,逐个解绑每个事件类型及其对应的处理函数,实现对多个事件的解绑 this.off( type, selector, types[ type ] ); } return this; } // 如果selector参数是false或者是一个函数类型,说明可能传入的参数形式是( types [, fn] ),将fn赋值为原本的selector,然后将selector设为undefined来符合预期的参数格式 if ( selector === false || typeof selector === "function" ) { fn = selector; selector = undefined; } // 如果fn的值为false,将fn指向一个名为returnFalse的函数(和on方法中类似的处理逻辑) if ( fn === false ) { fn = returnFalse; } // 遍历当前jQuery对象集合中的每个元素,调用jQuery.event.remove方法为每个元素移除指定的事件绑定,传入事件类型、处理函数以及选择器等参数 return this.each(function() { jQuery.event.remove( this, types, fn, selector ); }); }, // bind方法是对on方法的一种简化调用形式,用于绑定事件,它传入事件类型、数据以及处理函数,将选择器参数设为null,也就是不涉及选择器相关的复杂事件绑定场景 bind: function( types, data, fn ) { return this.on( types, null, data, fn ); }, // unbind方法是对off方法的一种简化调用形式,用于解绑事件,它传入事件类型以及处理函数,将选择器参数设为null,也就是不涉及选择器相关的复杂事件解绑场景 unbind: function( types, fn ) { return this.off( types, null, fn ); }, // live方法用于给当前元素的上下文(context)绑定事件,通过调用on方法,传入事件类型、选择器(this.selector表示当前元素相关的选择器)、数据以及处理函数等来实现,实现一种类似事件委托的效果,让符合选择器的后代元素可以响应事件 live: function( types, data, fn ) { jQuery( this.context ).on( types, this.selector, data, fn ); return this; }, // die方法用于移除通过live方法绑定的事件,通过调用off方法,传入事件类型以及选择器(如果this.selector不存在则使用"**"作为通配符选择器,确保能移除相关的事件绑定)等来实现 die: function( types, fn ) { jQuery( this.context ).off( types, this.selector || "**", fn ); return this; }, // delegate方法是对on方法的另一种调用形式,用于进行事件委托绑定,传入选择器、事件类型、数据以及处理函数等参数,本质上就是调用on方法来完成事件委托相关的事件绑定操作 delegate: function( selector, types, data, fn ) { return this.on( types, selector, data, fn ); }, // undelegate方法用于移除通过delegate方法绑定的事件,根据传入参数的个数来判断具体的解绑逻辑,如果只传入一个参数,说明可能是基于命名空间等来解绑,调用off方法并传入相应参数;如果传入多个参数,就是常规的传入事件类型、选择器以及处理函数等参数来解绑对应的事件委托绑定 undelegate: function( selector, types, fn ) { // ( namespace ) or ( selector, types [, fn] ) return arguments.length == 1? this.off( selector, "**" ) : this.off( types, selector, fn ); }, // trigger方法用于触发指定的事件,遍历当前jQuery对象集合中的每个元素,调用jQuery.event.trigger方法来触发传入的事件类型以及传递相关的数据,触发每个元素上对应的事件 trigger: function( type, data ) { return this.each(function() { jQuery.event.trigger( type, data, this ); }); }, // triggerHandler方法用于触发指定的事件,但和trigger方法不同的是,它只在第一个元素(this[0])上触发事件,并且以一种特殊的方式触发(最后一个参数true可能有特定的内部触发机制含义,比如不进行事件冒泡等),如果存在第一个元素则调用jQuery.event.trigger方法触发事件并返回结果,否则返回undefined之类的值(因为没有元素可触发事件) triggerHandler: function( type, data ) { if ( this[0] ) { return jQuery.event.trigger( type, data, this[0], true ); } }, // toggle方法用于实现点击事件的切换效果,也就是每次点击执行不同的函数 toggle: function( fn ) { // 保存传入的所有参数,方便在闭包中访问 var args = arguments, guid = fn.guid || jQuery.guid++, i = 0, toggler = function( event ) { // 计算出当前应该执行的函数索引,通过获取当前元素上存储的上一次执行的函数索引(通过jQuery._data方法获取,以"lastToggle" + fn.guid为键,初始化为0,如果不存在则默认为0),然后取余得到本次要执行的函数索引 var lastToggle = ( jQuery._data( this, "lastToggle" + fn.guid ) || 0 ) % i; // 更新当前元素上存储的上一次执行的函数索引,自增1,为下一次点击做准备 jQuery._data( this, "lastToggle" + fn.guid, lastToggle + 1 ); // 阻止事件的默认行为,比如阻止链接的跳转等(如果是在点击链接等可触发默认行为的元素上绑定的toggle事件) event.preventDefault(); // 执行对应的函数,并返回执行结果,如果结果为假值则返回false(可能用于一些逻辑判断等) return args[ lastToggle ].apply( this, arguments ) || false; }; // 让toggler函数继承传入函数的唯一标识符(guid),保证可以通过这个标识符来统一管理相关的事件绑定等操作 toggler.guid = guid; // 遍历所有传入的函数参数,让每个函数都继承相同的唯一标识符(guid),使得它们可以作为一组来进行相关的事件绑定和管理 while ( i < args.length ) { args[ i++ ].guid = guid; } // 将toggler函数绑定到当前jQuery对象集合的点击事件(click)上,实现点击切换执行不同函数的效果 return this.click( toggler ); }, // hover方法用于方便地绑定鼠标移入(mouseenter)和鼠标移出(mouseleave)事件,传入对应的处理函数,如果只传入一个函数则同时作为鼠标移入和移出的处理函数,通过链式调用分别调用mouseenter和mouseleave方法来绑定事件 hover: function( fnOver, fnOut ) { return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver ); } }); // 遍历一个包含众多常见事件名称的字符串,通过空格分割后得到的事件名称数组,对每个事件名称进行相关操作 jQuery.each( ("blur focus focusin focusout load resize scroll unload click dblclick " + "mousedown mouseup mousemove mouseover mouseout mouseenter mouseleave " + "change select submit keydown keypress keyup error contextmenu").split(" "), function( i, name ) { // 为jQuery.fn对象(也就是jQuery的原型对象)添加以事件名称为属性名的方法,用于方便地绑定对应事件 jQuery.fn[ name ] = function( data, fn ) { if ( fn == null ) { fn = data; data = null; } // 根据传入参数的个数来决定是绑定事件(传入了data和fn参数)还是触发事件(只传入事件名称作为参数,没有其他参数),如果传入参数个数大于0则调用on方法绑定事件,否则调用trigger方法触发事件 return arguments.length > 0? this.on( name, null, data, fn ) : this.trigger( name ); }; // 如果jQuery.attrFn对象存在(可能是用于处理元素属性相关的一个对象,和事件有一定关联等情况),在其上面标记当前事件名称对应的属性为true(具体用途可能要看jQuery.attrFn在其他地方的使用逻辑) if ( jQuery.attrFn ) { jQuery.attrFn[ name ] = true; } // 如果事件名称匹配键盘事件相关的正则表达式(rkeyEvent应该是在别处定义的用于判断键盘事件的正则),将当前事件名称对应的事件修复钩子(fixHooks,用于在事件处理过程中进行一些兼容性等方面的修复操作)指向jQuery.event.keyHooks(应该是处理键盘事件的通用钩子函数等) if ( rkeyEvent.test( name ) ) { jQuery.event.fixHooks[ name ] = jQuery.event.keyHooks; } // 如果事件名称匹配鼠标事件相关的正则表达式(rmouseEvent应该是在别处定义的用于判断鼠标事件的正则),将当前事件名称对应的事件修复钩子(fixHooks)指向jQuery.event.mouseHooks(应该是处理鼠标事件的通用钩子函数等) if ( rmouseEvent.test( name ) ) { jQuery.event.fixHooks[ name ] = jQuery.event.mouseHooks; } }); /*! * Sizzle CSS Selector Engine * Copyright 2011, The Dojo Foundation * Released under the MIT, BSD, and GPL Licenses. * More information: http://sizzlejs.com/ */ // 立即执行函数,创建一个独立的作用域,避免变量污染全局环境 (function () { // 定义一个正则表达式用于分割选择器字符串,它能匹配各种复杂的选择器语法结构,例如括号包裹的内容、方括号包裹的内容、转义字符、普通字符等,并提取相关部分 var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, // 生成一个随机的扩展属性名称(用于在对象上添加自定义属性来做一些标记等操作,避免与其他属性冲突),通过在随机数基础上处理得到一个字符串 expando = "sizcache" + (Math.random() + '').replace('.', ''), done = 0, // 获取Object原型上的toString方法的引用,用于后续判断对象类型 toString = Object.prototype.toString, hasDuplicate = false, baseHasDuplicate = true, // 用于匹配转义字符的正则表达式 rBackslash = /\\/g, // 用于匹配回车换行符的正则表达式 rReturn = /\r\n/g, // 用于匹配非单词字符(比如标点符号等)的正则表达式 rNonWord = /\W/; // 这里检查JavaScript引擎是否使用了某种优化,导致不总是调用我们的比较函数。如果是这种情况(目前已知包括Google Chrome),则丢弃hasDuplicate的值。 // 通过对包含两个0的数组进行排序操作,并在排序比较函数中修改baseHasDuplicate的值来检测这种情况 [0, 0].sort(function () { baseHasDuplicate = false; return 0; }); // 定义Sizzle函数,它是主要的选择器解析和元素查找函数,用于根据给定的选择器在指定的上下文中查找匹配的元素 var Sizzle = function (selector, context, results, seed) { // 如果没有传入results参数,则初始化为空数组,用于存储查找结果 results = results || []; // 如果没有传入context参数,则默认为文档对象(document),表示查找的上下文范围 context = context || document; // 保存原始的查找上下文对象,方便后续可能的使用 var origContext = context; // 如果传入的上下文对象不是元素节点(nodeType为1表示元素节点,9表示文档节点),则直接返回空数组,因为无法在这样的对象中查找元素 if (context.nodeType!== 1 && context.nodeType!== 9) { return []; } // 如果没有传入选择器或者选择器类型不是字符串,也直接返回results(此时可能为空数组),不符合查找元素的基本要求 if (!selector || typeof selector!== "string") { return results; } // 定义一系列局部变量,用于后续在选择器解析和元素查找过程中的各种操作 var m, set, checkSet, extra, ret, cur, pop, i, prune = true, // 判断上下文是否是XML文档环境(通过调用Sizzle.isXML方法来判断,该方法应该在别处定义) contextXML = Sizzle.isXML(context), // 用于存储分割后的选择器各个部分,方便后续按顺序处理 parts = [], soFar = selector; // 重置chunker正则表达式的匹配位置(从开头开始匹配),然后通过循环不断用chunker正则表达式分割选择器字符串 do { chunker.exec(""); m = chunker.exec(soFar); if (m) { // 更新剩余未处理的选择器部分 soFar = m[3]; // 将匹配到的选择器部分添加到parts数组中 parts.push(m[1]); // 如果存在分隔符(表示后面还有更多选择器部分需要处理),提取额外的部分并跳出循环,后续会单独处理这部分 if (m[2]) { extra = m[3]; break; } } } while (m); // 如果分割后的选择器部分数量大于1,并且原始选择器匹配特定的位置相关模式(origPOS应该是在别处定义的用于判断位置相关选择器的正则或函数等) if (parts.length > 1 && origPOS.exec(selector)) { // 如果分割后的部分只有两个,且第一个部分是相对位置选择器(Expr.relative应该是一个包含各种相对位置选择器相关信息的对象,在别处定义) if (parts.length === 2 && Expr.relative[parts[0]]) { // 调用posProcess函数(应该是处理位置相关选择器的函数,在别处定义)来处理这两个部分组成的选择器,并获取匹配的元素集合 set = posProcess(parts[0] + parts[1], context, seed); } else { // 如果第一个部分是相对位置选择器,将初始的元素集合设为包含上下文对象的数组(也就是从上下文开始查找),否则调用Sizzle函数(递归调用自身)查找第一个选择器部分对应的元素集合 set = Expr.relative[parts[0]]? [context] : Sizzle(parts.shift(), context); // 循环处理剩余的选择器部分 while (parts.length) { selector = parts.shift(); // 如果当前选择器部分是相对位置选择器,将其与下一个部分合并(因为相对位置选择器通常要和后面的部分一起处理才有意义) if (Expr.relative[selector]) { selector += parts.shift(); } // 再次调用posProcess函数处理合并后的选择器,并更新匹配的元素集合 set = posProcess(selector, set, seed); } } } else { // 如果选择器的根部分(第一个部分)是ID选择器(并且满足一些其他条件,比如不是内层选择器也是ID选择器等情况,这样做可能是为了优化查找速度),则采取快捷方式先查找该ID对应的元素,并更新上下文为找到的元素(如果有的话) if (!seed && parts.length > 1 && context.nodeType === 9 &&!contextXML && Expr.match.ID.test(parts[0]) &&!Expr.match.ID.test(parts[parts.length - 1])) { ret = Sizzle.find(parts.shift(), context, contextXML); context = ret.expr? Sizzle.filter(ret.expr, ret.set)[0] : ret.set[0]; } // 如果存在上下文(也就是前面处理后找到了合适的查找起点) if (context) { // 如果传入了seed参数,构建一个包含expr和set属性的对象,expr为剩余选择器部分的最后一个(通过pop取出),set为传入的seed(可能是已经筛选过的元素集合等);否则调用Sizzle.find函数查找最后一个选择器部分对应的元素集合(根据不同情况传入合适的上下文等参数) ret = seed? { expr: parts.pop(), set: makeArray(seed) } : Sizzle.find(parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode? context.parentNode : context, contextXML); // 如果找到的结果中有expr属性(表示可能经过了筛选等操作),则调用Sizzle.filter函数进一步过滤元素集合;否则直接使用找到的元素集合 set = ret.expr? Sizzle.filter(ret.expr, ret.set) : ret.set; // 如果还有剩余的选择器部分需要处理,则将当前的元素集合转为数组(方便后续循环处理) if (parts.length > 0) { checkSet = makeArray(set); } else { // 如果没有剩余选择器部分了,设置prune为false,表示不需要进行后续的一些筛选操作(具体要看后面的逻辑,可能和元素集合的处理方式有关) prune = false; } // 循环处理剩余的选择器部分(从后往前处理,因为可能涉及到相对位置选择器等依赖后面元素的情况) while (parts.length) { cur = parts.pop(); pop = cur; // 如果当前选择器部分不是相对位置选择器,将其设为空字符串(可能有特殊处理逻辑,比如当作普通选择器等情况) if (!Expr.relative[cur]) { cur = ""; } else { // 如果是相对位置选择器,取出下一个选择器部分(可能和当前相对位置选择器配合使用) pop = parts.pop(); } // 如果取出的配合使用的选择器部分为null(可能没有下一个部分了等情况),则将其设为上下文对象(也就是当作相对当前上下文来处理) if (pop == null) { pop = context; } // 调用相对位置选择器对应的处理函数(Expr.relative[cur]应该是一个函数,根据不同的相对位置选择器有不同的处理逻辑),传入当前的元素集合、相关的参考元素以及是否是XML文档环境等参数,来更新元素集合 Expr.relative[cur](checkSet, pop, contextXML); } } else { // 如果没有合适的上下文(比如传入的上下文不符合要求等情况),将checkSet和parts都设为空数组,后续可能会根据这个情况进行错误处理等操作 checkSet = parts = []; } } // 如果checkSet为空(可能前面处理过程中没有正确赋值等情况),则将其设为set(也就是前面查找得到的元素集合) if (!checkSet) { checkSet = set; } // 如果checkSet仍然为空,说明出现了错误,调用Sizzle.error函数(应该是用于输出错误信息等的函数,在别处定义),传入当前的选择器部分或者整个选择器字符串来提示错误 if (!checkSet) { Sizzle.error(cur || selector); } // 判断checkSet的类型是否是数组(通过调用toString方法并判断返回值是否是"[object Array]"来确定) if (toString.call(checkSet) === "[object Array]") { // 如果不需要进行筛选操作(prune为false),直接将checkSet中的元素添加到results数组中(通过apply方法将元素逐个添加,模拟数组的pushAll操作) if (!prune) { results.push.apply(results, checkSet); } else if (context && context.nodeType === 1) { // 如果需要筛选,并且上下文是元素节点,循环遍历checkSet数组,判断每个元素是否符合条件(比如元素存在、是元素节点并且满足Sizzle.contains方法判断的包含关系等,Sizzle.contains应该是判断元素包含关系的函数,在别处定义),符合条件的元素对应的set中的元素添加到results数组中 for (i = 0; checkSet[i]!= null; i++) { if (checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i]))) { results.push(set[i]); } } } else { // 如果需要筛选,但上下文不是元素节点,循环遍历checkSet数组,只判断元素是否是元素节点,是元素节点的对应的set中的元素添加到results数组中 for (i = 0; checkSet[i]!= null; i++) { if (checkSet[i] && checkSet[i].nodeType === 1) { results.push(set[i]); } } } } else { // 如果checkSet不是数组类型,调用makeArray函数(应该是将类数组对象等转为真正数组的函数,在别处定义)将其转为数组,并将结果添加到results数组中 makeArray(checkSet, results); } // 如果存在额外的选择器部分(前面分割出来的,还未处理的部分) if (extra) { // 递归调用Sizzle函数,传入额外的选择器部分、原始的上下文对象以及results数组(用于继续添加查找结果)和seed(可能用于延续之前的查找逻辑等) Sizzle(extra, origContext, results, seed); // 调用Sizzle.uniqueSort函数(应该是对结果数组进行去重和排序的函数,在别处定义)对results数组进行处理,确保结果的唯一性和顺序性 Sizzle.uniqueSort(results); } // 返回最终的查找结果数组 return results; }; // 定义Sizzle对象的uniqueSort方法,用于对结果数组进行去重和排序操作 Sizzle.uniqueSort = function (results) { // 如果sortOrder存在(sortOrder应该是在别处定义的用于排序的规则相关变量,可能是一个比较函数等,用于确定元素的排序顺序) if (sortOrder) { // 恢复hasDuplicate的值为baseHasDuplicate的值(前面可能在某些检测情况下对baseHasDuplicate进行了修改,这里重新赋值给hasDuplicate,用于后续判断是否有重复元素) hasDuplicate = baseHasDuplicate; // 使用sortOrder规则对results数组进行排序,改变数组内元素的顺序使其符合指定的排序要求 results.sort(sortOrder); // 如果存在重复元素(hasDuplicate为真) if (hasDuplicate) { // 从索引为1开始遍历results数组(因为比较重复是从第二个元素开始和前一个元素对比) for (var i = 1; i < results.length; i++) { // 如果当前元素和前一个元素相等(说明是重复元素) if (results[i] === results[i - 1]) { // 使用splice方法从数组中移除当前这个重复元素,同时将索引i自减1,因为移除元素后后面的元素会向前移动一位,下次循环需要再次检查当前位置的元素是否和前一个重复 results.splice(i--, 1); } } } } // 返回经过去重和排序后的results数组 return results; }; // 定义Sizzle对象的matches方法,它通过调用Sizzle函数,传入表达式、空的上下文、空的初始结果以及给定的元素集合(set),来判断给定的元素集合中的元素是否匹配指定的表达式(expr),本质上是利用Sizzle函数进行元素匹配的一种封装 Sizzle.matches = function (expr, set) { return Sizzle(expr, null, null, set); }; // 定义Sizzle对象的matchesSelector方法,它通过调用Sizzle函数,传入表达式、空的上下文、空的初始结果以及包含单个节点(node)的数组,然后判断返回的结果数组长度是否大于0,以此来确定给定的节点是否匹配指定的表达式(expr),也就是判断单个节点是否符合特定的选择器要求的一种方式 Sizzle.matchesSelector = function (node, expr) { return Sizzle(expr, null, null, [node]).length > 0; }; // 定义Sizzle对象的find方法,用于查找与给定表达式(expr)匹配的元素集合,在指定的上下文(context)中进行查找,同时考虑是否是XML文档环境(isXML) Sizzle.find = function (expr, context, isXML) { var set, i, len, match, type, left; // 如果没有传入表达式(expr),直接返回空数组,因为没有查找依据 if (!expr) { return []; } // 遍历Expr.order数组(Expr.order应该是在别处定义的,包含了各种查找类型的顺序相关信息,用于按顺序尝试不同的查找方式) for (i = 0, len = Expr.order.length; i < len; i++) { type = Expr.order[i]; // 使用对应类型的左匹配正则(Expr.leftMatch[type],不同的查找类型有不同的匹配正则表达式,用于提取表达式中的关键部分)来尝试匹配传入的表达式(expr),如果匹配成功 if ((match = Expr.leftMatch[type].exec(expr))) { // 获取匹配到的左边部分(可能是选择器的关键标识部分等) left = match[1]; // 移除匹配结果数组中的第二个元素(具体用途可能和不同查找类型的处理逻辑有关,这里不清楚具体原因,但从代码逻辑看是要去掉这个元素) match.splice(1, 1); // 如果左边部分的最后一个字符不是转义字符(\),说明是正常的匹配情况 if (left.substr(left.length - 1)!== "\\") { // 去除匹配结果数组中第二个元素(经过前面的splice操作后,这里的第二个元素对应的含义应该和具体查找类型相关,可能是要去除一些转义相关的干扰等)中的转义字符(通过替换为空字符串的方式) match[1] = (match[1] || "").replace(rBackslash, ""); // 调用对应查找类型的查找函数(Expr.find[type],不同类型有不同的查找逻辑实现,在别处定义),传入处理后的匹配结果、上下文以及是否是XML文档环境等参数,获取查找到的元素集合 set = Expr.find[type](match, context, isXML); // 如果查找到了元素集合(不为null) if (set!= null) { // 将表达式中已经匹配并处理过的部分替换为空字符串,也就是去掉已经查找过的部分,继续处理剩余的表达式部分 expr = expr.replace(Expr.match[type], ""); // 找到元素集合后就跳出循环,因为已经完成了本次查找任务 break; } } } } // 如果没有找到匹配的元素集合(set为null) if (!set) { // 判断上下文对象是否有getElementsByTagName方法(用于在非XML文档环境下获取所有标签名为*(也就是所有元素)的元素集合),如果有则调用该方法获取所有元素,否则返回空数组 set = typeof context.getElementsByTagName!== "undefined"? context.getElementsByTagName("*") : []; } // 返回一个包含找到的元素集合(set)以及剩余未处理的表达式(expr)的对象,方便后续可能的进一步处理(比如继续基于剩余表达式筛选元素等) return { set: set, expr: expr }; }; Sizzle.filter = function( expr, set, inplace, not ) { var match, anyFound, type, found, item, filter, left, i, pass, old = expr, result = [], curLoop = set, isXMLFilter = set && set[0] && Sizzle.isXML( set[0] ); while ( expr && set.length ) { for ( type in Expr.filter ) { if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) { filter = Expr.filter[ type ]; left = match[1]; anyFound = false; match.splice(1,1); if ( left.substr( left.length - 1 ) === "\\" ) { continue; } if ( curLoop === result ) { result = []; } if ( Expr.preFilter[ type ] ) { match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter ); if ( !match ) { anyFound = found = true; } else if ( match === true ) { continue; } } if ( match ) { for ( i = 0; (item = curLoop[i]) != null; i++ ) { if ( item ) { found = filter( item, match, i, curLoop ); pass = not ^ found; if ( inplace && found != null ) { if ( pass ) { anyFound = true; } else { curLoop[i] = false; } } else if ( pass ) { result.push( item ); anyFound = true; } } } } if ( found !== undefined ) { if ( !inplace ) { curLoop = result; } expr = expr.replace( Expr.match[ type ], "" ); if ( !anyFound ) { return []; } break; } } } // Improper expression if ( expr === old ) { if ( anyFound == null ) { Sizzle.error( expr ); } else { break; } } old = expr; } return curLoop; }; Sizzle.error = function( msg ) { throw new Error( "Syntax error, unrecognized expression: " + msg ); }; /** * Utility function for retreiving the text value of an array of DOM nodes * @param {Array|Element} elem */ var getText = Sizzle.getText = function( elem ) { var i, node, nodeType = elem.nodeType, ret = ""; if ( nodeType ) { if ( nodeType === 1 || nodeType === 9 || nodeType === 11 ) { // Use textContent || innerText for elements if ( typeof elem.textContent === 'string' ) { return elem.textContent; } else if ( typeof elem.innerText === 'string' ) { // Replace IE's carriage returns return elem.innerText.replace( rReturn, '' ); } else { // Traverse it's children for ( elem = elem.firstChild; elem; elem = elem.nextSibling) { ret += getText( elem ); } } } else if ( nodeType === 3 || nodeType === 4 ) { return elem.nodeValue; } } else { // If no nodeType, this is expected to be an array for ( i = 0; (node = elem[i]); i++ ) { // Do not traverse comment nodes if ( node.nodeType !== 8 ) { ret += getText( node ); } } } return ret; }; // 定义Expr对象,它包含选择器引擎的一些核心属性和方法,Sizzle.selectors将其引用为Expr var Expr = Sizzle.selectors = { // 定义选择器匹配的优先级顺序 order: [ "ID", "NAME", "TAG" ], // 定义一个对象,包含不同类型的选择器匹配正则表达式 match: { // ID选择器,匹配以#开头的字符串,后面跟随一个或多个字母、数字、连字符、Unicode字符或转义字符 ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, // 类选择器,匹配以.开头的字符串,后面跟随一个或多个字母、数字、连字符、Unicode字符或转义字符 CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, // 名称选择器,匹配[name='value']的形式,其中value可以是字母、数字、连字符、Unicode字符或转义字符 NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/, // 属性选择器,匹配[attr=value]的形式,支持多种属性值和复杂的表达式 ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(?:(['"])(.*?)\3|(#?(?:[\w\u00c0-\uFFFF\-]|\\.)*)|)|)\s*\]/, // 标签选择器,匹配一个或多个字母、数字、星号、连字符、Unicode字符或转义字符 TAG: /^((?:[\w\u00c0-\uFFFF*\-]|\\.)+)/, // 子选择器,匹配:first-child, :last-child, :nth-child(n), :only-child等 CHILD: /:(only|nth|last|first)-child(?:\(\s*(even|odd|(?:[+\-]?\d+|(?:[+\-]?\d*)?n\s*(?:[+\-]\s*\d+)?))\s*\))?/, // 位置选择器,匹配:nth, :eq, :gt, :lt, :first, :last, :even, :odd等 POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/, // 伪类选择器,匹配形如:hover, :focus等的伪类,支持带参数的伪类如:not(.class) PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/ }, // leftMatch对象,可能用于存储一些预处理或解析过程中使用的匹配信息 leftMatch: {}, // attrMap对象,定义了一些属性名的映射,用于处理某些HTML属性在DOM中的不同表示 attrMap: { "class": "className", // class属性在DOM中对应className "for": "htmlFor" // for属性在DOM中对应htmlFor }, // attrHandle对象,定义了一些特定属性的处理函数 attrHandle: { // href属性的处理函数,返回元素的href属性值 href: function( elem ) { return elem.getAttribute( "href" ); }, // type属性的处理函数,返回元素的type属性值 type: function( elem ) { return elem.getAttribute( "type" ); } }, // 定义一个对象,包含处理相对选择器的函数 relative: { // "+" 选择器的处理函数,用于查找当前元素之前的相邻兄弟元素 "+": function(checkSet, part){ // 判断part是否为字符串类型 var isPartStr = typeof part === "string", // 判断part是否为标签名(即不包含非单词字符) isTag = isPartStr && !rNonWord.test( part ), // 判断part是否为字符串但不是标签名(可能是一个类名、ID等) isPartStrNotTag = isPartStr && !isTag; // 如果part是标签名,则将其转换为小写 if ( isTag ) { part = part.toLowerCase(); } // 遍历checkSet中的每个元素 for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) { // 获取当前遍历到的元素 if ( (elem = checkSet[i]) ) { // 向上遍历前一个兄弟元素,直到找到一个元素节点(nodeType为1)或没有兄弟元素为止 while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {} // 根据条件更新checkSet[i]的值 // 如果part不是标签名,或者elem存在且其标签名与part(小写)相同,则设置为elem或false(如果elem不存在) // 如果part是具体的某个元素(通过===比较),则直接比较elem和part是否相同 checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ? elem || false : // 如果条件满足,设置为elem(如果存在)或false(如果不存在) elem === part; // 如果part是具体的元素,直接比较是否相等 } } // 如果part是字符串但不是标签名(可能是一个类名、ID等),则调用Sizzle的filter函数来处理 if ( isPartStrNotTag ) { Sizzle.filter( part, checkSet, true ); } // “>” 选择器的处理函数,用于查找当前元素的直接子元素 }, ">": function( checkSet, part ) { var elem, // 当前遍历到的元素 isPartStr = typeof part === "string", // 判断part是否为字符串类型 i = 0, // 循环计数器 l = checkSet.length; // checkSet的长度 // 如果part是字符串且不包含非单词字符(可能是标签名),则进行以下处理 if ( isPartStr && !rNonWord.test( part ) ) { part = part.toLowerCase(); // 将part转换为小写,以确保比较时不区分大小写 // 遍历checkSet中的每个元素 for ( ; i < l; i++ ) { elem = checkSet[i]; // 获取当前遍历到的元素 if ( elem ) { var parent = elem.parentNode; // 获取当前元素的父元素 // 如果当前元素的父元素的标签名与part(小写)相同,则将该父元素设置为checkSet[i]的值,否则设置为false checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false; } } // 注意:这里的代码片段没有包含rNonWord正则表达式的定义,也没有包含函数的结束括号和对象字面量的结束花括号。 } // 如果part不是简单的标签名(即包含非单词字符或未定义rNonWord导致的其他情况) else { // 遍历checkSet中的每个元素 for (; i < l; i++) { elem = checkSet[i]; // 获取当前遍历到的元素 if (elem) { // 根据part是否为字符串类型,更新checkSet[i]的值 // 如果是字符串,则保留elem的父元素;如果不是(可能是具体的DOM元素),则比较elem的父元素是否等于part checkSet[i] = isPartStr ? elem.parentNode : // 如果part是字符串,则设置为elem的父元素 elem.parentNode === part; // 如果part不是字符串(可能是DOM元素),则比较elem的父元素是否等于part } } // 如果part是字符串类型,则对checkSet进行进一步的筛选 // 这可能是为了处理类名、ID或其他属性选择器的情况 if (isPartStr) { Sizzle.filter(part, checkSet, true); // 调用Sizzle的filter函数进行筛选,true可能是表示某种特定行为的标志 } } }, // 注意:这里的代码片段是“>”选择器的处理函数的一部分,并且没有包含函数的结束括号和对象字面量的结束花括号。 // 此外,还假设了Sizzle.filter函数的存在和行为,以及isPartStr、checkSet、part、i和l等变量的上下文。 // 定义一个对象字面量,其中包含不同选择器的处理函数 // 空字符串选择器的处理函数 "": function(checkSet, part, isXML){ var nodeCheck, // 用于存储节点检查的变量(可能是标签名) doneName = done++, // 一个唯一标识符,可能用于跟踪检查过程的状态,`done`可能是一个外部变量,在此函数中自增 checkFn = dirCheck; // 初始检查函数,可能用于执行一般的DOM树遍历和检查 // 如果part是字符串且不包含非单词字符,则进行以下处理 if ( typeof part === "string" && !rNonWord.test( part ) ) { part = part.toLowerCase(); // 将part转换为小写 nodeCheck = part; // 将part设置为nodeCheck,用于后续的节点检查 checkFn = dirNodeCheck; // 将检查函数更改为dirNodeCheck,可能用于基于节点名的检查 } // 调用检查函数,传入相关参数以执行检查逻辑 // "parentNode"表示要检查的DOM关系(在这个上下文中,可能是用于遍历父元素) // part、doneName、checkSet、nodeCheck和isXML是其他必要的参数 checkFn( "parentNode", part, doneName, checkSet, nodeCheck, isXML ); }, // 波浪线选择器(~)的处理函数,用于选择同一父元素下的兄弟元素 "~": function( checkSet, part, isXML ) { var nodeCheck, // 同上,用于存储节点检查的变量 doneName = done++, // 同上,生成一个唯一标识符 checkFn = dirCheck; // 同上,初始检查函数 // 如果part是字符串且不包含非单词字符,则进行与空字符串选择器相同的处理 if ( typeof part === "string" && !rNonWord.test( part ) ) { part = part.toLowerCase(); // 转换为小写 nodeCheck = part; // 设置为nodeCheck checkFn = dirNodeCheck; // 更改检查函数为dirNodeCheck } // 调用检查函数,但这次传入"previousSibling"作为要检查的DOM关系 // 这表明我们是在查找同一父元素下的前一个兄弟元素(但实际上可能是通过逻辑来查找所有匹配的兄弟元素) // 注意:这里的实现细节可能有所不同,因为波浪线选择器通常需要检查所有兄弟元素,而不仅仅是前一个 checkFn( "previousSibling", part, doneName, checkSet, nodeCheck, isXML ); } }, // 注意:这里的代码片段没有包含`done`变量的定义、`dirCheck`和`dirNodeCheck`函数的定义,以及对象字面量的结束花括号。 // 这些都是在外部定义的,可能是选择器引擎的一部分。 find: { // ID选择器函数,用于根据元素的ID查找元素 ID: function( match, context, isXML ) { // 如果上下文对象(context)有getElementById方法,并且不是在XML文档中 if ( typeof context.getElementById !== "undefined" && !isXML ) { // 使用getElementById方法获取匹配的元素 var m = context.getElementById(match[1]); // 检查获取到的元素是否有父节点,以处理Blackberry 4.6返回的不在文档中的节点的问题 // #6963是一个可能是相关的bug编号 return m && m.parentNode ? [m] : []; } }, // NAME选择器函数,用于根据元素的name属性查找元素 NAME: function( match, context ) { // 如果上下文对象(context)有getElementsByName方法 if ( typeof context.getElementsByName !== "undefined" ) { // 初始化一个空数组,用于存储匹配的结果 var ret = [], // 使用getElementsByName方法获取所有name属性匹配的元素 results = context.getElementsByName(match[1]); // 遍历所有获取到的元素 for (var i = 0, l = results.length; i < l; i++) { // 如果元素的name属性确实匹配查找的值 if (results[i].getAttribute("name") === match[1]) { // 将该元素添加到结果数组中 ret.push(results[i]); } } // 如果没有找到任何匹配的元素,返回null;否则返回结果数组 return ret.length === 0 ? null : ret; } } }, TAG: function( match, context ) { // 如果上下文对象(context)有getElementsByTagName方法 if ( typeof context.getElementsByTagName !== "undefined" ) { // 使用getElementsByTagName方法查找所有匹配的标签名元素,并返回结果数组 return context.getElementsByTagName( match[1] ); } }, // 预处理过滤器对象,用于在正式查找前对选择器进行一些处理 preFilter: { // CLASS过滤器函数,用于处理类名选择器 CLASS: function( match, curLoop, inplace, result, not, isXML ) { // 对match[1](类名字符串)进行处理,去除可能的反斜杠(这里rBackslash应该是一个正则表达式) // 并在类名字符串的前后加上空格,这通常是为了处理类名选择器时的边界情况 match = " " + match[1].replace( rBackslash, "" ) + " "; // 如果是在XML文档中,则直接返回处理后的类名字符串 // 这里的处理可能是为了后续在XML环境中进行特定的匹配 if ( isXML ) { return match; } // 注意:此代码段在此处被截断,没有显示后续的处理逻辑 // 通常情况下,这里会有更多的代码来处理类名选择器,尤其是在非XML环境中 // 遍历curLoop数组中的每个元素,直到elem为null for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) { // 如果elem不是假值(即elem是一个有效的DOM元素) if ( elem ) { // 检查elem的className是否包含match指定的类名 // not是一个布尔值,用于指示是否进行反向匹配(即排除具有该类名的元素) // ^ 是位异或运算符,但在这里它可能是一个逻辑错误,因为通常我们会用!或==/!=来比较布尔值 // 正确的逻辑可能是使用!或==来根据not的值决定是包含还是排除类名 // elem.className && (" " + elem.className + " ").replace(/[\t\n\r]/g, " ").indexOf(match) >= 0 // 这段代码首先确保elem有className,然后将className前后加上空格,并替换掉所有的制表符、换行符和回车符,最后检查处理后的字符串中是否包含match指定的类名 if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n\r]/g, " ").indexOf(match) >= 0) ) { // 如果不在inplace模式下,并且元素匹配(或根据not的值应该被包含),则将元素添加到结果集中 if ( !inplace ) { result.push( elem ); } } else if ( inplace ) { // 如果在inplace模式下,并且元素不匹配(或根据not的值应该被排除),则将当前元素在curLoop中的位置设置为false // 这意味着在后续的迭代中,这个元素将被跳过 curLoop[i] = false; } } } // 函数返回false,这可能表示在当前的处理逻辑下,没有特殊的返回值需要传递给调用者 // 或者这可能是一个约定俗成的返回值,用于指示某种状态或条件 return false; }, // ID选择器处理函数 ID: function( match ) { // 从match数组中提取ID值,并去除可能的反斜杠字符,然后返回处理后的ID字符串 return match[1].replace( rBackslash, "" ); }, // TAG选择器处理函数 TAG: function( match, curLoop ) { // 从match数组中提取标签名,去除可能的反斜杠字符,并将其转换为小写,然后返回处理后的标签名字符串 // 注意:尽管这个函数接收了curLoop参数,但在当前代码段中并没有使用它 return match[1].replace( rBackslash, "" ).toLowerCase(); }, // CHILD选择器处理函数,用于处理如:nth-child之类的伪类选择器 CHILD: function( match ) { // 如果匹配的是nth类型的子选择器 if ( match[1] === "nth" ) { // 如果没有提供nth的具体参数(如2n+1),则抛出错误 if ( !match[2] ) { Sizzle.error( match[0] ); } // 去除nth参数前的加号或空格 match[2] = match[2].replace(/^\+|\s*/g, ''); // 使用正则表达式解析nth参数,支持'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6'等形式的表达式 var test = /(-?)(\d*)(?:n([+\-]?\d*))?/.exec( match[2] === "even" && "2n" || // 如果是'even',则转换为'2n' match[2] === "odd" && "2n+1" || // 如果是'odd',则转换为'2n+1' !/\D/.test( match[2] ) && "0n+" + match[2] || // 如果是一个纯数字,则前面加上'0n+' match[2] // 如果都不匹配,则直接使用原始match[2] ); // 计算nth参数中的系数和偏移量,包括处理负数的情况 // test[1]是符号(+或-),test[2]是系数(可能是空字符串,表示默认为1),test[3]是偏移量 match[2] = (test[1] + (test[2] || 1)) - 0; // 系数,确保是数字类型 match[3] = test[3] - 0; // 偏移量,确保是数字类型 } // 如果不是nth类型的子选择器,但提供了第二个参数(通常不应该),则抛出错误 else if ( match[2] ) { Sizzle.error( match[0] ); } // 注意:函数没有返回值,因为它可能是通过修改match数组来影响外部状态的 // TODO: 这是一个待办事项,表示需要将这部分代码移动到正常的缓存系统中去 // 这可能是指优化性能,通过缓存某些计算结果来避免重复计算 match[0] = done++; // 将match数组的第一个元素设置为一个递增的计数器值,可能用于标识处理进度或唯一性 return match; // 返回处理后的match数组 }, // ATTR选择器处理函数,用于处理属性选择器,如[attr=value] ATTR: function( match, curLoop, inplace, result, not, isXML ) { // 从match数组中提取属性名,并去除可能的反斜杠字符 var name = match[1] = match[1].replace( rBackslash, "" ); // 如果不是在XML上下文中,并且Expr.attrMap中存在该属性名的映射 // 则将属性名替换为映射后的名称(这通常用于处理HTML中的特殊属性) if ( !isXML && Expr.attrMap[name] ) { match[1] = Expr.attrMap[name]; } // 处理未使用引号括起来的值(如果有的话) // 这通常是为了确保属性值中的特殊字符被正确处理 match[4] = ( match[4] || match[5] || "" ).replace( rBackslash, "" ); // 如果使用的是~=运算符(表示“包含一个单词”的匹配) // 则在属性值的前后各添加一个空格,以确保单词边界的正确匹配 if ( match[2] === "~=" ) { match[4] = " " + match[4] + " "; } // 返回处理后的match数组 return match; }, // PSEUDO函数,用于处理伪类选择器 PSEUDO: function( match, curLoop, inplace, result, not ) { // 检查是否处理的是:not伪类 if ( match[1] === "not" ) { // 如果:not内部包含复杂的表达式或简单的单词表达式 // 使用chunker正则表达式来分割和解析表达式,或者检查表达式是否以单词字符开头 if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) { // 对:not内部的表达式进行完整的Sizzle查询 // 注意:这里假设chunker是一个能够解析复杂选择器的正则表达式 // null参数可能表示在当前的文档或上下文中进行查询 match[3] = Sizzle(match[3], null, null, curLoop); } else { // 如果:not内部是一个简单的选择器,则使用Sizzle.filter进行过滤 var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not); // 如果不是就地修改结果集 if ( !inplace ) { // 将过滤后的结果添加到最终的结果集中 result.push.apply( result, ret ); } // 返回false,表示这个分支已经处理了结果,不需要进一步处理 return false; } // 检查是否处理的是位置伪类(如:first-child)或其他Expr.match.POS定义的伪类 } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) { // 返回true,表示这个选择器需要特殊处理(可能是由其他部分的代码处理) return true; } // 如果没有特别处理,则返回原始的match数组 return match; }, // POS函数,可能用于处理位置伪类选择器(如:first-child, :last-child等) // 但这里的实现看起来有些简单,只是向match数组中添加了一个true值 POS: function( match ) { match.unshift(true); // 在match数组的开头添加一个true值,可能表示这个选择器需要特殊处理 return match; // 返回修改后的match数组 } }, // filters对象,包含一系列用于过滤DOM元素的函数 filters: { // enabled函数,用于检查元素是否启用(即不是disabled状态且type不是hidden) enabled: function( elem ) { return elem.disabled === false && elem.type !== "hidden"; // 如果元素没有被禁用且type属性不是"hidden",则返回true }, // disabled函数,用于检查元素是否被禁用 disabled: function( elem ) { return elem.disabled === true; // 如果元素的disabled属性为true,则返回true }, // checked函数,用于检查元素是否被选中(通常用于checkbox和radio按钮) checked: function( elem ) { return elem.checked === true; // 如果元素的checked属性为true,则返回true }, // selected函数,用于检查元素是否被选中(通常用于