jQ1.5源码注释以及解读RE
jQ作为javascript的库( ▼-▼ ), 尽善尽美, 代码优美, 值得学习。 这一周平常上班没啥事也看jQ1.5的代码, 今天周六差不多看完了(Sizzle部分还没看), 重新看了一下, 又有很多新东西;
相对与1.4版本的ajax部分, 整个进行了重写, 实在是坑爹, 现在还有很多没弄懂, ajax可以非常简单地:
var xhr = new XMLHttpReques || new window.ActiveXObject("Microsoft.XMLHTTP");
也可以同样可以写几千行, 所有的$.get(), $.post(), $.getJSON(), $.getScript().... 都是$.ajax的shortcut, 所有最后都是通过$.ajax 这个方法统一处理, 而且多了传送器这东西, 一下子觉得好酷炫, 而且查资料查到司徒大神2011就开始了解这些东西, 自己实在差太多了, 大家都要加油才行;
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
//匹配URL的那种空格; var r20 = /%20/g, rbracket = /\[\]$/, //回车加换行,或者单单回车(for mac); rCRLF = /\r?\n/g, //是否有# rhash = /#.*$/, rheaders = /^(.*?):\s*(.*?)\r?$/mg, // IE leaves an \r character at EOL rinput = /^(?:color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i, rnoContent = /^(?:GET|HEAD)$/, rprotocol = /^\/\//, rquery = /\?/, // "<div>11</div><script>console.log(1)</script><div>11</div>".match(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi); // <script>console.log(1)</script>会被匹配出来; rscript = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, rselectTextarea = /^(?:select|textarea)/i, rspacesAjax = /\s+/, rts = /([?&])_=[^&]*/, rurl = /^(\w+:)\/\/([^\/?#:]+)(?::(\d+))?/, // Keep a copy of the old load method _load = jQuery.fn.load, /* Prefilters * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example) * 2) These are called: * - BEFORE asking for a transport * - AFTER param serialization (s.data is a string if s.processData is true) * 3) key is the dataType * 4) the catchall symbol "*" can be used * 5) execution will start with transport dataType and THEN continue down to "*" if needed */ prefilters = {}, /* Transports bindings * 1) key is the dataType * 2) the catchall symbol "*" can be used * 3) selection will start with transport dataType and THEN go to "*" if needed */ transports = {}; // Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport // prefilters or transports; // "json jsonp" "script" "script" XML请求包装函数; function addToPrefiltersOrTransports( structure ) { // 又是一个闭包; // dataTypeExpression is optional and defaults to "*" return function( dataTypeExpression, func ) { //debugger; if ( typeof dataTypeExpression !== "string" ) { func = dataTypeExpression; dataTypeExpression = "*"; } if ( jQuery.isFunction( func ) ) { // rspacesAjax = /\s+/; var dataTypes = dataTypeExpression.toLowerCase().split( rspacesAjax ), i = 0, length = dataTypes.length, dataType, list, placeBefore; // For each dataType in the dataTypeExpression // json jsonp script 或者是 *; for(; i < length; i++ ) { dataType = dataTypes[ i ]; // We control if we're asked to add before // any existing element // 可能dataTypes是这样的 +json jsonp; 那么这个placeBefore就是ture, 这个回调会被放到了list最前排; placeBefore = /^\+/.test( dataType ); if ( placeBefore ) { dataType = dataType.substr( 1 ) || "*"; } list = structure[ dataType ] = structure[ dataType ] || []; // then we add to the structure accordingly // 保存回调方法; list[ placeBefore ? "unshift" : "push" ]( func ); } } }; } //Base inspection function for prefilters and transports function inspectPrefiltersOrTransports( structure, options, originalOptions, jXHR, dataType /* internal */, inspected /* internal */ ) { dataType = dataType || options.dataTypes[ 0 ]; inspected = inspected || {}; inspected[ dataType ] = true; var list = structure[ dataType ], i = 0, length = list ? list.length : 0, executeOnly = ( structure === prefilters ), selection; for(; i < length && ( executeOnly || !selection ); i++ ) { selection = list[ i ]( options, originalOptions, jXHR ); // If we got redirected to another dataType // we try there if not done already if ( typeof selection === "string" ) { if ( inspected[ selection ] ) { selection = undefined; } else { options.dataTypes.unshift( selection ); selection = inspectPrefiltersOrTransports( structure, options, originalOptions, jXHR, selection, inspected ); } } } // If we're only executing or nothing was selected // we try the catchall dataType if not done already if ( ( executeOnly || !selection ) && !inspected[ "*" ] ) { selection = inspectPrefiltersOrTransports( structure, options, originalOptions, jXHR, "*", inspected ); } // unnecessary when only executing (prefilters) // but it'll be ignored by the caller in that case return selection; } jQuery.fn.extend({ load: function( url, params, callback ) { if ( typeof url !== "string" && _load ) { return _load.apply( this, arguments ); // Don't do a request if no elements are being requested } else if ( !this.length ) { return this; } var off = url.indexOf( " " ); if ( off >= 0 ) { var selector = url.slice( off, url.length ); url = url.slice( 0, off ); } // Default to a GET request var type = "GET"; // If the second parameter was provided if ( params ) { // If it's a function if ( jQuery.isFunction( params ) ) { // We assume that it's the callback callback = params; params = null; // Otherwise, build a param string } else if ( typeof params === "object" ) { params = jQuery.param( params, jQuery.ajaxSettings.traditional ); type = "POST"; } } var self = this; // Request the remote document jQuery.ajax({ url: url, type: type, dataType: "html", data: params, // Complete callback (responseText is used internally) complete: function( jXHR, status, responseText ) { // Store the response as specified by the jXHR object responseText = jXHR.responseText; // If successful, inject the HTML into all the matched elements if ( jXHR.isResolved() ) { // #4825: Get the actual response in case // a dataFilter is present in ajaxSettings jXHR.done(function( r ) { responseText = r; }); // See if a selector was specified self.html( selector ? // Create a dummy div to hold the results jQuery("<div>") // inject the contents of the document in, removing the scripts // to avoid any 'Permission Denied' errors in IE .append(responseText.replace(rscript, "")) // Locate the specified elements .find(selector) : // If not, just inject the full result responseText ); } if ( callback ) { self.each( callback, [ responseText, status, jXHR ] ); } } }); return this; }, /* <form id="form" action="form"> <input type="text" name="ipt0" id="ipt0" value="111"/> <input type="text" name="ipt1" id="ipt1" value="222"/> </form> ==>> $("#form").serializeArray(); */ serialize: function() { return jQuery.param( this.serializeArray() ); }, serializeArray: function() { return this.map(function(){ return this.elements ? jQuery.makeArray( this.elements ) : this; }) .filter(function(){ return this.name && !this.disabled && ( this.checked || rselectTextarea.test( this.nodeName ) || rinput.test( this.type ) ); }) .map(function( i, elem ){ var val = jQuery( this ).val(); return val == null ? null : jQuery.isArray( val ) ? jQuery.map( val, function( val, i ){ return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; }) : { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; }).get(); } }); // Attach a bunch of functions for handling common AJAX events // 利用闭包减少代码量; jQuery.each( "ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split( " " ), function( i, o ){ jQuery.fn[ o ] = function( f ){ return this.bind( o, f ); }; } ); jQuery.each( [ "get", "post" ], function( i, method ) { jQuery[ method ] = function( url, data, callback, type ) { // shift arguments if data argument was omitted if ( jQuery.isFunction( data ) ) { type = type || callback; callback = data; data = null; } return jQuery.ajax({ type: method, url: url, data: data, success: callback, dataType: type }); }; } ); jQuery.extend({ getScript: function( url, callback ) { return jQuery.get( url, null, callback, "script" ); }, getJSON: function( url, data, callback ) { return jQuery.get( url, data, callback, "json" ); }, ajaxSetup: function( settings ) { /* setting : { jsonp : "callback", jsonpCallback : fn }, setting : { accepts : { script : "text/javascript, application/javascript" }, contents : { script : /javascript/ <<==是一个正则; }, converters : function( text ) { jQuery.globalEval( text ); return text; } } */ //debugger; jQuery.extend( true, jQuery.ajaxSettings, settings ); if ( settings.context ) { jQuery.ajaxSettings.context = settings.context; } }, ajaxSettings: { url: location.href, global: true, type: "GET", contentType: "application/x-www-form-urlencoded", processData: true, async: true, /* timeout: 0, data: null, dataType: null, username: null, password: null, cache: null, traditional: false, headers: {}, crossDomain: null, */ accepts: { xml: "application/xml, text/xml", html: "text/html", text: "text/plain", json: "application/json, text/javascript", "*": "*/*" }, contents: { xml: /xml/, html: /html/, json: /json/ }, responseFields: { xml: "responseXML", text: "responseText" }, // List of data converters // 1) key format is "source_type destination_type" (a single space in-between) // 2) the catchall symbol "*" can be used for source_type converters: { // Convert anything to text "* text": window.String, // Text to html (true = no transformation) "text html": true, // Evaluate text as a json expression "text json": jQuery.parseJSON, // Parse text as xml "text xml": jQuery.parseXML } }, //预传送器, 后面会初始化json和jsonp(包括回调的处理等), 标准xml的预传送器; ajaxPrefilter: addToPrefiltersOrTransports( prefilters ), //标准传送器, 包括ajax的传送器的执行操作, jsonp中需要添加script标签到界面的操作; ajaxTransport: addToPrefiltersOrTransports( transports ), // file///:C/本地协议的文件; // Main method /* cb = function(arg){console.log(arg)}; var url = "http://www.filltext.com/?callback=?"; $.getJSON( url, { 'callback' : "cb", 'rows': 5, 'fname': '{firstName}', 'lname': '{lastName}', 'tel': '{phone|format}', }); */ ajax: function( url, options ) { // If options is not an object, // we simulate pre-1.5 signature if ( typeof options !== "object" ) { options = url; url = undefined; } // Force options to be an object options = options || {}; var // Create the final options object s = jQuery.extend( true, {}, jQuery.ajaxSettings, options ), // Callbacks contexts // We force the original context if it exists // or take it from jQuery.ajaxSettings otherwise // (plain objects used as context get extended) callbackContext = ( s.context = ( "context" in options ? options : jQuery.ajaxSettings ).context ) || s, globalEventContext = callbackContext === s ? jQuery.event : jQuery( callbackContext ), // Deferreds deferred = jQuery.Deferred(), completeDeferred = jQuery._Deferred(), // Status-dependent callbacks statusCode = s.statusCode || {}, // Headers (they are sent all at once) requestHeaders = {}, // Response headers responseHeadersString, responseHeaders, // transport transport, // timeout handle timeoutTimer, // Cross-domain detection vars loc = document.location, protocol = loc.protocol || "http:", parts, // The jXHR state state = 0, // Loop variable i, //假的XMLHttpRequest, 因为xml的属性 不兼容, 为xhr包裹一层, 方便统一管理; // Fake xhr jXHR = { readyState: 0, // Caches the header setRequestHeader: function( name, value ) { if ( state === 0 ) { requestHeaders[ name.toLowerCase() ] = value; } return this; }, // Raw string getAllResponseHeaders: function() { return state === 2 ? responseHeadersString : null; }, // Builds headers hashtable if needed getResponseHeader: function( key ) { var match; if ( state === 2 ) { if ( !responseHeaders ) { responseHeaders = {}; while( ( match = rheaders.exec( responseHeadersString ) ) ) { responseHeaders[ match[1].toLowerCase() ] = match[ 2 ]; } } match = responseHeaders[ key.toLowerCase() ]; } return match || null; }, // Cancel the request abort: function( statusText ) { statusText = statusText || "abort"; if ( transport ) { transport.abort( statusText ); } done( 0, statusText ); return this; } }; // Callback for when everything is done // It is defined here because jslint complains if it is declared // at the end of the function (which would be more logical and readable) function done( status, statusText, responses, headers) { // Called once if ( state === 2 ) { return; } // State is "done" now state = 2; // Clear timeout if it exists if ( timeoutTimer ) { clearTimeout( timeoutTimer ); } // Dereference transport for early garbage collection // (no matter how long the jXHR object will be used) transport = undefined; // Cache response headers responseHeadersString = headers || ""; // Set readyState jXHR.readyState = status ? 4 : 0; var isSuccess, success, error, response = responses ? ajaxHandleResponses( s, jXHR, responses ) : undefined, lastModified, etag; // If successful, handle type chaining if ( status >= 200 && status < 300 || status === 304 ) { // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. if ( s.ifModified ) { if ( ( lastModified = jXHR.getResponseHeader( "Last-Modified" ) ) ) { jQuery.lastModified[ s.url ] = lastModified; } if ( ( etag = jXHR.getResponseHeader( "Etag" ) ) ) { jQuery.etag[ s.url ] = etag; }; }; // If not modified if ( status === 304 ) { statusText = "notmodified"; isSuccess = true; // If we have data } else { try { success = ajaxConvert( s, response ); statusText = "success"; isSuccess = true; } catch(e) { // We have a parsererror statusText = "parsererror"; error = e; }; }; } else { // We extract error from statusText // then normalize statusText and status for non-aborts error = statusText; if( status ) { statusText = "error"; if ( status < 0 ) { status = 0; } } }; // Set data for the fake xhr object jXHR.status = status; jXHR.statusText = statusText; // Success/Error if ( isSuccess ) { debugger; deferred.resolveWith( callbackContext, [ success, statusText, jXHR ] ); } else { deferred.rejectWith( callbackContext, [ jXHR, statusText, error ] ); } // Status-dependent callbacks jXHR.statusCode( statusCode ); statusCode = undefined; if ( s.global ) { globalEventContext.trigger( "ajax" + ( isSuccess ? "Success" : "Error" ), [ jXHR, s, isSuccess ? success : error ] ); } // Complete completeDeferred.resolveWith( callbackContext, [ jXHR, statusText ] ); if ( s.global ) { globalEventContext.trigger( "ajaxComplete", [ jXHR, s] ); // Handle the global AJAX counter if ( !( --jQuery.active ) ) { jQuery.event.trigger( "ajaxStop" ); } } }; //var def = {}; $.Deferred().promise(def) 把这个对象送到promise会为着对象继承(非真正意义上的继承,只是复制了属性而已)一个延迟对象的实例; // Attach deferreds deferred.promise( jXHR ); jXHR.success = jXHR.done; jXHR.error = jXHR.fail; jXHR.complete = completeDeferred.done; // Status-dependent callbacks jXHR.statusCode = function( map ) { if ( map ) { var tmp; if ( state < 2 ) { for( tmp in map ) { statusCode[ tmp ] = [ statusCode[tmp], map[tmp] ]; } } else { tmp = map[ jXHR.status ]; jXHR.then( tmp, tmp ); } } return this; }; //变量初始化完毕; // Remove hash character (#7531: and string promotion) // Add protocol if not provided (#5866: IE7 issue with protocol-less urls) // We also use the url parameter if available //去除空格; // rprotocol = /^\/\// 如果协议头是//就改成 ==》 当前协议头+// s.url = ( "" + ( url || s.url ) ).replace( rhash, "" ).replace( rprotocol, protocol + "//" ); // Extract dataTypes list // rspacesAjax = /\s+/; //把用户期望的返回类型保存起来,方便回调; s.dataTypes = jQuery.trim( s.dataType || "*" ).toLowerCase().split( rspacesAjax ); //判断是否跨域了哇; // Determine if a cross-domain request is in order if ( !s.crossDomain ) { parts = rurl.exec( s.url.toLowerCase() ); s.crossDomain = !!( parts && ( parts[ 1 ] != protocol || parts[ 2 ] != loc.hostname || ( parts[ 3 ] || ( parts[ 1 ] === "http:" ? 80 : 443 ) ) != ( loc.port || ( protocol === "http:" ? 80 : 443 ) ) ) ); }; // Convert data if not already a string if ( s.data && s.processData && typeof s.data !== "string" ) { //把json的数据转化成通过&拼接的数据; s.data = jQuery.param( s.data, s.traditional ); }; // Apply prefilters //prefilters = {JSON : fn, JSONP : fn, SCRIPT : fn}; //根据setting的dataType类型设置预处理的参数; inspectPrefiltersOrTransports( prefilters, s, options, jXHR ); // Uppercase the type s.type = s.type.toUpperCase(); // Determine if request has content // /^(?:GET|HEAD)$/ 没有发送数据就是没有content; s.hasContent = !rnoContent.test( s.type ); // Watch for a new set of requests //false // jQuery.active 目前等于 0; if ( s.global && jQuery.active++ === 0 ) { jQuery.event.trigger( "ajaxStart" ); }; // More options handling for requests with no content if ( !s.hasContent ) { // If data is available, append data to url if ( s.data ) { // s.url ==>> "http://www.filltext.com/?callback=jQuery15003316175821237266_1420601952773" // s.data ==>> "callback=cb&rows=5&fname=%7BfirstName%7D&lname=%7BlastName%7D&tel=%7Bphone%7Cformat%7D" s.url += ( rquery.test( s.url ) ? "&" : "?" ) + s.data; //jsonp会改变callback为jQ自己定义的callback然后在执行成功的时候才执行用户传进来的callback; //最后变成了 ==>> http://www.filltext.com/?callback=jQuery15003316175821237266_1420601952773&callback=cb&rows=5&fname=%7BfirstName%7D&lname=%7BlastName%7D&tel=%7Bphone%7Cformat%7D }; // Add anti-cache in url if needed if ( s.cache === false ) { var ts = jQuery.now(), // try replacing _= if it is there; // rts = /([?&])_=[^&]*/; 把 ?_=替换成?=times 或者是 #_=替换成#_=times, 而且后面不是&, 避免出现别的错误; /* "sdfsdfdsf?_=abcd".replace(rts, "$1_="+jQuery.now()) ==>> "sdfsdfdsf?_=1420614479567" "sdfsdfdsf#_=abcd".replace(rts, "$1_="+jQuery.now()) ==>> "sdfsdfdsf#_=abcd" */ ret = s.url.replace( rts, "$1_=" + ts ); // 给最后添加一个时间戳; // if nothing was replaced, add timestamp to the end s.url = ret + ( (ret === s.url ) ? ( rquery.test( s.url ) ? "&" : "?" ) + "_=" + ts : "" ); }; }; //JSON是不走这边的; // Set the correct header, if data is being sent if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) { requestHeaders[ "content-type" ] = s.contentType; } // // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. if ( s.ifModified ) { if ( jQuery.lastModified[ s.url ] ) { requestHeaders[ "if-modified-since" ] = jQuery.lastModified[ s.url ]; } if ( jQuery.etag[ s.url ] ) { requestHeaders[ "if-none-match" ] = jQuery.etag[ s.url ]; } } // 根据用户需求的请求头, 服务器可以返回期望的类型; // Set the Accepts header for the server, depending on the dataType requestHeaders.accept = s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[0] ] ? s.accepts[ s.dataTypes[0] ] + ( s.dataTypes[ 0 ] !== "*" ? ", */*; q=0.01" : "" ) : s.accepts[ "*" ]; // Check for headers option for ( i in s.headers ) { requestHeaders[ i.toLowerCase() ] = s.headers[ i ]; }; // 执行before的事件,这个是全局的; // Allow custom headers/mimetypes and early abort if ( s.beforeSend && ( s.beforeSend.call( callbackContext, jXHR, s ) === false || state === 2 ) ) { // Abort if not done already done( 0, "abort" ); // Return false jXHR = false; } else { // Install callbacks on deferreds for ( i in { success: 1, error: 1, complete: 1 } ) { //把用户的回调安装到延迟对象; //jXHR.success( s.success ); //jXHR.complete( s.complete ); //jXHR.error( s.error ); jXHR[ i ]( s[ i ] ); } // Get transport //获取传送器, 根据传送器的设置发送数据, 这个里面会执行get,post或者新增script的操作; transport = inspectPrefiltersOrTransports( transports, s, options, jXHR ); // If no transport, we auto-abort if ( !transport ) { done( -1, "No Transport" ); } else { // Set state as sending state = jXHR.readyState = 1; // Send global event // 触发全局的ajaxSend事件;参数为JXHR, s; if ( s.global ) { globalEventContext.trigger( "ajaxSend", [ jXHR, s ] ); } //开启一定定时器,如果是异步而且超时的话就触发超时的事件; // Timeout if ( s.async && s.timeout > 0 ) { timeoutTimer = setTimeout( function(){ jXHR.abort( "timeout" ); }, s.timeout ); } //JSONP的传送器有一个是send,一个是abort; try { transport.send( requestHeaders, done ); } catch (e) { //如果出了错; // Propagate exception as error if not done if ( status < 2 ) { done( -1, e ); // Simply rethrow otherwise } else { jQuery.error( e ); } } } } return jXHR; }, // Serialize an array of form elements or a set of // key/values into a query string param: function( a, traditional ) { var s = [], add = function( key, value ) { // If value is a function, invoke it and return its value value = jQuery.isFunction( value ) ? value() : value; s[ s.length ] = encodeURIComponent( key ) + "=" + encodeURIComponent( value ); }; // Set traditional to true for jQuery <= 1.3.2 behavior. if ( traditional === undefined ) { traditional = jQuery.ajaxSettings.traditional; } // If an array was passed in, assume that it is an array of form elements. if ( jQuery.isArray( a ) || a.jquery ) { // Serialize the form elements jQuery.each( a, function() { add( this.name, this.value ); } ); } else { // If traditional, encode the "old" way (the way 1.3.2 or older // did it), otherwise encode params recursively. for ( var prefix in a ) { buildParams( prefix, a[ prefix ], traditional, add ); } } // Return the resulting serialization return s.join( "&" ).replace( r20, "+" ); } }); function buildParams( prefix, obj, traditional, add ) { if ( jQuery.isArray( obj ) && obj.length ) { // Serialize array item. jQuery.each( obj, function( i, v ) { if ( traditional || rbracket.test( prefix ) ) { // Treat each array item as a scalar. add( prefix, v ); } else { // If array item is non-scalar (array or object), encode its // numeric index to resolve deserialization ambiguity issues. // Note that rack (as of 1.0.0) can't currently deserialize // nested arrays properly, and attempting to do so may cause // a server error. Possible fixes are to modify rack's // deserialization algorithm or to provide an option or flag // to force array serialization to be shallow. buildParams( prefix + "[" + ( typeof v === "object" || jQuery.isArray(v) ? i : "" ) + "]", v, traditional, add ); } }); } else if ( !traditional && obj != null && typeof obj === "object" ) { // If we see an array here, it is empty and should be treated as an empty // object if ( jQuery.isArray( obj ) || jQuery.isEmptyObject( obj ) ) { add( prefix, "" ); // Serialize object item. } else { jQuery.each( obj, function( k, v ) { buildParams( prefix + "[" + k + "]", v, traditional, add ); }); } } else { // Serialize scalar item. add( prefix, obj ); } } // This is still on the jQuery object... for now // Want to move this to jQuery.ajax some day jQuery.extend({ // Counter for holding the number of active queries active: 0, // Last-Modified header cache for next request lastModified: {}, etag: {} }); /* Handles responses to an ajax request: * - sets all responseXXX fields accordingly * - finds the right dataType (mediates between content-type and expected dataType) * - returns the corresponding response */ function ajaxHandleResponses( s, jXHR, responses ) { var contents = s.contents, dataTypes = s.dataTypes, responseFields = s.responseFields, ct, type, finalDataType, firstDataType; // Fill responseXXX fields for( type in responseFields ) { if ( type in responses ) { jXHR[ responseFields[type] ] = responses[ type ]; } } // Remove auto dataType and get content-type in the process while( dataTypes[ 0 ] === "*" ) { dataTypes.shift(); if ( ct === undefined ) { ct = jXHR.getResponseHeader( "content-type" ); } } // Check if we're dealing with a known content-type if ( ct ) { for ( type in contents ) { if ( contents[ type ] && contents[ type ].test( ct ) ) { dataTypes.unshift( type ); break; } } } // Check to see if we have a response for the expected dataType if ( dataTypes[ 0 ] in responses ) { finalDataType = dataTypes[ 0 ]; } else { // Try convertible dataTypes for ( type in responses ) { if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[0] ] ) { finalDataType = type; break; } if ( !firstDataType ) { firstDataType = type; } } // Or just use first one finalDataType = finalDataType || firstDataType; } // If we found a dataType // We add the dataType to the list if needed // and return the corresponding response if ( finalDataType ) { if ( finalDataType !== dataTypes[ 0 ] ) { dataTypes.unshift( finalDataType ); } return responses[ finalDataType ]; } } // Chain conversions given the request and the original response function ajaxConvert( s, response ) { // Apply the dataFilter if provided if ( s.dataFilter ) { response = s.dataFilter( response, s.dataType ); } var dataTypes = s.dataTypes, converters = s.converters, i, length = dataTypes.length, tmp, // Current and previous dataTypes current = dataTypes[ 0 ], prev, // Conversion expression conversion, // Conversion function conv, // Conversion functions (transitive conversion) conv1, conv2; // For each dataType in the chain for( i = 1; i < length; i++ ) { // Get the dataTypes prev = current; current = dataTypes[ i ]; // If current is auto dataType, update it to prev if( current === "*" ) { current = prev; // If no auto and dataTypes are actually different } else if ( prev !== "*" && prev !== current ) { // Get the converter conversion = prev + " " + current; conv = converters[ conversion ] || converters[ "* " + current ]; // If there is no direct converter, search transitively if ( !conv ) { conv2 = undefined; for( conv1 in converters ) { tmp = conv1.split( " " ); if ( tmp[ 0 ] === prev || tmp[ 0 ] === "*" ) { conv2 = converters[ tmp[1] + " " + current ]; if ( conv2 ) { conv1 = converters[ conv1 ]; if ( conv1 === true ) { conv = conv2; } else if ( conv2 === true ) { conv = conv1; } break; } } } } // If we found no converter, dispatch an error if ( !( conv || conv2 ) ) { jQuery.error( "No conversion from " + conversion.replace(" "," to ") ); } // If found converter is not an equivalence if ( conv !== true ) { // Convert with 1 or 2 converters accordingly response = conv ? conv( response ) : conv2( conv1(response) ); } } } return response; } var jsc = jQuery.now(), jsre = /(\=)\?(&|$)|()\?\?()/i; // Default jsonp settings jQuery.ajaxSetup({ jsonp: "callback", jsonpCallback: function() { return jQuery.expando + "_" + ( jsc++ ); } }); // Detect, normalize options and install callbacks for jsonp requests jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, dataIsString /* internal */ ) { dataIsString = ( typeof s.data === "string" ); if ( s.dataTypes[ 0 ] === "jsonp" || originalSettings.jsonpCallback || originalSettings.jsonp != null || s.jsonp !== false && ( jsre.test( s.url ) || dataIsString && jsre.test( s.data ) ) ) { var responseContainer, jsonpCallback = s.jsonpCallback = jQuery.isFunction( s.jsonpCallback ) ? s.jsonpCallback() : s.jsonpCallback, previous = window[ jsonpCallback ], url = s.url, data = s.data, replace = "$1" + jsonpCallback + "$2"; if ( s.jsonp !== false ) { url = url.replace( jsre, replace ); if ( s.url === url ) { if ( dataIsString ) { data = data.replace( jsre, replace ); } if ( s.data === data ) { // Add callback manually url += (/\?/.test( url ) ? "&" : "?") + s.jsonp + "=" + jsonpCallback; } } } s.url = url; s.data = data; window[ jsonpCallback ] = function( response ) { responseContainer = [ response ]; }; s.complete = [ function() { // Set callback back to previous value window[ jsonpCallback ] = previous; // Call if it was a function and we have a response if ( previous) { if ( responseContainer && jQuery.isFunction( previous ) ) { window[ jsonpCallback ] ( responseContainer[ 0 ] ); } } else { // else, more memory leak avoidance try{ delete window[ jsonpCallback ]; } catch( e ) {} } }, s.complete ]; // Use data converter to retrieve json after script execution s.converters["script json"] = function() { if ( ! responseContainer ) { jQuery.error( jsonpCallback + " was not called" ); } return responseContainer[ 0 ]; }; // force json dataType s.dataTypes[ 0 ] = "json"; // Delegate to script return "script"; } } ); // Install script dataType jQuery.ajaxSetup({ accepts: { script: "text/javascript, application/javascript" }, contents: { script: /javascript/ }, converters: { "text script": function( text ) { jQuery.globalEval( text ); return text; } } }); // Handle cache's special case and global jQuery.ajaxPrefilter( "script", function( s ) { if ( s.cache === undefined ) { s.cache = false; } if ( s.crossDomain ) { s.type = "GET"; s.global = false; } } ); // Bind script tag hack transport //这个是跨域的传送器哇; jQuery.ajaxTransport( "script", function(s) { // This transport only deals with cross domain requests // 如果请求的url发生改变,就默认用jsonp方式(script标签)进行加载; if ( s.crossDomain ) { var script, head = document.getElementsByTagName( "head" )[ 0 ] || document.documentElement; return { send: function( _, callback ) { script = document.createElement( "script" ); //async是标准浏览器所支持的东西; //defer是IE支持的异步加载方式; script.async = "async"; //字符串编码; if ( s.scriptCharset ) { script.charset = s.scriptCharset; } //script和link标签只有在添加到dom才发送请求哇; script.src = s.url; // Attach handlers for all browsers // 事件还是先加载 script.onload = script.onreadystatechange = function( _, isAbort ) { if ( !script.readyState || /loaded|complete/.test( script.readyState ) ) { // Handle memory leak in IE // IE的内存泄露问题; script.onload = script.onreadystatechange = null; // Remove the script if ( head && script.parentNode ) { head.removeChild( script ); } // Dereference the script script = undefined; //因为是JSONP的方式, 就直接返回200的状态和 success的姿态; // Callback if not abort if ( !isAbort ) { callback( 200, "success" ); } } }; // Use insertBefore instead of appendChild to circumvent an IE6 bug. // This arises when a base node is used (#2709 and #4378). head.insertBefore( script, head.firstChild ); }, abort: function() { if ( script ) { script.onload( 0, 1 ); } } }; } } ); var // Next active xhr id xhrId = jQuery.now(), // active xhrs xhrs = {}, // #5280: see below xhrUnloadAbortInstalled, // 用来临时用的; // XHR used to determine supports properties testXHR; // Create the request object // (This is still attached to ajaxSettings for backward compatibility) jQuery.ajaxSettings.xhr = window.ActiveXObject ? /* Microsoft failed to properly * implement the XMLHttpRequest in IE7 (can't request local files), * so we use the ActiveXObject when it is available * Additionally XMLHttpRequest can be disabled in IE7/IE8 so * we need a fallback. */ function() { if ( window.location.protocol !== "file:" ) { try { return new window.XMLHttpRequest(); } catch( xhrError ) {} } try { return new window.ActiveXObject("Microsoft.XMLHTTP"); } catch( activeError ) {} } : // For all other browsers, use the standard XMLHttpRequest object function() { return new window.XMLHttpRequest(); }; // Test if we can create an xhr object try { testXHR = jQuery.ajaxSettings.xhr(); } catch( xhrCreationException ) {}; //测试是否支持XHR这个东西; //Does this browser support XHR requests? jQuery.support.ajax = !!testXHR; /* 默认情况下,跨源请求不提供凭据(cookie、HTTP认证及客户端SSL证明等)。 通过将withCredentials属性设置为true,可以指定某个请求应该发送凭据。 如果服务器接收带凭据的请求,会用下面的HTTP头部来响应。 如果发送的是带凭据的请求,但服务器的相应中没有包含这个头部, 那么浏览器就不会把相应交给JavaScript(于是,responseText中将是空字符串,status的值为0, 而且会调用onerror()事件处理程序)。 另外,服务器还可以在Preflight响应中发送这个HTTP头部, 表示允许源发送带凭据的请求。 支持withCredentials属性的浏览器有Firefox 3.5+、Safari 4+和Chrome。IE10及更早版本都不支持。 */ // 是否支持跨域直接通过withCredentials进行判断; // Does this browser support crossDomain XHR requests jQuery.support.cors = testXHR && ( "withCredentials" in testXHR ); // No need for the temporary xhr anymore testXHR = undefined; // Create transport if the browser can provide an xhr if ( jQuery.support.ajax ) { //传进来的是setting; jQuery.ajaxTransport(function( s ) { // Cross domain only allowed if supported through XMLHttpRequest if ( !s.crossDomain || jQuery.support.cors ) { var callback; return { send: function( headers, complete ) { //避免错误的 // #5280: we need to abort on unload or IE will keep connections alive if ( !xhrUnloadAbortInstalled ) { xhrUnloadAbortInstalled = 1; jQuery(window).bind( "unload", function() { // Abort all pending requests jQuery.each( xhrs, function( _, xhr ) { if ( xhr.onreadystatechange ) { xhr.onreadystatechange( 1 ); } } ); } ); } // Get a new xhr var xhr = s.xhr(), handle; // Open the socket // Passing null username, generates a login popup on Opera (#2865) if ( s.username ) { xhr.open( s.type, s.url, s.async, s.username, s.password ); } else { xhr.open( s.type, s.url, s.async ); } // Requested-With header // Not set for crossDomain requests with no content // (see why at http://trac.dojotoolkit.org/ticket/9486) // Won't change header if already provided // 设置头; if ( !( s.crossDomain && !s.hasContent ) && !headers["x-requested-with"] ) { headers[ "x-requested-with" ] = "XMLHttpRequest"; } // Need an extra try/catch for cross domain requests in Firefox 3 try { jQuery.each( headers, function( key, value ) { xhr.setRequestHeader( key, value ); } ); } catch( _ ) {}; // Do send the request // This may raise an exception which is actually // handled in jQuery.ajax (so no try/catch here) // POST或者是GET,所以要判断一下; xhr.send( ( s.hasContent && s.data ) || null ); //cancel when I use arguments (0,1 ), kind like : callback(0,1); // Listener callback = function( _, isAbort ) { // Was never called and is aborted or complete if ( callback && ( isAbort || xhr.readyState === 4 ) ) { //执行成功了就不用了; // Only called once callback = 0; // Do not keep as active anymore if ( handle ) { xhr.onreadystatechange = jQuery.noop; delete xhrs[ handle ]; } // If it's an abort //cance和send放在一起, 一个接口, 多个使用; if ( isAbort ) { // Abort it manually if needed if ( xhr.readyState !== 4 ) { xhr.abort(); } } else { // Get info var status = xhr.status, statusText, responseHeaders = xhr.getAllResponseHeaders(), responses = {}, xml = xhr.responseXML; // Construct response list if ( xml && xml.documentElement /* #4958 */ ) { responses.xml = xml; } responses.text = xhr.responseText; // Firefox throws an exception(exception异常) when accessing // statusText for faulty cross-domain requests // 火狐会报异常在跨域请求的时候; try { statusText = xhr.statusText; } catch( e ) { // We normalize with Webkit giving an empty statusText statusText = ""; } // 修正各种浏览器的状态码; // Filter status for non standard behaviours status = // Opera returns 0 when it should be 304 // Webkit returns 0 for failing cross-domain no matter the real status status === 0 ? ( // Webkit, Firefox: filter out faulty cross-domain requests !s.crossDomain || statusText ? ( // Opera: filter out real aborts #6060 responseHeaders ? 304 : 0 ) : // We assume 302 but could be anything cross-domain related 302 ) : ( // IE sometimes returns 1223 when it should be 204 (see #1450) status == 1223 ? 204 : status ); // Call complete //有responseXML和 //responseText两种值; //返回的返回头; complete( status, statusText, responses, responseHeaders ); } } }; // if we're in sync mode or it's in cache // and has been retrieved directly (IE6 & IE7) // we need to manually fire the callback if ( !s.async || xhr.readyState === 4 ) { callback(); } else { // Add to list of active xhrs handle = xhrId++; xhrs[ handle ] = xhr; xhr.onreadystatechange = callback; } }, abort: function() { if ( callback ) { callback(0,1); } } }; } }); }
JS的动画处理也看了, 他默认把所有的动画放在一个queue里面, 根据先进先出的方式一次次执行, 而且传的参数也挺人性化不管你各个参数的排序怎么样, 最后都会自动修正, 正常的是这样的
$("#div1").animate({height:40},1000,"swing",function(){console.log(1)})
也对用户传进来的非px的单位, 比如em, %进行了参数转化, 通过实例化一个动画对象放到$.timers这个数组里面, 只要数组有东西, 就开着定时器一直跑, 你可以添加自己的动画方法, 因为本身jQ就提供了 "swing" 和 "linear"两种方式, $.easing;
easing: { linear: function( p, n, firstNum, diff ) { return firstNum + diff * p; }, swing: function( p, n, firstNum, diff ) { return ((-Math.cos(p*Math.PI)/2) + 0.5) * diff + firstNum; } }
一个是线性, 就是速度不变的情况下在固定时间到达终点, 一个是先慢后快再慢这种情况; 话说好像有科学证明所有人对渐渐消失和渐渐显示都有很好印象, 不会太唐突;
像speed这些, 默认的动画让他normal就是400毫秒自己跑, 快的就是200毫秒, 慢的就是800毫秒:
speeds: { slow: 600, fast: 200, // Default speed _default: 400 }
这些非常简单, 真正难得在代码里面:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
//保存默认的显示设置的变量; var elemdisplay = {}, // rfxtypes = /^(?:toggle|show|hide)$/, //开头是+-这样的符号 //包含数字.- //这个应该就是符号,符号可以是百分比; rfxnum = /^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i, timerId, fxAttrs = [ // height animations [ "height", "marginTop", "marginBottom", "paddingTop", "paddingBottom" ], // width animations [ "width", "marginLeft", "marginRight", "paddingLeft", "paddingRight" ], // opacity animations [ "opacity" ] ]; jQuery.fn.extend({ /* $("#div1").hide(); $("#div1").show(); $._data($("#div1")[0]) ==>> Object {olddisplay: "block"} 所谓的olddisplay永远只有一个值; */ show: function( speed, easing, callback ) { var elem, display; //有传深度进来就调用animate if ( speed || speed === 0 ) { return this.animate( genFx("show", 3), speed, easing, callback); } else { //直接通过display方式进行直接显示或者隐藏; for ( var i = 0, j = this.length; i < j; i++ ) { elem = this[i]; display = elem.style.display; // Reset the inline display of this element to learn if it is // being hidden by cascaded rules or not // 不可见变成可见,!jQuery._data(elem, "olddisplay")只有第一次才走这边; // 如果没有没有被$(el).hide()过就没有olddisplay的, 就直接让元素根据样式表的默认样式进行显示; if ( !jQuery._data(elem, "olddisplay") && display === "none" ) { display = elem.style.display = ""; }; // Set elements which have been overridden with display: none // in a stylesheet to whatever the default browser style is // for such an element // 如果元素设置成display=""以后, 而且默认样式还是none, 就获取默认样式保存到私有数据缓存系统中; if ( display === "" && jQuery.css( elem, "display" ) === "none" ) { jQuery._data(elem, "olddisplay", defaultDisplay(elem.nodeName)); }; }; // Set the display of most of the elements in a second loop // to avoid the constant reflow // 这个可以直接放在上面的循环, 不过为了避免常量重渲染, 才把这个放在第二个循环里面 for ( i = 0; i < j; i++ ) { elem = this[i]; display = elem.style.display; //是空或者是none就给他展示默认的样式; if ( display === "" || display === "none" ) { elem.style.display = jQuery._data(elem, "olddisplay") || ""; } } return this; } }, hide: function( speed, easing, callback ) { if ( speed || speed === 0 ) { return this.animate( genFx("hide", 3), speed, easing, callback); } else { for ( var i = 0, j = this.length; i < j; i++ ) { var display = jQuery.css( this[i], "display" ); //如果这个元素是隐藏状态的话, 而且没有保存原来的显示值, 会把这个元素最初的显示值保存起来; if ( display !== "none" && !jQuery._data( this[i], "olddisplay" ) ) { jQuery._data( this[i], "olddisplay", display ); } } // Set the display of the elements in a second loop // to avoid the constant reflow // 隐藏起来; for ( i = 0; i < j; i++ ) { this[i].style.display = "none"; }; return this; } }, // Save the old toggle function _toggle: jQuery.fn.toggle, toggle: function( fn, fn2, callback ) { var bool = typeof fn === "boolean"; //卧槽, 这个是跑到click那个toggle去了; if ( jQuery.isFunction(fn) && jQuery.isFunction(fn2) ) { this._toggle.apply( this, arguments ); //fn === null || fn === undefined, 不传参数的走这个else; } else if ( fn == null || bool ) { this.each(function() { //有就根据不二值展示, 没有根据当前进行显示和隐藏; var state = bool ? fn : jQuery(this).is(":hidden"); jQuery(this)[ state ? "show" : "hide" ](); }); } else { //别的参数调用动画去了; this.animate(genFx("toggle", 3), fn, fn2, callback); }; return this; }, fadeTo: function( speed, to, easing, callback ) { return this.filter(":hidden").css("opacity", 0).show().end() .animate({opacity: to}, speed, easing, callback); }, /* $("#div1").animate({height:40},1000,"swing",function(){console.log(1)}) $("#div1").animate({height:400},1000,"swing",function(){console.log(1)}) */ animate: function( prop, speed, easing, callback ) { /* 参数被处理后会是这个样子的, 因为本身就是回调是函数形式, 动画形式是字符串形式, 经过时间是数字形式, 所以处理还是挺简单的; complete: function () { if ( opt.queue !== false ) { jQuery(this).dequeue(); } if ( jQuery.isFunction( opt.old ) ) { opt.old.call( this ); } }, duration: 1000, easing: "swing", old: function (){console.log(1)} */ var optall = jQuery.speed(speed, easing, callback); if ( jQuery.isEmptyObject( prop ) ) { return this.each( optall.complete ); }; //默认的queue是undefined, 这个是动画大体上形式; return this[ optall.queue === false ? "each" : "queue" ](function() { // XXX 'this' does not always have a nodeName when running the // test suite //重新复制了一份opt了; var opt = jQuery.extend({}, optall), p, isElement = this.nodeType === 1, hidden = isElement && jQuery(this).is(":hidden"), self = this; for ( p in prop ) { //要对属性转驼峰; var name = jQuery.camelCase( p ); if ( p !== name ) { prop[ name ] = prop[ p ]; delete prop[ p ]; p = name; }; // if ( prop[p] === "hide" && hidden || prop[p] === "show" && !hidden ) { return opt.complete.call(this); } /* jQ1.4就只有这两句; if ( ( p === "height" || p === "width" ) && this.style ) { //保存原来的display属性和overflow属性; // Store display property opt.display = jQuery.css(this, "display"); // Make sure that nothing sneaks out opt.overflow = this.style.overflow; }; */ if ( isElement && ( p === "height" || p === "width" ) ) { //sneaks out 渐隐;偷偷溜走; // Make sure that nothing sneaks out // Record all 3 overflow attributes because IE does not // change the overflow attribute when overflowX and // overflowY are set to the same value // 记录这个属性因为IE改变overflow是不改变overflowX和overflowY为相同的值; opt.overflow = [ this.style.overflow, this.style.overflowX, this.style.overflowY ]; // Set display property to inline-block for height/width // animations on inline elements that are having width/height // animated // 要改变width或者height的话必须要是块元素, 所以我们让他变成块元素 //如果支持inline-block,就用inlineblock //否知使用inline并设置zoom为1, 触发元素的hasLayout; if ( jQuery.css( this, "display" ) === "inline" && jQuery.css( this, "float" ) === "none" ) { if ( !jQuery.support.inlineBlockNeedsLayout ) { this.style.display = "inline-block"; } else { var display = defaultDisplay(this.nodeName); // inline-level elements accept inline-block; // block-level elements need to be inline with layout if ( display === "inline" ) { this.style.display = "inline-block"; } else { this.style.display = "inline"; this.style.zoom = 1; } } } } //$("#div1").animate({height:[1000, "swing"]},1000,function(){console.log(1)}) //$("#div1").animate({height:[40, "swing"]},1000,function(){console.log(1)}) if ( jQuery.isArray( prop[p] ) ) { // Create (if needed) and add to specialEasing (opt.specialEasing = opt.specialEasing || {})[p] = prop[p][1]; prop[p] = prop[p][0]; } }; //触发layout? if ( opt.overflow != null ) { this.style.overflow = "hidden"; } //需要改变的值保存到curAnim里面去; opt.curAnim = jQuery.extend({}, prop); //根据需要改变属性的对象迭代 jQuery.each( prop, function( name, val ) { /* self : 当前元素; opt : { complete : fn, curAnim : { height : "400px" }, duration : 1000, easing : "swing", old : fn, overflow : ["","",""] } */ var e = new jQuery.fx( self, opt, name ); //如果是toggle或者说是hide或者是show的话; if ( rfxtypes.test(val) ) { e[ val === "toggle" ? hidden ? "show" : "hide" : val ]( prop ); } else { var parts = rfxnum.exec(val), start = e.cur() || 0; //处理参数, 最后把实际的参数放到动画实例; if ( parts ) { var end = parseFloat( parts[2] ), unit = parts[3] || "px"; // We need to compute starting value // 单位可能是%, em 等不常用的单位; if ( unit !== "px" ) { //设置为需要的单位的最终值; jQuery.style( self, name, (end || 1) + unit); /*计算 //e.cur()而是根据用户的unit的最终值; end result -------- = -------- e.cur() start result = end/e.cur()*start; */ start = ((end || 1) / e.cur()) * start; //还原单位的初始值; jQuery.style( self, name, start + unit); }; // If a +=/-= token was provided, we're doing a relative animation if ( parts[1] ) { end = ((parts[1] === "-=" ? -1 : 1) * end) + start; }; //直接放到fx的对列, 让他跑就好了; e.custom( start, end, unit ); } else { e.custom( start, val, "" ); } } }); // For JS strict compliance return true; }); }, stop: function( clearQueue, gotoEnd ) { var timers = jQuery.timers; //如果有clearQueue, 把queue队列清空 if ( clearQueue ) { this.queue([]); }; //把jQ下的timers时间列表给删除 this.each(function() { // go in reverse order so anything added to the queue during the loop is ignored for ( var i = timers.length - 1; i >= 0; i-- ) { if ( timers[i].elem === this ) { if (gotoEnd) { // force the next step to be the last timers[i](true); }; timers.splice(i, 1); }; }; }); // start the next in the queue if the last step wasn't forced if ( !gotoEnd ) { this.dequeue(); } return this; } }); /* JSON.stringify(genFx("show", 1),null,4) "{ "height": "show", "marginTop": "show", "marginBottom": "show", "paddingTop": "show", "paddingBottom": "show" }"; JSON.stringify(genFx("show", 3),null,4) "{ "height": "show", "marginTop": "show", "marginBottom": "show", "paddingTop": "show", "paddingBottom": "show", "width": "show", "marginLeft": "show", "marginRight": "show", "paddingLeft": "show", "paddingRight": "show", "opacity": "show" }" */ function genFx( type, num ) { var obj = {}; /* fxAttrs = [ // height animations [ "height", "marginTop", "marginBottom", "paddingTop", "paddingBottom" ], // width animations [ "width", "marginLeft", "marginRight", "paddingLeft", "paddingRight" ], // opacity animations [ "opacity" ] ] */ jQuery.each( fxAttrs.concat.apply([], fxAttrs.slice(0,num)), function() { obj[ this ] = type; }); return obj; } // Generate shortcuts for custom animations /* 比如你要让一个元素slideDown 那么这个元素的height,margintop marginbottom paddingtop padding-bottom都要一个个变小, 为这个元素的这几个属性添加变小的定时器; */ jQuery.each({ slideDown: genFx("show", 1), slideUp: genFx("hide", 1), slideToggle: genFx("toggle", 1), fadeIn: { opacity: "show" }, fadeOut: { opacity: "hide" }, fadeToggle: { opacity: "toggle" } }, function( name, props ) { jQuery.fn[ name ] = function( speed, easing, callback ) { return this.animate( props, speed, easing, callback ); }; }); //初始化变量, 用户可以传 number(动画时间), string(动画形式), function(动画完成的回调) jQuery.extend({ speed: function( speed, easing, fn ) { var opt = speed && typeof speed === "object" ? jQuery.extend({}, speed) : { complete: fn || !fn && easing || jQuery.isFunction( speed ) && speed, duration: speed, easing: fn && easing || easing && !jQuery.isFunction(easing) && easing }; opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration : opt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[opt.duration] : jQuery.fx.speeds._default; // Queueing opt.old = opt.complete; opt.complete = function() { if ( opt.queue !== false ) { jQuery(this).dequeue(); } if ( jQuery.isFunction( opt.old ) ) { opt.old.call( this ); } }; return opt; }, easing: { linear: function( p, n, firstNum, diff ) { return firstNum + diff * p; }, swing: function( p, n, firstNum, diff ) { return ((-Math.cos(p*Math.PI)/2) + 0.5) * diff + firstNum; } }, timers: [], //fx动画的函数, 很重要, 所有的动画开始结束都是基于这个函数的; fx: function( elem, options, prop ) { this.options = options; this.elem = elem; this.prop = prop; if ( !options.orig ) { options.orig = {}; } } }); jQuery.fx.prototype = { // Simple function for setting a style value // 更新元素的fx.prop为fx.now; update: function() { if ( this.options.step ) { this.options.step.call( this.elem, this.now, this ); } //opacity和默认动画 , 你也可以把为这个spep设置width或者高的运动step (jQuery.fx.step[this.prop] || jQuery.fx.step._default)( this ); }, // Get the current size // 通过$.css获取当前样式; cur: function() { if ( this.elem[this.prop] != null && (!this.elem.style || this.elem.style[this.prop] == null) ) { return this.elem[ this.prop ]; } var r = parseFloat( jQuery.css( this.elem, this.prop ) ); return r || 0; }, // Start an animation from one number to another custom: function( from, to, unit ) { var self = this, fx = jQuery.fx; this.startTime = jQuery.now(); this.start = from; this.end = to; this.unit = unit || this.unit || "px"; this.now = this.start; this.pos = this.state = 0; function t( gotoEnd ) { return self.step(gotoEnd); } t.elem = this.elem; //只要t返回ture那么times就会push这个step, !timerId时候才添加, 只要一个线程就好了, 提高性能; if ( t() && jQuery.timers.push(t) && !timerId ) { //timerId和fx是在同一个作用域的; timerId = setInterval(fx.tick, fx.interval); } }, // Simple 'show' function show: function() { // Remember where we started, so that we can go back to it later this.options.orig[this.prop] = jQuery.style( this.elem, this.prop ); this.options.show = true; // Begin the animation // Make sure that we start at a small width/height to avoid any // flash of content this.custom(this.prop === "width" || this.prop === "height" ? 1 : 0, this.cur()); // Start by showing the element jQuery( this.elem ).show(); }, // Simple 'hide' function hide: function() { // Remember where we started, so that we can go back to it later this.options.orig[this.prop] = jQuery.style( this.elem, this.prop ); this.options.hide = true; // Begin the animation this.custom(this.cur(), 0); }, // Each step of an animation //如果动画完成了就返回ture, 否知返回false step: function( gotoEnd ) { //假设当前的这个属性的动画完成了 var t = jQuery.now(), done = true; //jQ的动画是根据时间进行的, 所以这边要判断一下时间是否超过预期的时间; if ( gotoEnd || t >= this.options.duration + this.startTime ) { this.now = this.end; this.pos = this.state = 1; this.update(); this.options.curAnim[ this.prop ] = true; //this.options保存了原来的opt参数, 只要有一个属性动画没完成,动画就是未完成; for ( var i in this.options.curAnim ) { if ( this.options.curAnim[i] !== true ) { done = false; }; }; // 如果动画完成了就把这个元素原来的属性赋值到元素; if ( done ) { // Reset the overflow if ( this.options.overflow != null && !jQuery.support.shrinkWrapBlocks ) { var elem = this.elem, options = this.options; jQuery.each( [ "", "X", "Y" ], function (index, value) { elem.style[ "overflow" + value ] = options.overflow[index]; } ); }; // Hide the element if the "hide" operation was done if ( this.options.hide ) { jQuery(this.elem).hide(); }; // 把结果值再赋值一遍; // Reset the properties, if the item has been hidden or shown if ( this.options.hide || this.options.show ) { for ( var p in this.options.curAnim ) { jQuery.style( this.elem, p, this.options.orig[p] ); }; }; // done的话就走complete; // Execute the complete function this.options.complete.call( this.elem ); }; // return false; } else { // 根据经过的时间表示进度; var n = t - this.startTime; this.state = n / this.options.duration; // // Perform the easing function, defaults to swing var specialEasing = this.options.specialEasing && this.options.specialEasing[this.prop]; var defaultEasing = this.options.easing || (jQuery.easing.swing ? "swing" : "linear"); //进度 //经过的时间 //总的时间; this.pos = jQuery.easing[specialEasing || defaultEasing](this.state, n, 0, 1, this.options.duration); //计算结果的值; this.now = this.start + ((this.end - this.start) * this.pos); // debugger; // Perform the next step of the animation // 把值更新到元素上; this.update(); } return true; } }; jQuery.extend( jQuery.fx, { //这个tick是一个定时器, 只要有要运动元素, 这个定时器都在跑 tick: function() { var timers = jQuery.timers; for ( var i = 0; i < timers.length; i++ ) { //timers保存的是每一个元素改变样式的step闭包, 如果元素的执行完成就会返回false, 那么这个改变的线程就会被删除; if ( !timers[i]() ) { timers.splice(i--, 1); }; }; //如果没有了就会清空timers, fx.stopstop就在这个方法的后面; if ( !timers.length ) { jQuery.fx.stop(); }; }, interval: 13, stop: function() { clearInterval( timerId ); timerId = null; }, speeds: { slow: 600, fast: 200, // Default speed _default: 400 }, //fx.step 这个可以添加; step: { opacity: function( fx ) { jQuery.style( fx.elem, "opacity", fx.now ); }, _default: function( fx ) { if ( fx.elem.style && fx.elem.style[ fx.prop ] != null ) { fx.elem.style[ fx.prop ] = (fx.prop === "width" || fx.prop === "height" ? Math.max(0, fx.now) : fx.now) + fx.unit; } else { fx.elem[ fx.prop ] = fx.now; } } } }); if ( jQuery.expr && jQuery.expr.filters ) { jQuery.expr.filters.animated = function( elem ) { return jQuery.grep(jQuery.timers, function( fn ) { return elem === fn.elem; }).length; }; } function defaultDisplay( nodeName ) { //缓冲elemdisplay, 优化,避免下一次再跑同样的标签; if ( !elemdisplay[ nodeName ] ) { var elem = jQuery("<" + nodeName + ">").appendTo("body"), display = elem.css("display"); elem.remove(); if ( display === "none" || display === "" ) { display = "block"; }; elemdisplay[ nodeName ] = display; }; return elemdisplay[ nodeName ]; };
获取元素的宽高在跨浏览器的时候实在是悲剧, 因为早期DOM并不是规范, 浏览器都是按照自己的来, 导致获取client, scoll, offset, left, top height, width有各种各样的问题, 现在标准早已建立, 比如最简单的盒模型等等, 根据这套标准, 获取元素各个属性有迹可循,如果对DOM的各个宽高值不清楚的看这个会比较麻烦:
![](https://images.cnblogs.com/OutliningIndicators/ContractedBlock.gif)
//匹配标签是table或者是td或者是th这种标签; var rtable = /^t(?:able|d|h)$/i, //匹配是body或者是html标签; rroot = /^(?:body|html)$/i; //jQ真的很细心, 工作和生活其实都要这样才能有成就啊; //getBoundingClientRect虽然是ie的东西,但是在任何浏览器都是全兼容的, 所以可以放心使用, 通过这个东东计算宽高很快的; if ( "getBoundingClientRect" in document.documentElement ) { //返回相对于body的top和left; jQuery.fn.offset = function( options ) { // var elem = this[0], box; //如果有传参数, 就是设置值; if ( options ) { return this.each(function( i ) { jQuery.offset.setOffset( this, options, i ); }); }; //不存在elem还玩个毛; if ( !elem || !elem.ownerDocument ) { return null; } //如果是body进行特殊判断; if ( elem === elem.ownerDocument.body ) { //是body的话直接返回body的left和top,默认的的8pxmargin return jQuery.offset.bodyOffset( elem ); }; //获取这个元素相对于这个界面的left和top; try { box = elem.getBoundingClientRect(); } catch(e) {}; var doc = elem.ownerDocument, docElem = doc.documentElement; // Make sure we're not dealing with a disconnected DOM node // 如果box这个没返回, 或者box的ownerDocument不包含这个box[in the fragmentDocument : document.createDocumentFragment()] if ( !box || !jQuery.contains( docElem, elem ) ) { //如果这个元素不在dom里面也有top和left吗? return box ? { top: box.top, left: box.left } : { top: 0, left: 0 }; }; // var body = doc.body, win = getWindow(doc), //IE的documentElement是不显示的, 只有body; clientTop = docElem.clientTop || body.clientTop || 0, clientLeft = docElem.clientLeft || body.clientLeft || 0, scrollTop = (win.pageYOffset || jQuery.support.boxModel && docElem.scrollTop || body.scrollTop ), scrollLeft = (win.pageXOffset || jQuery.support.boxModel && docElem.scrollLeft || body.scrollLeft), //去除border的高度, 因为滚动条包含border的; top = box.top + scrollTop - clientTop, left = box.left + scrollLeft - clientLeft; return { top: top, left: left }; }; } else { //没有getBoundingClientRect jQuery.fn.offset = function( options ) { var elem = this[0]; if ( options ) { return this.each(function( i ) { jQuery.offset.setOffset( this, options, i ); }); } if ( !elem || !elem.ownerDocument ) { return null; } if ( elem === elem.ownerDocument.body ) { return jQuery.offset.bodyOffset( elem ); } //获得关于offset相关的浏览器特征 jQuery.offset.initialize(); var computedStyle, offsetParent = elem.offsetParent, prevOffsetParent = elem, doc = elem.ownerDocument, docElem = doc.documentElement, body = doc.body, defaultView = doc.defaultView, prevComputedStyle = defaultView ? defaultView.getComputedStyle( elem, null ) : elem.currentStyle, //从当前元素的offset开始(再重复一次, offset是从border开始的(不包含border,即margin-box); top = elem.offsetTop, left = elem.offsetLeft; //不用getBoundingClientRect获取offset很麻烦; //迭代父级, 而不是迭代offsetParent哦, 因为parentNode如果有scroll的话计算出来的offsetLeft或者offsetTop不准确), 直到body或者html标签; //迭代每一个父级,对于每一个父级的scroll和border进行特殊处理, while ( (elem = elem.parentNode) && elem !== body && elem !== docElem ) { //fix的标签是根据界面定位的, 一定要例外处理, 否则计算出的值有误;; if ( jQuery.offset.supportsFixedPosition && prevComputedStyle.position === "fixed" ) { break; }; computedStyle = defaultView ? defaultView.getComputedStyle(elem, null) : elem.currentStyle; //因为我们要计算的是相对界面的top和left, 所以要把对所有有滚动的父级进行处理(减去处理); top -= elem.scrollTop; left -= elem.scrollLeft; //对有定位的offsetParent才处理,(要弄懂offsetParent的元素是有定位的元素,比如absolute或者是relative的元素); if ( elem === offsetParent ) { top += elem.offsetTop; left += elem.offsetLeft; //offset应该返回的是border-box,但在一些表格元素却没有计算它们的border值,需要自行添加 //offset是指从当前的margin-box(包含margin)到父级的border-box(包含border-box),有些浏览器的offset不包含border, 要注意, 所以也要特殊处理; //又对table进行特殊处理 if ( jQuery.offset.doesNotAddBorder && !(jQuery.offset.doesAddBorderForTableAndCells && rtable.test(elem.nodeName)) ) { top += parseFloat( computedStyle.borderTopWidth ) || 0; left += parseFloat( computedStyle.borderLeftWidth ) || 0; }; prevOffsetParent = offsetParent; //把offsetParent给offsetParent offsetParent = elem.offsetParent; } //修正safari的错误 if ( jQuery.offset.subtractsBorderForOverflowNotVisible && computedStyle.overflow !== "visible" ) { top += parseFloat( computedStyle.borderTopWidth ) || 0; left += parseFloat( computedStyle.borderLeftWidth ) || 0; }; prevComputedStyle = computedStyle; } //最后加上body的偏移 if ( prevComputedStyle.position === "relative" || prevComputedStyle.position === "static" ) { top += body.offsetTop; left += body.offsetLeft; } //如果元素使用了fix定位, 要加上最大滚动距离; if ( jQuery.offset.supportsFixedPosition && prevComputedStyle.position === "fixed" ) { top += Math.max( docElem.scrollTop, body.scrollTop ); left += Math.max( docElem.scrollLeft, body.scrollLeft ); } return { top: top, left: left }; }; } jQuery.offset = { //一些兼容检测, 坑多, 太多了; initialize: function() { var body = document.body, container = document.createElement("div"), innerDiv, checkDiv, table, td, bodyMarginTop = parseFloat( jQuery.css(body, "marginTop") ) || 0, html = "<div style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;'><div></div></div><table style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;' cellpadding='0' cellspacing='0'><tr><td></td></tr></table>"; jQuery.extend( container.style, { position: "absolute", top: 0, left: 0, margin: 0, border: 0, width: "1px", height: "1px", visibility: "hidden" } ); container.innerHTML = html; body.insertBefore( container, body.firstChild ); innerDiv = container.firstChild; checkDiv = innerDiv.firstChild; td = innerDiv.nextSibling.firstChild.firstChild; this.doesNotAddBorder = (checkDiv.offsetTop !== 5); this.doesAddBorderForTableAndCells = (td.offsetTop === 5); checkDiv.style.position = "fixed"; checkDiv.style.top = "20px"; // safari subtracts parent border width here which is 5px this.supportsFixedPosition = (checkDiv.offsetTop === 20 || checkDiv.offsetTop === 15); checkDiv.style.position = checkDiv.style.top = ""; innerDiv.style.overflow = "hidden"; innerDiv.style.position = "relative"; this.subtractsBorderForOverflowNotVisible = (checkDiv.offsetTop === -5); this.doesNotIncludeMarginInBodyOffset = (body.offsetTop !== bodyMarginTop); body.removeChild( container ); body = container = innerDiv = checkDiv = table = td = null; jQuery.offset.initialize = jQuery.noop; }, bodyOffset: function( body ) { var top = body.offsetTop, left = body.offsetLeft; //兼容检测; jQuery.offset.initialize(); //offsetLeft offsetTop默认就是不包含当前元素的margin, 可能向前的浏览器不给力; if ( jQuery.offset.doesNotIncludeMarginInBodyOffset ) { top += parseFloat( jQuery.css(body, "marginTop") ) || 0; left += parseFloat( jQuery.css(body, "marginLeft") ) || 0; } return { top: top, left: left }; }, //这个还是jQ的工具方法; setOffset: function( elem, options, i ) { var position = jQuery.css( elem, "position" ); //如果元素没有指定position的值, 那么默认的返回为static; // set position first, in-case top/left are set even on static elem if ( position === "static" ) { elem.style.position = "relative"; }; //获取默认值; var curElem = jQuery( elem ), curOffset = curElem.offset(), curCSSTop = jQuery.css( elem, "top" ), curCSSLeft = jQuery.css( elem, "left" ), //如果有个定位的值是auto; calculatePosition = (position === "absolute" && jQuery.inArray('auto', [curCSSTop, curCSSLeft]) > -1), props = {}, curPosition = {}, curTop, curLeft; // 需要计算值, 这个值是通过有定位的父级的position计算出相对的值; // need to be able to calculate position if either top or left is auto and position is absolute if ( calculatePosition ) { curPosition = curElem.position(); }; // 通过计算获取的值优先用, 否者用计算后样式; curTop = calculatePosition ? curPosition.top : parseInt( curCSSTop, 10 ) || 0; curLeft = calculatePosition ? curPosition.left : parseInt( curCSSLeft, 10 ) || 0; // 判断是不是函数 if ( jQuery.isFunction( options ) ) { options = options.call( elem, i, curOffset ); }; // if (options.top != null) { // 这里是通过$.css设置定位, 所以有个恒等式 : 设置后的相对界面的位置 - 设置前元素相对界面的位置 = 设置后元素相对父级的位置 - 设置前元素相对父级的位置 // 我们要求的是设置后相对父级的位置, 把这个东东倒一下就好了; //我们要设置的offset, 相对界面的位置 //用户设置的值 //相对界面的定位 //相对父级的值; props.top = (options.top - curOffset.top) + curTop; } if (options.left != null) { props.left = (options.left - curOffset.left) + curLeft; } //可以传一个using的方法, 会把元素和 最后的样式作为参数传进去; if ( "using" in options ) { options.using.call( elem, props ); } else { curElem.css( props ); } } }; jQuery.fn.extend({ // position是获取相对有定位父级的位置; position: function() { if ( !this[0] ) { return null; } var elem = this[0], // Get *real* offsetParent offsetParent = this.offsetParent(), // Get correct offsets offset = this.offset(), //如果是html或者body就把left和top设置为0, 不要忘记了元素的定位是包含margin的,这个很重要,而且子元素的定位是根据父级的padding-box进行设置的; //元素的offset是从border-box开始的 parentOffset = rroot.test(offsetParent[0].nodeName) ? { top: 0, left: 0 } : offsetParent.offset(); // Subtract element margins // note: when an element has margin: auto the offsetLeft and marginLeft // are the same in Safari causing offset.left to incorrectly be 0 offset.top -= parseFloat( jQuery.css(elem, "marginTop") ) || 0; offset.left -= parseFloat( jQuery.css(elem, "marginLeft") ) || 0; // Add offsetParent borders parentOffset.top += parseFloat( jQuery.css(offsetParent[0], "borderTopWidth") ) || 0; parentOffset.left += parseFloat( jQuery.css(offsetParent[0], "borderLeftWidth") ) || 0; // Subtract the two offsets return { //元素的offset是从border-box开始的 //position是指这个元素的margin定点到padding-box(包含padding)的距离, 所以要计算该元素和父级元素的offset, 将这两个元素的offset相减, 当前的值是包含了 当前元素的margin的且 不包含父级元素border的,所以要重新计算 //这里最好画图,要么头会晕, 而且个个属性的定义要弄清楚; top: offset.top - parentOffset.top, left: offset.left - parentOffset.left }; }, offsetParent: function() { return this.map(function() { var offsetParent = this.offsetParent || document.body; //如果元素的定位为static而且不是根元素, 重新定义offsetParent, 应该是某些浏览器会返回position为static的offstParent; while ( offsetParent && (!rroot.test(offsetParent.nodeName) && jQuery.css(offsetParent, "position") === "static") ) { offsetParent = offsetParent.offsetParent; }; return offsetParent; }); } }); // Create scrollLeft and scrollTop methods jQuery.each( ["Left", "Top"], function( i, name ) { var method = "scroll" + name; //减少代码而设置的闭包; //scrollLeft; //scrollTop jQuery.fn[ method ] = function(val) { var elem = this[0], win; //参数检测; if ( !elem ) { return null; } //设置值; if ( val !== undefined ) { // Set the scroll offset return this.each(function() { win = getWindow( this ); //如果是window就是设置滚动的值; if ( win ) { win.scrollTo( !i ? val : jQuery(win).scrollLeft(), i ? val : jQuery(win).scrollTop() ); } else { //this.scrollTop, this.scrollLeft; this[ method ] = val; } }); } else { // 获取 win = getWindow( elem ); // Return the scroll offset //浏览器的能力检测; return win ? ("pageXOffset" in win) ? win[ i ? "pageYOffset" : "pageXOffset" ] : jQuery.support.boxModel && win.document.documentElement[ method ] || win.document.body[ method ] : //直接返回 elem[ method ]; } }; }); //你不能传一个nodeType为1的元素进来; function getWindow( elem ) { return jQuery.isWindow( elem ) ? elem : //documentNodeType ==》》 9 elem.nodeType === 9 ? elem.defaultView || elem.parentWindow : false; } // Create innerHeight, innerWidth, outerHeight and outerWidth methods jQuery.each([ "Height", "Width" ], function( i, name ) { //又是一个闭包; var type = name.toLowerCase(); // innerHeight and innerWidth jQuery.fn["inner" + name] = function() { //innerWidth是包含padding的, 引用css的方法, 传的是padding return this[0] ? parseFloat( jQuery.css( this[0], type, "padding" ) ) : null; }; // outerHeight and outerWidth // 获取的是borderBox的宽或者高度,可以传true获取的是包含margin的宽和高; jQuery.fn["outer" + name] = function( margin ) { return this[0] ? parseFloat( jQuery.css( this[0], type, margin ? "margin" : "border" ) ) : null; }; //type为 width或者是height; jQuery.fn[ type ] = function( size ) { // Get window width or height var elem = this[0]; //避免错误; if ( !elem ) { return size == null ? null : this; } //对函数进行处理; if ( jQuery.isFunction( size ) ) { return this.each(function( i ) { var self = jQuery( this ); //引用自身, 把计算后值传到回调方法里; self[ type ]( size.call( this, i, self[ type ]() ) ); }); }; // 是window的话 if ( jQuery.isWindow( elem ) ) { // Everyone else use document.documentElement or document.body depending on Quirks vs Standards mode // 3rd condition allows Nokia support, as it supports the docElem prop but not CSS1Compat //标准浏览器的用户宽和高是就是html标签的宽高; var docElemProp = elem.document.documentElement[ "client" + name ]; //标准浏览器会走第一个 return elem.document.compatMode === "CSS1Compat" && docElemProp || //IE的诡异模式会走这一个 //这个就不知道什么情况了; elem.document.body[ "client" + name ] || docElemProp; // Get document width or height } else if ( elem.nodeType === 9 ) { // Either scroll[Width/Height] or offset[Width/Height], whichever is greater //计算documentElemnt的最大值; return Math.max( elem.documentElement["client" + name], elem.body["scroll" + name], elem.documentElement["scroll" + name], elem.body["offset" + name], elem.documentElement["offset" + name] ); // Get or set width or height on the element //这个是获取; } else if ( size === undefined ) { var orig = jQuery.css( elem, type ), ret = parseFloat( orig ); //parseFlaot是NaN应该是auto这种情况; return jQuery.isNaN( ret ) ? orig : ret; // Set the width or height on the element (default to pixels if value is unitless) //最后走设置 } else { //width或者是高; return this.css( type, typeof size === "string" ? size : size + "px" ); } }; }); })(window);
最后把所有代码贴出来, 过会儿看angular去, 下次就是看jQ1.6了;
/*! * jQuery JavaScript Library v1.5 * 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: Mon Jan 31 08:31:29 2011 -0500 */ //本来想折腾karma和yeoman, 折腾不起来, 浪费了一大堆时间, 不玩他们了, 复习下jQuery才是正道啊; (function( window, undefined ) { // Use the correct document accordingly with window argument (sandbox) // 提高document速度; var document = window.document; 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.$, //通过jQ实例化后的根节点(document), 里面用的比较多, 直接缓存起来; // A central reference to the root jQuery(document) rootjQuery, // A simple way to check for HTML strings or ID strings // (both of which we optimize for) //(<[\w\W]+>)匹配< ***** > 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+$/, // Check for digits //只要匹配单数字就可以了; rdigit = /\d/, // Match a standalone tag // 只要匹配<div sdfsd></div> //或者 //<div sdfsdf> 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.]+))?/, // Keep a UserAgent string for use with jQuery.browser userAgent = navigator.userAgent, // For matching the engine and version of the browser browserMatch, // Has the ready events already been bound? readyBound = false, //这个就是一个数组, 保存了DOM ready的列表; // The deferred used on DOM ready readyList, // Promise methods promiseMethods = "then done fail isResolved isRejected promise".split( " " ), //DOM准备(展示)好了就会触发这个 // 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, //{ [object Object] : "object" , [object Array] : "array" }象这样的东东; // [[Class]] -> type pairs class2type = {}; jQuery.fn = jQuery.prototype = { //不改就是object的constructor了; constructor: jQuery, //处理了几种情况 init: function( selector, context, rootjQuery ) { var match, elem, ret, doc; // Handle $(""), $(null), or $(undefined) //没有的话返回空的实例,继承了jq的原型的空对象; if ( !selector ) { return this; } //单独处理传单个元素进来的情况; // Handle $(DOMElement) if ( selector.nodeType ) { this.context = this[0] = selector; this.length = 1; return this; } //处理了body, 优化, 1.4是没有这个的; // The body element only exists once, optimize finding it if ( selector === "body" && !context && document.body ) { this.context = document; this[0] = document.body; this.selector = "body"; this.length = 1; return this; } // Handle HTML strings // 是字符串 或者是 一个函数 if ( typeof selector === "string" ) { // Are we dealing with HTML string or an ID? match = quickExpr.exec( selector ); //$("<div></div>") ==>> ["<div></div>", "<div></div>", undefined]; //$("#div1") ==>> ["#div1", undefined, "div1"]; // 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; //通过context获取当前界面的文档 或者document; //有可能是iframe里面的元素; 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 ) { //$("<div></div>",{hehe:"hehe"}); if ( jQuery.isPlainObject( context ) ) { selector = [ document.createElement( ret[1] ) ]; jQuery.fn.attr.call( selector, context, true ); } else { //$("<div>") selector = [ doc.createElement( ret[1] ) ]; } } else { //通过buildFragement处理传进来的字符串; //比如 : $("<div dfdf></div><div>sdfsdf</div>"); //会变成: [<div dfdf></div>, <div>sdfsdf</div>]; 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 // ie下name和id混肴的情况通过sizzle匹配元素 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 { //通过sizzle //this.constructor()就是 $() return this.constructor( context ).find( selector ); } // HANDLE: $(function) // Shortcut for document ready } else if ( jQuery.isFunction( selector ) ) { //$(document).ready(fn(){}) return rootjQuery.ready( selector ); } if (selector.selector !== undefined) { this.selector = selector.selector; this.context = selector.context; } //继承jQ return jQuery.makeArray( selector, this ); }, // Start with an empty selector selector: "", // The current version of jQuery being used jquery: "1.5", // 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; }, //这个就是 jQ.fn //就是 jQ.prototype 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) // $("html").pushStack($("body")).prevObject pushStack: function( elems, name, selector ) { // Build a new jQuery matched element set //一个空的jQ对象; var ret = this.constructor(); //如果是数组就把树枝放到ret里去; if ( jQuery.isArray( elems ) ) { push.apply( ret, elems ); } else { //否者就直接放到ret; jQuery.merge( ret, elems ); } // Add the old object onto the stack (as a reference) ret.prevObject = this; ret.context = this.context; //保存selector; 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 ) { //如果有传args的话callback的参数为value, key; return jQuery.each( this, callback, args ); }, // jQuery.ready(function() {}) ready: function( fn ) { // Attach the listeners //没事,这个就会绑定一次,第二次会跳出来; jQuery.bindReady(); // Add the callback readyList.done( fn ); //链式调用 return this; }, eq: function( 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 }; // 所有在fn上的 // 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; }; //要继承的目标不是对象, 或者不是function // Handle case when target is a string or something (possible in deep copy) if ( typeof target !== "object" && !jQuery.isFunction(target) ) { target = {}; }; //$.extend({xx : yy}); 就是 length === 1; // 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 // null undefined继承个毛线; if ( (options = arguments[ i ]) != null ) { // Extend the base object /* //这个是target === copy的情况, 避免循环引用; var bar = {copy : 3 }; $.extend(bar, { wawa : 2 ,lele : 1 , foo : bar}); */ for ( name in options ) { src = target[ name ]; copy = options[ name ]; // Prevent never-ending loop if ( target === copy ) { continue; } //不是深度继承,主要copy不是undefined直接覆盖掉 // Recurse if we're merging plain objects or arrays if ( deep && copy && ( jQuery.isPlainObject(copy) || (copyIsArray = jQuery.isArray(copy)) ) ) { //如果targer下不存在这个 数组 或者 对象 属性,就新建一个同名属性; if ( copyIsArray ) { copyIsArray = false; clone = src && jQuery.isArray(src) ? src : []; } else { clone = src && jQuery.isPlainObject(src) ? src : {}; } //再继承;O 了 // 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; }; //bind工具方法和 DOMready; jQuery.extend({ noConflict: function( deep ) { //某些版本会这样处理 /* if(window.$ == $) { window.$ = _$; } */ window.$ = _$; if ( deep ) { window.jQuery = _jQuery; } return jQuery; }, /* //查看jQ的6781bugs时候找到的东东,贴出来, IE ,onpropertchange属性真是碉堡了。。; <!DOCTYPE html> <html> <head> <title>Resource Loading event/property test</title> <script> "use strict"; //Set up the (expando) property if (!("resourceLoading" in document.documentElement)) { document.documentElement.resourceLoading = 0; } function modify(value) { //modify the value document.documentElement.resourceLoading = value; //Notify listeners. //No attachEvent branch since in IE modifying the expando triggers //onpropertychange if (document.addEventListener) { var evt = document.createEvent("UIEvents"); evt.initEvent("documentElementResourceLoading", false, false); document.dispatchEvent(evt); } } function listen(callback) { if (document.addEventListener) { document.addEventListener("documentElementResourceLoading", function (evt) { callback(document.documentElement.resourceLoading); }, false); } else if (document.attachEvent) { document.documentElement.attachEvent("onpropertychange", function (evt) { if (evt.propertyName === "resourceLoading") { callback(document.documentElement.resourceLoading); } }); } } function increment() { modify(document.documentElement.resourceLoading + 1); } function decrement() { modify(document.documentElement.resourceLoading - 1); } function msg(message) { document.getElementById("output").innerHTML += '<br>' + message; } increment(); increment(); listen(function (value) { msg('resourceLoading is now: ' + value); }); </script> </head> <body> <h1>Resource Loading event/property test</h1> <button onclick="increment()">Increment</button> <button onclick="decrement()">Decrement</button> <button onclick="msg('resourceLoading current value: ' + document.documentElement.resourceLoading)">Current Value</button> <button onclick="msg('Is resourceLoading in documentElement: ' + ('resourceLoading' in document.documentElement))">resourceLoading in document.documentElement</button> <div id="output"></div> </body> </html> */ // 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, // Handle when the DOM is ready // 用户$.ready(true)这样执行也ok,手动触发事件; ready: function( wait ) { // A third-party is pushing the ready event forwards if ( wait === true ) { jQuery.readyWait--; } // Make sure that the DOM is not already loaded //默认的readyWait被-1了,执行的话就为真了 //DOM的isReady是假,就会走; if ( !jQuery.readyWait || (wait !== true && !jQuery.isReady) ) { // Make sure body exists, at least, in case IE gets a little overzealous (ticket #5443). // body都不存在还玩个毛.... if ( !document.body ) { return setTimeout( jQuery.ready, 1 ); } // Remember that the DOM is ready jQuery.isReady = true; //需要支援了, wa了个去; // If a normal DOM Ready event fired, decrement, and wait if need be if ( wait !== true && --jQuery.readyWait > 0 ) { return; }; //reasyList是该版本jQ的延迟对象; // If there are functions bound, to execute readyList.resolveWith( document, [ jQuery ] ); // Trigger any bound ready events if ( jQuery.fn.trigger ) { //解绑定ready事件; jQuery( document ).trigger( "ready" ).unbind( "ready" ); }; } }, bindReady: function() { //readyBound是闭包内部的全局 if ( readyBound ) { return; } readyBound = true; // Catch cases where $(document).ready() is called after the // browser event has already occurred. // 因为jQ有可能是在DOM已经加载完成的的状态下加载的; if ( document.readyState === "complete" ) { // Handle it asynchronously to allow scripts the opportunity to delay ready // 虽然没有绑定用户事件, 但是jQ内部会添加DOMReady用来检测DOM加载完毕的一些兼容问题; return setTimeout( jQuery.ready, 1 ); } //DOM3的加载完毕事件; // Mozilla, Opera and webkit nightlies currently support this event if ( document.addEventListener ) { // Use the handy event callback document.addEventListener( "DOMContentLoaded", DOMContentLoaded, false ); // 也有可能onload比DOMContentLoaded先加载完毕 // A fallback to window.onload, that will always work window.addEventListener( "load", jQuery.ready, false ); // If IE event model is used // 回退到IE的事件绑定; } 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 ); // 如果当前的界面不是通过iframe套到别的界面的话; // 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). // IE8及下 typeof window.alert 返回的值为 "object" // 扩展IE8以及前版本 的的DOM是组件,ActiveX与com组件 : 参考http://baike.baidu.com/link?url=_S3UOypMzmx855aEEmzYqlC7iaHzWSxZE4si844SqWdr1glw2VgRkNBDb949loODUc5OEyZkRGXowtnztL5wWK isFunction: function( obj ) { return jQuery.type(obj) === "function"; }, isArray: Array.isArray || function( obj ) { return jQuery.type(obj) === "array"; }, // A crude way of determining if an object is a window isWindow: function( obj ) { return obj && typeof obj === "object" && "setInterval" in obj; }, isNaN: function( obj ) { return obj == null || !rdigit.test( obj ) || isNaN( 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; } // 如果是纯净的obj,那么constructor肯定是Object; // Not own constructor property must be Object if ( obj.constructor && !hasOwn.call(obj, "constructor") && !hasOwn.call(obj.constructor.prototype, "isPrototypeOf") ) { return false; } // Own properties are enumerated firstly, so to speed up, // if last one is own, then all properties are own. //继承的key值会在最后遍历到, 所以只要检测最后一个就好了; 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; }, //直接 throw msg 不就好了 error: function( msg ) { throw 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 ); // Make sure the incoming data is actual JSON // Logic borrowed from http://json.org/json2.js // 替换一些非法字符, 这些字符会邮箱到JSON的parse; // /^[\],:{}\s]*$/ // /\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g if ( rvalidchars.test(data.replace(rvalidescape, "@") .replace(rvalidtokens, "]") .replace(rvalidbraces, "")) ) { // Try to use the native JSON parser first return window.JSON && window.JSON.parse ? window.JSON.parse( data ) : //匿名 自动执行 , 这里可用eval; (new Function("return " + data))(); } else { jQuery.error( "Invalid JSON: " + data ); }; //扩展 (new Function("wa","return {hehe:wa}"))(1); }, //IE通过Microsoft.XMLDOM方式parseXML; // Cross-browser xml parsing // (xml & tmp used internally) parseXML: function( data , xml , tmp ) { 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 ); }; tmp = xml.documentElement; if ( ! tmp || ! tmp.nodeName || tmp.nodeName === "parsererror" ) { jQuery.error( "Invalid XML: " + data ); } return xml; }, noop: function() {}, // Evalulates a script in a global context globalEval: function( data ) { //rnotwhite = /\S/ 非空字符就好了; if ( data && rnotwhite.test(data) ) { // Inspired by code by Andrea Giammarchi // http://webreflection.blogspot.com/2007/08/global-scope-evaluation-and-dom.html var head = document.getElementsByTagName("head")[0] || document.documentElement, script = document.createElement("script"); script.type = "text/javascript"; //标准的通过新建createTextNode if ( jQuery.support.scriptEval() ) { script.appendChild( document.createTextNode( data ) ); } else { //IE的用text属性; script.text = data; } // Use insertBefore instead of appendChild to circumvent an IE6 bug. // This arises when a base node is used (#2709). head.insertBefore( script, head.firstChild ); head.removeChild( script ); } }, nodeName: function( elem, name ) { //实例方法的东东是引用工具方法的 return elem.nodeName && elem.nodeName.toUpperCase() === name.toUpperCase(); }, // args is for internal usage only // $.each({1:2},function(a ,args){console.log(this);console.log(arguments)},["a","b","c","d"]) each: function( object, callback, args ) { var name, i = 0, length = object.length, isObj = length === undefined || jQuery.isFunction(object); //如果有args存在的情况下, 内部用的吧,平常我们用不多; 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 ) { //key value if ( callback.call( object[ name ], name, object[ name ] ) === false ) { break; } } } else { for ( var value = object[0]; //index obj; i < length && callback.call( value, i, value ) !== false; value = object[++i] ) {} } } 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("ab") ["ab"] $.makeArray(1) [1] $.makeArray( ) [] $.makeArray($("body"), [1,2,3,4]); [1, 2, 3, 4, "<body>…</body>"] makeArray是把前面的往后面放; */ makeArray: function( array, results ) { var ret = results || []; if ( array != null ) { // The window, strings (and functions) also have 'length' // The extra typeof function check is to prevent crashes // in Safari 2 (See: #3039) // 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可以检测NodeList或者是伪数组; inArray: function( elem, array ) { if ( array.indexOf ) { return array.indexOf( elem ); } for ( var i = 0, length = array.length; i < length; i++ ) { if ( array[ i ] === elem ) { return i; } } return -1; }, //merge可以merge伪数组; //$.merge($("body"),$("html")); 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就是new Array().filter, 当然, 前提是如果有的话; 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; }, // 就是new Array().map, 当然, 前提是如果有的话; // arg is for internal usage only map: function( elems, callback, arg ) { var ret = [], value; // Go through the array, translating each of the items to their // new value (or values). for ( var i = 0, length = elems.length; i < length; i++ ) { value = callback( elems[ i ], i, arg ); if ( value != null ) { ret[ ret.length ] = value; } } // Flatten any nested arrays return ret.concat.apply( [], ret ); }, //小东西的大用处; // A global GUID counter for objects guid: 1, /* 你可以用proxy把两个函数设置成统一的guid, 这个存在的意义是事件里面会用到fn.guid,代理以后的proxy的guid不能变的; $.proxy(fn0 = function() {}, fn1 = function(){}) fn0.guid ==>> 1 fn1.guid ==>> 1 */ proxy: function( fn, proxy, thisObject ) { if ( arguments.length === 2 ) { //这个.... if ( typeof proxy === "string" ) { thisObject = fn; fn = thisObject[ proxy ]; proxy = undefined; //如果proxy不是function //$.proxy(function(){console.log(this)}, document)() //就是说thisObject是上下文; } else if ( proxy && !jQuery.isFunction( proxy ) ) { thisObject = proxy; proxy = undefined; } }; if ( !proxy && fn ) { //这个就改了上下文,没有使用柯里化的方式; proxy = function() { return fn.apply( thisObject || this, arguments ); }; } //为了统一guid // Set the guid of unique handler to the same of original handler, so it can be removed if ( fn ) { //如果fn有guid就设置proxy.guid和proxy的guid相等; //有proxy就把proxy的guid和fn的guid相等; //都没有就设置一个就好了; proxy.guid = fn.guid = fn.guid || proxy.guid || jQuery.guid++; }; // So proxy can be declared as an argument return proxy; }, // Mutifunctional method to get and set values to a collection // The value/s can be optionally by executed if its a function //为了减少代码量, 弄了个这个东东; access: function( elems, key, value, exec, fn, pass ) { var length = elems.length; // Setting many attributes //对对对对,这个就是设置了 if ( typeof key === "object" ) { for ( var k in key ) { jQuery.access( elems, k, key[k], exec, fn, value ); } return elems; } // Setting one attribute if ( value !== undefined ) { // Optionally, function values get executed if exec is true // 首先exec要说true, 而且value是个function; // exec = !pass && exec && jQuery.isFunction(value); /* //当 $("body").attr("hehe","hehe").attr("hehe",function(index,att) { console.log(att); return att+111 }); //fn为获取和设置的回调; */ for ( var i = 0; i < length; i++ ) { //如果是执行就执行, this为当前元素,参数为index , //fn为获取当前值; //exec为假, value就是null或者一个要设定的值; fn( elems[i], key, exec ? value.call( elems[i], i, fn( elems[i], key ) ) : value, pass ); } return elems; } // Getting an attribute return length ? fn( elems[0], key ) : undefined; }, now: function() { return (new Date()).getTime(); }, // Create a simple deferred (one callbacks list) _Deferred: function() { var // callbacks list callbacks = [], // stored [ context , args ] fired, // to avoid firing when already doing so firing, // flag to know if the deferred has been cancelled cancelled, // the deferred itself //每次都返回一个延迟对象; deferred = { // done( f1, f2, ...) done: function() { //defer被cacel()的话就还done个毛线球; if ( !cancelled ) { var args = arguments, i, length, elem, type, _fired; if ( fired ) { _fired = fired; fired = 0; }; //把药执行的方法放到callback里面去; for ( i = 0, length = args.length; i < length; i++ ) { elem = args[ i ]; type = jQuery.type( elem ); if ( type === "array" ) { deferred.done.apply( deferred, elem ); } else if ( type === "function" ) { callbacks.push( elem ); } }; // if ( _fired ) { //如果resolveWith完以后就有_fired这个东东; deferred.resolveWith( _fired[ 0 ], _fired[ 1 ] ); } } return this; }, // resolve with given context and args resolveWith: function( context, args ) { if ( !cancelled && !fired && !firing ) { //firing变成已经执行; firing = 1; try { //使用context 和 args为一个数组,作为callback的参数传进去; while( callbacks[ 0 ] ) { callbacks.shift().apply( context, args ); } } finally { fired = [ context, args ]; firing = 0; } } return this; }, // resolve with this as context and given arguments resolve: function() { //如果是通过Deferred生成的实例, 这个配合promise执行, 上下文为promise , 这个promise有这些方法; //then done fail isResolved isRejected promise //如果没有promise就相当于resoveWith了, 主要是为callback提供了当前延迟对象的上下文; deferred.resolveWith( jQuery.isFunction( this.promise ) ? this.promise() : this, arguments ); return this; }, // Has this deferred been resolved? //返回状态 isResolved: function() { return !!( firing || fired ); }, // Cancel cancel: function() { cancelled = 1; callbacks = []; return this; } }; return deferred; }, // Full fledged deferred (two callbacks list) Deferred: function( func ) { var deferred = jQuery._Deferred(), failDeferred = jQuery._Deferred(), promise; // Add errorDeferred methods, then and promise jQuery.extend( deferred, { then: function( doneCallbacks, failCallbacks ) { deferred.done( doneCallbacks ).fail( failCallbacks ); return this; }, fail: failDeferred.done, rejectWith: failDeferred.resolveWith, reject: failDeferred.resolve, isRejected: failDeferred.isResolved, // Get a promise for this deferred // If obj is provided, the promise aspect is added to the object // promise是为了保护deferred的状态, 防止状态被修改; promise: function( obj , i /* internal */ ) { if ( obj == null ) { if ( promise ) { return promise; } promise = obj = {}; } i = promiseMethods.length; while( i-- ) { obj[ promiseMethods[ i ] ] = deferred[ promiseMethods[ i ] ]; } return obj; } } ); // Make sure only one callback list will be used //如果成功回调执行了, 失败的回调就清空; //如果失败的回调执行了, 成功的回调就清空; deferred.then( failDeferred.cancel, deferred.cancel ); // Unexpose cancel delete deferred.cancel; // Call given func if any //如果你传一个函数进来; //var f = function(arg){ f.aa = arg}; $.Deferred(f); // f就有了deferred这个东东; if ( func ) { func.call( deferred, deferred ); } return deferred; }, // Deferred helper when: function( object ) { var args = arguments, length = args.length, //若传进来只有一个deferred,那么deferred就是这个延迟对象, // 否则新建一deferred; deferred = length <= 1 && object && jQuery.isFunction( object.promise ) ? object : jQuery.Deferred(), //保护变量; promise = deferred.promise(), resolveArray; if ( length > 1 ) { resolveArray = new Array( length ); jQuery.each( args, function( index, element ) { jQuery.when( element ).then( function( value ) { //为每一个传进来的延迟对象添加一个回调function; //并把回调传进来的value保存到resolveArray里面; resolveArray[ index ] = arguments.length > 1 ? slice.call( arguments, 0 ) : value; //如果count清空了 if( ! --length ) { //执行回调; deferred.resolveWith( promise, resolveArray ); } }, deferred.reject ); } ); } else if ( deferred !== object ) { deferred.resolve( object ); }; //返回 return promise; }, // Use of jQuery.browser is frowned upon. // More details: http://docs.jquery.com/Utilities/jQuery.browser /* Here are some typical results: Internet Explorer: 6.0, 7.0, 8.0 Mozilla/Firefox/Flock/Camino: 1.7.12, 1.8.1.3, 1.9 Opera: 10.06, 11.01 Safari/Webkit: 312.8, 418.9 */ 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" }; }, //一个jQ的子集, 毛用啊,我勒个去; sub: function() { function jQuerySubclass( selector, context ) { //这个从原型链上找, 会找到了原来的jQ.fn.init return new jQuerySubclass.fn.init( selector, context ); } //继承工具方法; jQuery.extend( true, jQuerySubclass, this ); //把jQuery保存作为父类; jQuerySubclass.superclass = this; //改变sub的原型为 一个空的jQ对象; jQuerySubclass.fn = jQuerySubclass.prototype = this(); //修复constructor; jQuerySubclass.fn.constructor = jQuerySubclass; //子类; jQuerySubclass.subclass = this.subclass; jQuerySubclass.fn.init = function init( selector, context ) { //上下文的修复; if ( context && context instanceof jQuery && !(context instanceof jQuerySubclass) ) { context = jQuerySubclass(context); } //执行; return jQuery.fn.init.call( this, selector, context, rootjQuerySubclass ); }; //跟jQ一样把subClass的原型 挂到prototype.init的原型上面; jQuerySubclass.fn.init.prototype = jQuerySubclass.fn; //定义了一个闭包的 documentRoot; var rootjQuerySubclass = jQuerySubclass(document); return jQuerySubclass; }, browser: {} }); //到了目前为止, 为$ extend了一些工具方法; //该版本的这个延迟对象就是一个执行列表 // Create readyList deferred readyList = jQuery._Deferred(); //初始化type的map; // 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; }; //precated : 赞成 ,deprecated不赞成; // Deprecated, use jQuery.browser.webkit instead if ( jQuery.browser.webkit ) { jQuery.browser.safari = true; } //上面有$.inArray,应该是复写了; if ( indexOf ) { jQuery.inArray = function( elem, array ) { return indexOf.call( array, elem ); }; } // IE doesn't match non-breaking spaces with \s // 修复IE的正则reg的问题; 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 // 定义 标准的文档加载完毕 执行事件 ,事件会取消加载完毕的绑定,执行jQuery.ready(); 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 // IE下如果document可以滚动了就是dom加载完毕, 神马鸟hack。。笑尿;; // 事件会取消加载完毕的绑定,执行jQuery.ready() 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(); } // Expose jQuery to the global object return (window.jQuery = window.$ = jQuery); })(); //想看JS的兼容问题吗,来吧,来看jQuery的代码吧,哈哈; (function() { jQuery.support = {}; var div = document.createElement("div"); div.style.display = "none"; div.innerHTML = " <link/><table></table><a href='/a' style='color:red;float:left;opacity:.55;'>a</a><input type='checkbox'/>"; var all = div.getElementsByTagName("*"), a = div.getElementsByTagName("a")[0], select = document.createElement("select"), opt = select.appendChild( document.createElement("option") ); // Can't get basic test support if ( !all || !all.length || !a ) { return; } jQuery.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 insted) style: /red/.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: div.getElementsByTagName("input")[0].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, // Will be defined later deleteExpando: true, optDisabled: false, checkClone: false, _scriptEval: null, noCloneEvent: true, boxModel: null, inlineBlockNeedsLayout: false, shrinkWrapBlocks: false, reliableHiddenOffsets: true }; // Make sure that the options inside disabled selects aren't marked as disabled // (WebKit marks them as diabled) select.disabled = true; jQuery.support.optDisabled = !opt.disabled; jQuery.support.scriptEval = function() { if ( jQuery.support._scriptEval === null ) { var root = document.documentElement, script = document.createElement("script"), id = "script" + jQuery.now(); script.type = "text/javascript"; try { script.appendChild( document.createTextNode( "window." + id + "=1;" ) ); } catch(e) {} root.insertBefore( script, root.firstChild ); // Make sure that the execution of code works by injecting a script // tag with appendChild/createTextNode // (IE doesn't support this, fails, and uses .text instead) if ( window[ id ] ) { jQuery.support._scriptEval = true; delete window[ id ]; } else { jQuery.support._scriptEval = false; } root.removeChild( script ); // release memory in IE root = script = id = null; } return jQuery.support._scriptEval; }; // Test to see if it's possible to delete an expando from an element // Fails in Internet Explorer try { delete div.test; } catch(e) { jQuery.support.deleteExpando = false; } if ( div.attachEvent && div.fireEvent ) { div.attachEvent("onclick", function click() { // Cloning a node shouldn't copy over any // bound event handlers (IE does this) jQuery.support.noCloneEvent = false; div.detachEvent("onclick", click); }); div.cloneNode(true).fireEvent("onclick"); } div = document.createElement("div"); div.innerHTML = "<input type='radio' name='radiotest' checked='checked'/>"; var fragment = document.createDocumentFragment(); fragment.appendChild( div.firstChild ); // WebKit doesn't clone checked state correctly in fragments jQuery.support.checkClone = fragment.cloneNode(true).cloneNode(true).lastChild.checked; // Figure out if the W3C box model works as expected // document.body must exist before we can do this jQuery(function() { var div = document.createElement("div"), body = document.getElementsByTagName("body")[0]; // Frameset documents with no body should not run this code if ( !body ) { return; } div.style.width = div.style.paddingLeft = "1px"; body.appendChild( div ); jQuery.boxModel = jQuery.support.boxModel = div.offsetWidth === 2; if ( "zoom" in div.style ) { // 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.style.display = "inline"; div.style.zoom = 1; jQuery.support.inlineBlockNeedsLayout = div.offsetWidth === 2; // Check if elements with layout shrink-wrap their children // (IE 6 does this) div.style.display = ""; div.innerHTML = "<div style='width:4px;'></div>"; jQuery.support.shrinkWrapBlocks = div.offsetWidth !== 2; } div.innerHTML = "<table><tr><td style='padding:0;border:0;display:none'></td><td>t</td></tr></table>"; var tds = div.getElementsByTagName("td"); // 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) jQuery.support.reliableHiddenOffsets = 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) jQuery.support.reliableHiddenOffsets = jQuery.support.reliableHiddenOffsets && tds[0].offsetHeight === 0; div.innerHTML = ""; body.removeChild( div ).style.display = "none"; div = tds = null; }); // Technique from Juriy Zaytsev // http://thinkweb2.com/projects/prototype/detecting-event-support-without-browser-sniffing/ var eventSupported = function( eventName ) { var el = document.createElement("div"); eventName = "on" + eventName; // 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 ( !el.attachEvent ) { return true; } var isSupported = (eventName in el); if ( !isSupported ) { el.setAttribute(eventName, "return;"); isSupported = typeof el[eventName] === "function"; } el = null; return isSupported; }; jQuery.support.submitBubbles = eventSupported("submit"); jQuery.support.changeBubbles = eventSupported("change"); // release memory in IE div = all = a = null; })(); //这个是jQ实例会用到的工具方法,和jQ的实例的各种息息相关的; var rbrace = /^(?:\{.*\}|\[.*\])$/; 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加上版本加上随机的字符串,把非数字的拿掉; 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 ) { //如果是类型属性就在缓存中查找对应的data, 否则象window这样的元素直接通过expando查找; elem = elem.nodeType ? jQuery.cache[ elem[jQuery.expando] ] : elem[ jQuery.expando ]; //直接返回 而且要非空; return !!elem && !jQuery.isEmptyObject(elem); }, data: function( elem, name, data, pvt /* Internal Use Only */ ) { //如果是embed ,applet ,object就直接退出去,不报错; if ( !jQuery.acceptData( elem ) ) { return; } var internalKey = jQuery.expando, getByName = typeof name === "string", thisCache, // ie67 不区分节点属性和 dom属性的, data必须统一绑定到dom属性上面 // 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 // 如果是dom节点就通过jQuery的cache查找,剩下的比如window或者其他的对象 直接在自己身上找; 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, 如果连id都没有那还玩个毛先求; id = isNode ? elem[ jQuery.expando ] : elem[ jQuery.expando ] && jQuery.expando; // Avoid doing any more work than we need to when trying to get data on an // object that has no data at all // 如果连传进来的data都没有, 而且没有唯一的id 就返回 。 // 有可能要是指的name是一个对象,所以也要排除name是对象, getByName确保了name是对象的话为假,就不会return了 if ( (!id || (pvt && id && !cache[ id ][ internalKey ])) && getByName && data === undefined ) { return; } //没有id就设置一个id, 当前是有保存的数据(data)的 if ( !id ) { // Only DOM nodes need a new unique ID for each element since their data // ends up in the global cache // 只有dom元素才有唯一的id;其余的就直接用expando作为id, 参数的情况多,处理也多; if ( isNode ) { elem[ jQuery.expando ] = id = ++jQuery.uuid; } else { id = jQuery.expando; } } //如果不存在的话, 设置id缓存的空对象 if ( !cache[ id ] ) { cache[ id ] = {}; } // 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" ) { // pvt是内部用的,是private的意思, 把当前的name直接继承到私有的缓存中; if ( pvt ) { cache[ id ][ internalKey ] = jQuery.extend(cache[ id ][ internalKey ], name); } else { //直接继承 cache[ id ] = jQuery.extend(cache[ id ], name); } } //公用的缓存 thisCache = cache[ id ]; // Internal jQuery data is stored in a separate object inside the object's data // cache in order to avoid key collisions between internal data and user-defined // data // 有必要开辟一个私人的缓冲区,; if ( pvt ) { if ( !thisCache[ internalKey ] ) { thisCache[ internalKey ] = {}; } thisCache = thisCache[ internalKey ]; } //data不是undefined说明 name一定是字符串了?, 好吧,说的通...也没人那么做; if ( data !== undefined ) { thisCache[ name ] = data; } // TODO: This is a hack for 1.5 ONLY. It will be removed in 1.6. Users should // not attempt to inspect the internal events object using jQuery.data, as this // internal data object is undocumented and subject to change. // 你可以传一个event, 就会获取到该元素绑定的所有事件; if ( name === "events" && !thisCache[name] ) { return thisCache[ internalKey ] && thisCache[ internalKey ].events; } //如果是name字符串 就返回这个值, 否则返回整个元素缓存; return getByName ? thisCache[ name ] : thisCache; }, //删除数据; removeData: function( elem, name, pvt /* Internal Use Only */ ) { if ( !jQuery.acceptData( elem ) ) { return; } var 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[ jQuery.expando ] : jQuery.expando; // If there is already no cache entry for this object, there is no // purpose in continuing if ( !cache[ id ] ) { return; } if ( name ) { //分内部和非内部数据两种情况 var thisCache = pvt ? cache[ id ][ internalKey ] : cache[ id ]; //删删删删; if ( thisCache ) { delete thisCache[ name ]; // If there is no data left in the cache, we want to continue // and let the cache object itself get destroyed if ( !jQuery.isEmptyObject(thisCache) ) { //不给Object.delete; return; } } } // See jQuery.data for more information // 不管name是否有传,只要pvt是真 , 就把所有私人缓存全删了, 好屌是不是, 谁这么干啊,卧槽; if ( pvt ) { delete cache[ id ][ internalKey ]; // Don't destroy the parent cache unless the internal data object // had been the only thing left in it if ( !jQuery.isEmptyObject(cache[ id ]) ) { return; }; }; //引用内部数据的地址; var internalCache = cache[ id ][ internalKey ]; // 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 // 兼容问题 if ( jQuery.support.deleteExpando || cache != window ) { delete cache[ id ]; } else { cache[ id ] = null; } // We destroyed the entire user cache at once because it's faster than // iterating through each key, but we need to continue to persist internal // data if it existed if ( internalCache ) { cache[ id ] = {}; cache[ id ][ internalKey ] = internalCache; // Otherwise, we need to eliminate the expando on the node to avoid // false lookups in the cache for entries that no longer exist } else if ( isNode ) { // IE67 中的元素节点是com组件 ,你删他东西要报错的 ,他会提示你:(对象不支持此操作), 使用removeAttribute删除属性靠谱 // removeAttribute 就可以删除, 不会报错(因为IE67不区分dom属性和html属性); // 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[ jQuery.expando ]; } else if ( elem.removeAttribute ) { elem.removeAttribute( jQuery.expando ); } else { elem[ jQuery.expando ] = 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 //确认dom是否可以保存数据 acceptData: function( elem ) { if ( elem.nodeName ) { var match = jQuery.noData[ elem.nodeName.toLowerCase() ]; if ( match ) { //embed 和 applet是true, //apple 的组件 return !(match === true || elem.getAttribute("classid") !== match); /* 简写真是坑爹啊; if(match === true) { return false; }; if( elem.getAttribute("classid") !== match ) { return false; } */ } } return true; } }); //这个为实例原型继承了data; jQuery.fn.extend({ data: function( key, value ) { var data = null; //连key都不给, 就返回所有的数据呗; if ( typeof key === "undefined" ) { if ( this.length ) { //data为第一个 data = jQuery.data( this[0] ); if ( this[0].nodeType === 1 ) { var attr = this[0].attributes, name; for ( var i = 0, l = attr.length; i < l; i++ ) { name = attr[i].name; //把所有html5定义的datalist保存到缓存的列表; if ( name.indexOf( "data-" ) === 0 ) { name = name.substr( 5 ); dataAttr( this[0], name, data[ name ] ); } } } } return data; //这个相当于是设置了; } else if ( typeof key === "object" ) { return this.each(function() { jQuery.data( this, key ); }); }; //这个是key不是undefined; var parts = key.split("."); parts[1] = parts[1] ? "." + parts[1] : ""; //这个是获取值的情况; if ( value === undefined ) { //可以绑定元素的getData事件, 如果元素的属性发生改变,自动触发事件, 传进去的参数为改变的属性名; data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]); // Try to fetch any internally stored data first if ( data === undefined && this.length ) { //如果这个的值不在jQ的cache缓存里面; data = jQuery.data( this[0], key ); //从data-list里面查找; data = dataAttr( this[0], key, data ); } //parts[1]应该是命名空间, 为什么会重新迭代自己哇; return data === undefined && parts[1] ? this.data( parts[0] ) : data; } else { //这个是设置值 return this.each(function() { var $this = jQuery( this ), args = [ parts[0], value ]; //触发自定义的设置事件; //你通过这种方式绑定 : //$("body").bind("setData",function(){console.log(1)}); //$("body").data("xx",2) ==》》 就会在控制台打出 1; // ** 注意绑定的时候不要! 这个符号, 这个符号会被过滤掉; $this.triggerHandler( "setData" + parts[1] + "!", args ); jQuery.data( this, key, value ); //触发自定义的改变事件; $this.triggerHandler( "changeData" + parts[1] + "!", args ); }); } }, removeData: function( key ) { return this.each(function() { jQuery.removeData( this, key ); }); } }); //这个会把元素的datalist的值比如 data-xx="true" 更新到 $.cache.uniqueId里面去; 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 ) { data = elem.getAttribute( "data-" + key ); if ( typeof data === "string" ) { try { data = data === "true" ? true : data === "false" ? false : data === "null" ? null : !jQuery.isNaN( data ) ? parseFloat( 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; } //这个是工具方法,还是实例上面用的; jQuery.extend({ queue: function( elem, type, data ) { if ( !elem ) { return; } //用户可以使用自定义的队列type : 默认为 fxqueue; type = (type || "fx") + "queue"; //获取在缓存系统中的队列, 这个是公用的缓存; var q = jQuery._data( elem, type ); //如果没有data // Speed up dequeue by getting out quickly if this is just a lookup //这个是get; if ( !data ) { return q || []; } //调用工具方法_data保存队列; if ( !q || jQuery.isArray(data) ) { q = jQuery._data( elem, type, jQuery.makeArray(data) ); } else { //直接push就好了; q.push( data ); }; return q; }, /* $("body").queue(function(bar) { console.log(1),bar() }).queue(function(bar) { console.log(2),bar() }).queue(function(bar){ console.log(3),bar() }).queue(function(bar){ console.log(4) }); $("body").dequeue(); */ dequeue: function( elem, type ) { type = type || "fx"; //获取初始值; var queue = jQuery.queue( elem, type ), fn = queue.shift(); // // 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"); }; //用elem作为上下文 执行, 有一个回调时dequeue fn.call(elem, function() { jQuery.dequeue(elem, type); }); } if ( !queue.length ) { jQuery.removeData( elem, type + "queue", true ); } } }); /* $("body").queue(function(bar) { console.log(1),bar() }).queue(function(bar) { console.log(2),bar(); }).queue(function(bar){ console.log(3),bar() }).queue(function(bar){ console.log(4) }); //默认的fx会马上执行哦; //第二次执行的时候queue[0]是 inprogress,所以不会马上执行; $("body").queue(function(bar) { console.log(1),bar() }).queue(function(bar) { console.log(2),bar(); }).queue(function(bar){ console.log(3),bar() }).queue(function(bar){ console.log(4) }); //这样不会马上执行; $("body").queue('fx', "inprogress") .queue(function(bar) { console.log(1),bar() }).queue(function(bar) { console.log(2),bar(); }).queue(function(bar){ console.log(3),bar() }).queue(function(bar){ console.log(4) }); */ //添加方法到实例上面了 jQuery.fn.extend({ queue: function( type, data ) { if ( typeof type !== "string" ) { data = type; type = "fx"; } if ( data === undefined ) { return jQuery.queue( this[0], type ); } //进栈就马上执行哦; return this.each(function( i ) { //如果是默认的fx var queue = jQuery.queue( this, type, data ); //马上开始队列; if ( type === "fx" && queue[0] !== "inprogress" ) { jQuery.dequeue( this, type ); } }); }, dequeue: function( type ) { return this.each(function() { jQuery.dequeue( this, type ); }); }, //动画效果中有用到delay , 会延迟执行; // Based off of the plugin by Clint Helfers, with permission. // http://blindsignals.com/index.php/2009/07/jquery-delay/ delay: function( time, type ) { time = jQuery.fx ? jQuery.fx.speeds[time] || time : time; type = type || "fx"; return this.queue( type, function() { var elem = this; setTimeout(function() { jQuery.dequeue( elem, type ); }, time ); }); }, clearQueue: function( type ) { return this.queue( type || "fx", [] ); } }); var rclass = /[\n\t\r]/g, rspaces = /\s+/, rreturn = /\r/g, rspecialurl = /^(?:href|src|style)$/, rtype = /^(?:button|input)$/i, rfocusable = /^(?:button|input|object|select|textarea)$/i, rclickable = /^a(?:rea)?$/i, rradiocheck = /^(?:radio|checkbox)$/i; jQuery.props = { "for": "htmlFor", "class": "className", readonly: "readOnly", maxlength: "maxLength", cellspacing: "cellSpacing", rowspan: "rowSpan", colspan: "colSpan", tabindex: "tabIndex", usemap: "useMap", frameborder: "frameBorder" }; jQuery.fn.extend({ attr: function( name, value ) { //通过access减少代码量, //判断了value可以是一个function的情况; return jQuery.access( this, name, value, true, jQuery.attr ); }, //也是调用attr这个方法的, fn拿来搞毛用; removeAttr: function( name, fn ) { return this.each(function(){ jQuery.attr( this, name, "" ); if ( this.nodeType === 1 ) { this.removeAttribute( name ); } }); }, addClass: function( value ) { if ( jQuery.isFunction(value) ) { return this.each(function(i) { var self = jQuery(this); //如果value是function 就给这个function传 这个元素的class; self.addClass( value.call(this, i, self.attr("class")) ); }); } if ( value && typeof value === "string" ) { var classNames = (value || "").split( rspaces ); //为什么不调用each呢,还要写循环啊; for ( var i = 0, l = this.length; i < l; i++ ) { var elem = this[i]; //是元素节点的话 if ( elem.nodeType === 1 ) { //优化设置 if ( !elem.className ) { elem.className = value; } else { //通过字符串的操作进行class操作; var className = " " + elem.className + " ", setClass = elem.className; for ( var c = 0, cl = classNames.length; c < cl; c++ ) { if ( className.indexOf( " " + classNames[c] + " " ) < 0 ) { setClass += " " + classNames[c]; } } elem.className = jQuery.trim( setClass ); } } } } return this; }, removeClass: function( value ) { //这个也可以通过access调用吗; if ( jQuery.isFunction(value) ) { return this.each(function(i) { var self = jQuery(this); self.removeClass( value.call(this, i, self.attr("class")) ); }); } //字符串连接; if ( (value && typeof value === "string") || value === undefined ) { var classNames = (value || "").split( rspaces ); for ( var i = 0, l = this.length; i < l; i++ ) { var elem = this[i]; if ( elem.nodeType === 1 && elem.className ) { if ( value ) { var className = (" " + elem.className + " ").replace(rclass, " "); for ( var c = 0, cl = classNames.length; c < cl; c++ ) { className = className.replace(" " + classNames[c] + " ", " "); } elem.className = jQuery.trim( className ); } else { elem.className = ""; } } } } return this; }, toggleClass: function( value, stateVal ) { var type = typeof value, isBool = typeof stateVal === "boolean"; // if function if ( jQuery.isFunction( value ) ) { return this.each(function(i) { var self = jQuery(this); self.toggleClass( value.call(this, i, self.attr("class"), stateVal), stateVal ); }); } return this.each(function() { if ( type === "string" ) { // toggle individual class names var className, i = 0, self = jQuery( this ), state = stateVal, classNames = value.split( rspaces ); //toggleClass 你可以传("xx aa bb cc");一接口多用, write less, do more; while ( (className = classNames[ i++ ]) ) { // check each className given, space seperated list state = isBool ? state : !self.hasClass( className ); self[ state ? "addClass" : "removeClass" ]( className ); } } else if ( type === "undefined" || type === "boolean" ) { //把当前的classname保存到私有的数据里面; if ( this.className ) { // store className if set jQuery._data( this, "__className__", this.className ); }; //简写是好,但是不觉得看着麻烦吗。 john reisg \*_*\ // toggle whole className this.className = this.className || value === false ? "" : jQuery._data( this, "__className__" ) || ""; } }); }, hasClass: function( selector ) { var className = " " + selector + " "; for ( var i = 0, l = this.length; i < l; i++ ) { //用indexOf if ( (" " + this[i].className + " ").replace(rclass, " ").indexOf( className ) > -1 ) { return true; } } return false; }, //val做的处理有点多哦; val: function( value ) { //获取的情况下; if ( !arguments.length ) { var elem = this[0]; if ( elem ) { if ( jQuery.nodeName( elem, "option" ) ) { // attributes.value is undefined in Blackberry 4.7 but // uses .value. See #6932 var val = elem.attributes.value; //specified 特性, 如果用户设定了值那么specified就是true; return !val || val.specified ? elem.value : elem.text; }; // 如果是select // We need to handle select boxes special if ( jQuery.nodeName( elem, "select" ) ) { var index = elem.selectedIndex, values = [], options = elem.options, one = elem.type === "select-one"; // // Nothing was selected if ( index < 0 ) { return null; } // 会返回多个值; // Loop through all the selected options for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) { var option = options[ i ]; // Don't return options that are disabled or in a disabled optgroup if ( option.selected && (jQuery.support.optDisabled ? !option.disabled : option.getAttribute("disabled") === null) && (!option.parentNode.disabled || !jQuery.nodeName( option.parentNode, "optgroup" )) ) { // Get the specific value for the option value = jQuery(option).val(); // We don't need an array for one selects if ( one ) { return value; } // Multi-Selects return an array values.push( value ); } } return values; } //如果是单选radio标签; 统一返回on; // Handle the case where in Webkit "" is returned instead of "on" if a value isn't specified if ( rradiocheck.test( elem.type ) && !jQuery.support.checkOn ) { return elem.getAttribute("value") === null ? "on" : elem.value; } //最后弄这个, 如果是input的file文件类型 ,就会返回文件的全部路径; // Everything else, we just grab the value return (elem.value || "").replace(rreturn, ""); } return undefined; } //设置的情况下; var isFunction = jQuery.isFunction(value); return this.each(function(i) { var self = jQuery(this), val = value; //这个检测有必要吗.... if ( this.nodeType !== 1 ) { return; } //是function的话; if ( isFunction ) { val = value.call(this, i, self.val()); } // Treat null/undefined as ""; convert numbers to string if ( val == null ) { val = ""; } else if ( typeof val === "number" ) { val += ""; } else if ( jQuery.isArray(val) ) { //会有设置array的元素吗; val = jQuery.map(val, function (value) { return value == null ? "" : value + ""; }); } //兼容问题的处理; if ( jQuery.isArray(val) && rradiocheck.test( this.type ) ) { this.checked = jQuery.inArray( self.val(), val ) >= 0; } else if ( jQuery.nodeName( this, "select" ) ) { var values = jQuery.makeArray(val); jQuery( "option", this ).each(function() { this.selected = jQuery.inArray( jQuery(this).val(), values ) >= 0; }); if ( !values.length ) { this.selectedIndex = -1; } //如果不用库的话,自己弄也是直接使用 elem.value = settingValue; } else { this.value = val; } }); } }); jQuery.extend({ // shortcut, 快捷操作, 比如 // $("inpt").attr("val","hehe").attr("css","height:100px").attr({"html":"innerHTML"}); // 会调用实例下的指定的方法; attrFn: { val: true, css: true, html: true, text: true, data: true, width: true, height: true, offset: true }, //这个还是工具方法上面的方法,不是实例上的方法,这个方法包揽的东西不少; attr: function( elem, name, value, pass ) { // don't get/set attributes on text, comment and attribute nodes //text文本; //comment注释; //属性节点, 我就搞不懂了,为什么属性也可以算是节点; if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || elem.nodeType === 2 ) { return undefined; } if ( pass && name in jQuery.attrFn ) { return jQuery(elem)[name](value); } //不是xml,话说xml我用的也少啊; var notxml = elem.nodeType !== 1 || !jQuery.isXMLDoc( elem ), // Whether we are setting (or getting) set = value !== undefined; // Try to normalize/fix the name // 执行的前后顺序是先 (notxml&&jQuery.props /* jQuery.props = { "for": "htmlFor", "class": "className", readonly: "readOnly", maxlength: "maxLength", cellspacing: "cellSpacing", rowspan: "rowSpan", colspan: "colSpan", tabindex: "tabIndex", usemap: "useMap", frameborder: "frameBorder" }; */ name = notxml && jQuery.props[ name ] || name; // Only do all the following if this is a node (faster for style) if ( elem.nodeType === 1 ) { // These attributes require special treatment // rspecialurl ==>> /href|src|style/; var special = rspecialurl.test( name ); //safari无法获取selectIndex的情况; // Safari mis-reports the default selected property of an option // Accessing the parent's selectedIndex property fixes it if ( name === "selected" && !jQuery.support.optSelected ) { var parent = elem.parentNode; if ( parent ) { parent.selectedIndex; // Make sure that it also works with optgroups, see #5701 if ( parent.parentNode ) { parent.parentNode.selectedIndex; } } } // If applicable, access the attribute via the DOM 0 way // 'in' checks fail in Blackberry 4.7 #6931 // href ,src 和 style不处理; /*如果 name in elem 就是node的属性,不一定是attr该处理的东东了; "id" in document.body ==>> true; "className" in document.body ==>> true */ if ( (name in elem || elem[ name ] !== undefined) && notxml && !special ) { if ( set ) { //有type的只有input的节点了。。。。; // We can't allow the type property to be changed (since it causes problems in IE) if ( name === "type" && rtype.test( elem.nodeName ) && elem.parentNode ) { jQuery.error( "type property can't be changed" ); }; //这个是node属性,为什么要用removeAttribte删除呢;调用attr(elem, name , null);就会把这个属性删了哇; if ( value === null ) { if ( elem.nodeType === 1 ) { elem.removeAttribute( name ); } } else { elem[ name ] = value; } } //要弄懂为什么set在前面,而get是在后面,因为set以后也要返回get; //剩下的是的get; //document.body.getAttributeNode("id").nodeType ==>> 2; //getAttributeNode相当于attributes.item() // browsers index elements by id/name on forms, give priority to attributes. /* //getAttributeNode是神马东西的DEMO; <!DOCTYPE html> <html> <head> <title></title> </head> <body> <form name="foo"> <input value="hehe" type="text" name="ipt0" /> </form> <script> var eForm = document.getElementsByTagName("form")[0]; console.log( eForm.getAttributeNode("ipt0") ); // ==> null; console.log( eForm.getAttributeNode("name") ); // ==> nodeType==2, value=2; </script> </body> </html> */ if ( jQuery.nodeName( elem, "form" ) && elem.getAttributeNode(name) ) { //所用的 getAttributeNode() 已不赞成使用。请使用 getAttribute() 替代。;卧槽火狐爆了这个东西; return elem.getAttributeNode( name ).nodeValue; }; // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ if ( name === "tabIndex" ) { var attributeNode = elem.getAttributeNode( "tabIndex" ); //如果用户有定义过才返回值,没定义郭 // elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set // 死链; // http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/ // 看看司徒正美的 :http://www.cnblogs.com/rubylouvre/archive/2009/12/07/1618182.html /* 知识普及 : tabIndex在浏览器下都支持, tabIndex在W3C是属于节点属性又属于固有属性, 表单元素用的最多, //DIV等这些块节点在W3C下不能设置tabIndex, 但是所有浏览器厂商都实现了DIV的tabIndex;tabIndex如果有设置值得情况下,无论是通过固有属性还是节点方式获取, 值都能获取到,如下 : <div tabindex="2">第二个</div> $("div[tabindex=2]")[0].tabIndex ==>> 2 $("div[tabindex=2]")[0].getAttribute("tabIndex") ==>> "2" //这东西记也感觉记不住; 但是没有默认值得情况下, 标准浏览器通过节点属性 获取的值如果是DIV等元素 ==>> -1; 被设置了返回被设置的值; 是input这些元素 ==>> 0 如果是input这些元素 通过attribute获取 ==>> null; IE67无论任何方式获取的都是返回0 //IE下判断这属性是否被设置 var _hasAttr = function(node, name){ var attr = node.getAttributeNode && node.getAttributeNode(name); return attr && attr.specified; // Boolean }; */ return attributeNode && attributeNode.specified ? attributeNode.value : //主要是处理各个浏览器返回值不同的的兼容问题; //如果是可以聚焦的元素 或者是 拥有href的a 或者 area元素返回 null, 剩下的返回undefined; //rfocusable = /(button|input|object|select|textarea)/i //rclickable = /^(a|area)$/i, rfocusable.test( elem.nodeName ) || rclickable.test( elem.nodeName ) && elem.href ? null : undefined; }; return elem[ name ]; }; //node属性完结, 轮到了html节点属性了哇; //set先走,走完走get; //标准浏览器这个压根就不走哇; //$.attr(document.body, "style", "height:100px;,background:#f00"); //jQuery.support.style在chrome上是true, IE上测试是false;还是兼容测试哇; if ( !jQuery.support.style && notxml && name === "style" ) { if ( set ) { elem.style.cssText = "" + value; } return elem.style.cssText; } //还是set啊, 不过这个set是HTML节点的set;; if ( set ) { // convert the value to a string (all browsers do this but IE) see #1070 elem.setAttribute( name, "" + value ); } //没定义按照标准的就返回undefined,bb的返回""; // Ensure that missing attributes return undefined // Blackberry 4.7 returns "" from getAttribute #6938 if ( !elem.attributes[ name ] && (elem.hasAttribute && !elem.hasAttribute( name )) ) { return undefined; } //最后对毛链接做一下兼容; //IE下会自动补全地址;, 所以要给第二个参数; var attr = !jQuery.support.hrefNormalized && notxml && special ? // Some attributes require a special call on IE elem.getAttribute( name, 2 ) : elem.getAttribute( name ); //返回get的值; // Non-existent attributes return null, we normalize to undefined return attr === null ? undefined : attr; }; //如果不是dom节点的话,按照属性设置直接设置值就好了; // Handle everything which isn't a DOM element node if ( set ) { elem[ name ] = value; } return elem[ name ]; } }); //千辛万苦终于到了事件模块, 这个模块很重要哇 ; var rnamespaces = /\.(.*)$/, rformElems = /^(?:textarea|input|select)$/i, rperiod = /\./g, rspace = / /g, rescape = /[^\w\s.|`]/g, fcleanup = function( nm ) { return nm.replace(rescape, "\\$&"); }, eventKey = "events"; /* 知识点匹配需要转义的字符; rescape = /[^\w\s.|`]/g, //为每一个需要转义的字符添加\ nm.replace(rescape, "\\$&"); fcleanup("sdfsdfdsfds.s&**(((*)f\\") "sdfsdfdsfds.s\&\*\*\(\(\(\*\)f\\" */ /* * A number of helper functions used for managing events. * Many of the ideas behind this code originated from * Dean Edwards' addEvent library. */ jQuery.event = { // Bind an event to an element // Original by Dean Edwards //所有绑定事件都是通过add这个方法绑定的; //元素 //click mouseover 正常情况下; // 如果是代理的情况下; add会绑定两次,第一次是绑定live事件,第二个是绑定click事件; 第一个点以后的是要匹配的元素选择器(要把 ^替换成.); //"live.click.div" // "live.click.`hehe" ==>> click.`hehe; // live.click.div`hehe ==>> "click.div`hehe"; //data没毛用; add: function( elem, types, handler, data ) { //debugger; //text节点和 comment节点全滚哇, 话说 属性节点(nodeType === 2)的你可以绑定事件吗? 是的,好像真的可以哇, 奇葩了; if ( elem.nodeType === 3 || elem.nodeType === 8 ) { return; }; // For whatever reason, IE has trouble passing the window object // around, causing it to be cloned in the process // 跨iframe if ( jQuery.isWindow( elem ) && ( elem !== window && !elem.frameElement ) ) { elem = window; } //绑定一个空的事件 if ( handler === false ) { handler = returnFalse; } else if ( !handler ) { // Fixes bug #7229. Fix recommended by jdalton return; }; var handleObjIn, handleObj; //根据传进来的handler是否有handler属性,然后设置handlerObjIn事件描述和,事件触发要执行的函数handlerObjIn.handler; if ( handler.handler ) { handleObjIn = handler; handler = handleObjIn.handler; }; // Make sure that the function being executed has a unique ID //保证了唯一的事件, 后面可以根据这个唯一的id进行remove或者别的操作,比较方便; if ( !handler.guid ) { handler.guid = jQuery.guid++; }; // Init the element's event structure //这个获取的是内部私用的缓存保存的数据; var elemData = jQuery._data( elem ); // If no elemData is found then we must be trying to bind to one of the // banned noData elements // 没有就不玩啦, 为什么会没有呢 ,因为noData的元素不能乱给事件哇, object[classId="/\d/mig"], applet, embed; // 对啊embed不能绑定事件,只能通过innerHTML绑定事件, 以前碰到过这种情况; if ( !elemData ) { return; }; //eventKey = "events" 上面定义了这个鸟东西; var events = elemData[ eventKey ], //eventHandle是为这个元素绑定的事件 eventHandle = elemData.handle; //正常的events应该是一个数组哇, 是function的情况应该特别对待; if ( typeof events === "function" ) { // On plain objects events is a fn that holds the the data // which prevents this data from being JSON serialized // the function does not need to be called, it just contains the data eventHandle = events.handle; events = events.events; } else if ( !events ) { //处理非节点元素的事件绑定, 这个应该是为了扩张绑定事件到非节点元素上面; if ( !elem.nodeType ) { // On plain objects, create a fn that acts as the holder // of the values to avoid JSON serialization of event data elemData[ eventKey ] = elemData = function(){}; }; //新建一个事件保存列表; elemData.events = events = {}; }; //所有的事件都绑定同一个事件函数, 剩下的给event.handle处理不同的情况; //使用这种方式对用户的来说, 可配置性变好了, 比如 // 1 : 你可以让事件按照顺序执行(某些浏览器不按照顺序来,因为事件执行时冒泡阶段执行); // 2 : 没想出来; if ( !eventHandle ) { elemData.handle = eventHandle = function() { // Handle the second event of a trigger and when // an event is called after a page has unloaded //jQuery.event.triggered默认是false的; return typeof jQuery !== "undefined" && !jQuery.event.triggered ? jQuery.event.handle.apply( eventHandle.elem, arguments ) : undefined; }; }; // Add elem as a property of the handle function // This is to prevent a memory leak with non-native events in IE. // 为事件函数添加元素的引用; 阻止ie下的内存泄漏; eventHandle.elem = elem; // Handle multiple events separated by a space // jQuery(...).bind("mouseover mouseout", fn); //开始了一大堆处理, 对绑定的事件进行c; types = types.split(" "); var type, i = 0, namespaces; while ( (type = types[ i++ ]) ) { //重新弄一个事件描述(引用); handleObj = handleObjIn ? jQuery.extend({}, handleObjIn) : { handler: handler, data: data }; // Namespaced event handlers // 修复时间的命名空间; // 目测现在事件代理被弄成 live click^#div1^div的情况 if ( type.indexOf(".") > -1 ) { namespaces = type.split("."); type = namespaces.shift(); handleObj.namespace = namespaces.slice(0).sort().join("."); } else { namespaces = []; handleObj.namespace = ""; }; //为事件描述添加事件类型 handleObj.type = type; //为事件描述添加事件的guid, 这个handle是从bind那边处理过的(处理了one,bind), 也可能从live那边传过来的; if ( !handleObj.guid ) { handleObj.guid = handler.guid; } // Get the current list of functions bound to this event // 创建或者获取事件的队列; var handlers = events[ type ], special = jQuery.event.special[ type ] || {}; // Init the event handler queue if ( !handlers ) { handlers = events[ type ] = []; // Check for a special event handler // Only use addEventListener/attachEvent if the special // events handler returns false // 如果绑定的是beforeunload,就特殊对待, // //如果绑定focusin或者foucuseout就转化成使用fouse和blur, // live或者是 // 是ready就绑定到document.ready // 如果是mouseenter或者是mouseleave,就使用mouseout和mousein模拟; // live只有add和remove,所以这个setup肯定不走, 直接走addEVentListener的绑定; if ( !special.setup || special.setup.call( elem, data, namespaces, eventHandle ) === false ) { // Bind the global event handler to the element //如果是live就是绑定了自定义事件, 触发的时候要注意一下; if ( elem.addEventListener ) { elem.addEventListener( type, eventHandle, false ); } else if ( elem.attachEvent ) { elem.attachEvent( "on" + type, eventHandle ); } } } //使用事件代理的时候的却有点绕, jQ高版本的话对事件代理进行了优化;; //live的时候,这里又绑定了一次哦,只有live有add和remove; if ( special.add ) { //第一次add的是live的handlers,第二次add的才是真正的handlers; //调用special的绑定方式进行绑定; //这个绑定有重新迭代了一次$.event.add...所以要注意一下, 这一次的迭代才是真正绑定需要的事件 special.add.call( elem, handleObj ); if ( !handleObj.handler.guid ) { handleObj.handler.guid = handler.guid; }; }; //绑定事件的事件函数要做不同处理, 但是绑定的事件描述还是要根据事件的类型放到handlers里面去; //所有的处理都是为了把handleObj放到handlers这个对象里面; // Add the function to the element's handler list handlers.push( handleObj ); //优化; // Keep track of which events have been used, for global triggering jQuery.event.global[ type ] = true; /*handle结构是这样的 $.cache = { Number ElementGuid : { string jQueryExpando : { events : { "click" : [function(){}, function(){}, function(){}, function(){}] }, handle : function(){....} } } } */ }; // Nullify elem to prevent memory leaks in IE elem = null; }, global: {}, // Detach an event or set of events from an element //删除事件目测应该和绑定差不多道理; remove: function( elem, types, handler, pos ) { // don't do events on text and comment nodes // 依旧可以把事件绑定给属性节点; if ( elem.nodeType === 3 || elem.nodeType === 8 ) { return; }; if ( handler === false ) { handler = returnFalse; }; // var ret, type, fn, j, i = 0, all, namespaces, namespace, special, eventType, handleObj, origType, elemData = jQuery.hasData( elem ) && jQuery._data( elem ), events = elemData && elemData[ eventKey ]; //没有事件列表还玩个毛; if ( !elemData || !events ) { return; }; //这个和$.event.add一样的, if ( typeof events === "function" ) { elemData = events; events = events.events; }; //$.event.remove({type : "click",handler : clickFn}); // types is actually an event object here if ( types && types.type ) { handler = types.handler; types = types.type; }; // Unbind all events for the element // 没有types的话, 就是移除所有的事件; //类型是命名空间的话 if ( !types || typeof types === "string" && types.charAt(0) === "." ) { types = types || ""; //迭代命名空间的事件,一个个删除; for ( type in events ) { jQuery.event.remove( elem, type + types ); }; //下面没必要在走了; return; }; // Handle multiple events separated by a space // jQuery(...).unbind("mouseover mouseout", fn); types = types.split(" "); while ( (type = types[ i++ ]) ) { origType = type; handleObj = null; all = type.indexOf(".") < 0; namespaces = []; //all 指是或否是这个事件的全部命名空间都要删除; if ( !all ) { // Namespaced event handlers namespaces = type.split("."); type = namespaces.shift(); namespace = new RegExp("(^|\\.)" + jQuery.map( namespaces.slice(0).sort(), fcleanup ).join("\\.(?:.*\\.)?") + "(\\.|$)"); }; eventType = events[ type ]; if ( !eventType ) { continue; }; if ( !handler ) { for ( j = 0; j < eventType.length; j++ ) { handleObj = eventType[ j ]; //对这个事件描述对象进行判断, 如果匹配到了这个命名空间就把这个时间删了;; if ( all || namespace.test( handleObj.namespace ) ) { jQuery.event.remove( elem, origType, handleObj.handler, j ); eventType.splice( j--, 1 ); } } continue; } special = jQuery.event.special[ type ] || {}; for ( j = pos || 0; j < eventType.length; j++ ) { handleObj = eventType[ j ]; //用户也可以传绑定的函数进来, 如果guid一样就删; if ( handler.guid === handleObj.guid ) { // remove the given handler for the given type if ( all || namespace.test( handleObj.namespace ) ) { if ( pos == null ) { eventType.splice( j--, 1 ); }; //有remove的只有live有了; if ( special.remove ) { special.remove.call( elem, handleObj ); }; } if ( pos != null ) { break; } } } //如果某个事件的 事件列表删除完了, 就把这个events【type】清空; // remove generic event handler if no more handlers exist if ( eventType.length === 0 || pos != null && eventType.length === 1 ) { //mousein mouseout focusin fousout 对走这个; if ( !special.teardown || special.teardown.call( elem, namespaces ) === false ) { jQuery.removeEvent( elem, type, elemData.handle ); }; ret = null; delete events[ type ]; }; }; // Remove the expando if it's no longer used if ( jQuery.isEmptyObject( events ) ) { var handle = elemData.handle; if ( handle ) { handle.elem = null; } delete elemData.events; delete elemData.handle; if ( typeof elemData === "function" ) { jQuery.removeData( elem, eventKey, true ); } else if ( jQuery.isEmptyObject( elemData ) ) { jQuery.removeData( elem, undefined, true ); } } }, //trigger是给用户用的; // bubbling is internal trigger: function( event, data, elem /*, bubbling */ ) { // Event object or event type var type = event.type || event, bubbling = arguments[3]; //默认都是冒泡; if ( !bubbling ) { //自己新建一个event对象; event = typeof event === "object" ? // jQuery.Event object event[ jQuery.expando ] ? event : // Object literal jQuery.extend( jQuery.Event(type), event ) : // Just the event type (string) jQuery.Event(type); // 有!的代表触发的是自定义的属性更改事件, 对用户来说,作用不多,有点像IE的onpropertychange; if ( type.indexOf("!") >= 0 ) { event.type = type = type.slice(0, -1); event.exclusive = true; }; // Handle a global if ( !elem ) { // 如果你要执行对应type全部事件,那么就要阻止默认事件 // 如果你不阻止冒泡的话会 // Don't bubble custom events when global (to avoid too much overhead) // 这个event是假的,模拟出来的东东; event.stopPropagation(); // Only trigger if we've ever bound an event for it if ( jQuery.event.global[ type ] ) { // XXX This code smells terrible. event.js should not be directly // inspecting the data cache jQuery.each( jQuery.cache, function() { // internalKey variable is just used to make it easier to find // and potentially change this stuff later; currently it just // points to jQuery.expando var internalKey = jQuery.expando, internalCache = this[ internalKey ]; if ( internalCache && internalCache.events && internalCache.events[type] ) { jQuery.event.trigger( event, data, internalCache.handle.elem ); } }); //我不知道这里为什么不return掉; } } // Handle triggering a single element // don't do events on text and comment nodes if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 ) { return undefined; } // Clean up in case it is reused event.result = undefined; event.target = elem; // Clone the incoming data, if any data = jQuery.makeArray( data ); data.unshift( event ); } // event.currentTarget = elem; // Trigger the event, it is assumed that "handle" is a function var handle = elem.nodeType ? jQuery._data( elem, "handle" ) : (jQuery._data( elem, eventKey ) || {}).handle; //这个就是手动触发事件了哇, data里面是有新建的event对象的; if ( handle ) { handle.apply( elem, data ); }; var parent = elem.parentNode || elem.ownerDocument; //手动触发行内绑定的事件; // Trigger an inline bound script try { if ( !(elem && elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()]) ) { if ( elem[ "on" + type ] && elem[ "on" + type ].apply( elem, data ) === false ) { event.result = false; event.preventDefault(); } } // prevent IE from throwing an error for some elements with some event types, see #3533 } catch (inlineError) {} //我靠这个,又手动触发了父级的对应事件,就是事件冒泡了 ,(jQ为什么考虑这么全面); if ( !event.isPropagationStopped() && parent ) { jQuery.event.trigger( event, data, parent, true ); //默认事件没有被阻止的话; } else if ( !event.isDefaultPrevented() ) { var old, target = event.target, targetType = type.replace( rnamespaces, "" ), isClick = jQuery.nodeName( target, "a" ) && targetType === "click", special = jQuery.event.special[ targetType ] || {}; if ( (!special._default || special._default.call( elem, event ) === false) && !isClick && !(target && target.nodeName && jQuery.noData[target.nodeName.toLowerCase()]) ) { try { if ( target[ targetType ] ) { // Make sure that we don't accidentally re-trigger the onFOO events old = target[ "on" + targetType ]; if ( old ) { target[ "on" + targetType ] = null; } jQuery.event.triggered = true; target[ targetType ](); } // prevent IE from throwing an error for some elements with some event types, see #3533 } catch (triggerError) {} if ( old ) { target[ "on" + targetType ] = old; } jQuery.event.triggered = false; } } }, //所绑定的事件, 这个方法的event是最初的事件对象; handle: function( event ) { var all, handlers, namespaces, namespace_re, events, namespace_sort = [], //除了event浏览器的调用这个事件以外, 用户也可以模拟一个假的event,假的eventType等等,手动触发哦; args = jQuery.makeArray( arguments ); //对事件的event进行浏览器兼容统一处理 event = args[0] = jQuery.event.fix( event || window.event ); // currentTarget指的是绑定事件的元素;, // 如果是代理绑定的话, 那么事件函数里面的this不是绑定的元素, 用户如果有需要的话通过currentTarget引用即可; event.currentTarget = this; // Namespaced event handlers //如果没有命名空间的话就是true; /*比如 你通过 $("body").bind("click.nameSpace0",function(){console.log(1)}) 绑定了事件, $("body").bind("click.nameSpace1",function(){console.log(1)}) 当你左键点击body元素的时候 这两个绑定的事件都会触发; 但是你想手动触发nameSpace0这个事件的话,你可以直接trigger("click.nameSpace0"); 事件的命名空间要看你怎么用了, 不用也没大问题, 主要是解耦了各个事件函数; */ all = event.type.indexOf(".") < 0 && !event.exclusive; //这个也只有用户手动触发的时候会走; if ( !all ) { namespaces = event.type.split("."); //事件名和事件命名空间拿出来; event.type = namespaces.shift(); namespace_sort = namespaces.slice(0).sort(); //开头或者是一个dot; //结尾或者是一个dot; namespace_re = new RegExp("(^|\\.)" + namespace_sort.join("\\.(?:.*\\.)?") + "(\\.|$)"); }; //别忘记了命名空间和liveHanler是不一样的,别犯迷糊, 一个是事件的分类,一个是在父级上面绑定事件; event.namespace = event.namespace || namespace_sort.join("."); //获取所有事件 // eventKey === "events"; events = jQuery._data(this, eventKey); //默认的events不会是function的, 这个是什么情况,好像是jQuery.special.type[ name ]那里面的事件; if ( typeof events === "function" ) { events = events.events; }; //handler不是函数哦, 是所有有关这个事件的事件描述 //标准的事件描述对象 应该是这样的:{data : guid : handle :function(){}, name : "xx", type : "click"} //获取对应的事件 比如 click 还是 mouseoout这样的情况; handlers = (events || {})[ event.type ]; //如果没有绑定对应的事件就不走里面, 作用1:优化, 2:避免里面报错; if ( events && handlers ) { // Clone the handlers to prevent manipulation //复制一个事件描述对象; handlers = handlers.slice(0); //迭代所有的事件; for ( var j = 0, l = handlers.length; j < l; j++ ) { var handleObj = handlers[ j ]; // Filter the functions by class if ( all || namespace_re.test( handleObj.namespace ) ) { // Pass in a reference to the handler function itself // So that we can later remove it //事件 event.handler = handleObj.handler; //事件的数据;为事件添加data这个这么重要吗,还是我不会用; event.data = handleObj.data; //把handleObj事件描述对象挂到event事件对象上面 event.handleObj = handleObj; //现在事件里面的事件对象就有了handleObj事件描述对象这东西了; //执行事件; //默认的就一个event, 如果不是默认的就会把所有的参数重新传进去; //利用这一点,我们可以把自己定义个发布者和订阅者,而且参数自己填(event都是要的哇) var ret = handleObj.handler.apply( this, args ); //进行而外的处理 if ( ret !== undefined ) { //把数据保存到event.result, 下次执行的话,可以调用event.result获取上次事件保存的值, 有用,HOW? event.result = ret; //对return false进行特殊的处理; if ( ret === false ) { event.preventDefault(); event.stopPropagation(); }; }; //不执行了哇, 跳出这个循环了; if ( event.isImmediatePropagationStopped() ) { break; } } } } return event.result; }, props: "altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "), //复制一个事件对象, 统一事件对象的兼容; fix: function( event ) { //如果已经经过jQ处理过的事件对象; if ( event[ jQuery.expando ] ) { return event; } // store a copy of the original event object // and "clone" to set read-only properties var originalEvent = event; event = jQuery.Event( originalEvent ); //根据原始的对象复制一个假的事件对象, 要复制的属性分别是: //altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode layerX layerY metaKey newValue offsetX offsetY pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which; for ( var i = this.props.length, prop; i; ) { prop = this.props[ --i ]; event[ prop ] = originalEvent[ prop ]; }; // Fix target property, if necessary if ( !event.target ) { //火狐下是srcElement ,chorme和ie都是target; // Fixes #1925 where srcElement might not be defined either event.target = event.srcElement || document; }; // check if target is a textnode (safari) if ( event.target.nodeType === 3 ) { event.target = event.target.parentNode; } // Add relatedTarget, if necessary //修复IE下没有relateTarget但是有fromeElement和toElement; if ( !event.relatedTarget && event.fromElement ) { event.relatedTarget = event.fromElement === event.target ? event.toElement : event.fromElement; }; //这个也是IE的问题,没有pageX和pageY;,根据clientX或者clientY加上界面滚动值在减去IE678下(Body,或者HTML标签上)的2px问题; // Calculate pageX/Y if missing and clientX/Y available if ( event.pageX == null && event.clientX != null ) { var doc = document.documentElement, body = document.body; event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc && doc.clientLeft || body && body.clientLeft || 0); event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc && doc.clientTop || body && body.clientTop || 0); }; //DOM3规定使用whitch, 不用charCode也不用keyCode; // Add which for key events if ( event.which == null && (event.charCode != null || event.keyCode != null) ) { event.which = event.charCode != null ? event.charCode : event.keyCode; }; //苹果系统的META键就是window中的CTRL键; // Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs) if ( !event.metaKey && event.ctrlKey ) { event.metaKey = event.ctrlKey; } //刚刚测试过了,笔记本上面的fn键即使按住了,事件对象 并没有 按住fn键的属性 显示; // Add which for click: 1 === left; 2 === middle; 3 === right // Note: button is not normalized, so don't use it // 保证了当前不是通过键盘的事件; //保证了是button这个键存在值; if ( !event.which && event.button !== undefined ) { event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) )); }; return event; }, // Deprecated, use jQuery.guid instead guid: 1E8, // Deprecated, use jQuery.proxy instead proxy: jQuery.proxy, //这几个事件绑定的时候要特殊对待, 移除绑定也要特殊对待; special: { //当页面加载完毕以后要初始化的几个方法; ready: { // Make sure the ready event is setup setup: jQuery.bindReady, teardown: jQuery.noop }, live: { //事件代理是根据事件描述handleObj对象 , 重新绑定事件, 不过handle是liveHandler,这个很重要; add: function( handleObj ) { //这个就调用了add; jQuery.event.add( this, // click.^div^#div1^klass liveConvert( handleObj.origType, handleObj.selector ), //使用liveHandler作为事件对象; jQuery.extend({}, handleObj, {handler: liveHandler, guid: handleObj.handler.guid}) ); }, remove: function( handleObj ) { jQuery.event.remove( this, liveConvert( handleObj.origType, handleObj.selector ), handleObj ); } }, beforeunload: { setup: function( data, namespaces, eventHandle ) { // We only want to do this special case on windows if ( jQuery.isWindow( this ) ) { this.onbeforeunload = eventHandle; } }, teardown: function( namespaces, eventHandle ) { if ( this.onbeforeunload === eventHandle ) { this.onbeforeunload = null; } } } } }; //第一次运行就把正确的函数赋值给对象属性; 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 = function( src ) { // Allow instantiation without the 'new' keyword // if !(this instanceof jQuery.Event) 也行; if ( !this.preventDefault ) { return new jQuery.Event( src ); } // Event object // 一般来说src是对象的话,应该是系统提供的事件对象; if ( src && src.type ) { this.originalEvent = src; this.type = src.type; // Events bubbling up the document may have been marked as prevented // by a handler lower down the tree; reflect the correct value. this.isDefaultPrevented = (src.defaultPrevented || src.returnValue === false || src.getPreventDefault && src.getPreventDefault()) ? returnTrue : returnFalse; // Event type } else { this.type = src; } // timeStamp is buggy for some events on Firefox(#3843) // So we won't rely on the native value this.timeStamp = jQuery.now(); // Mark it as fixed this[ jQuery.expando ] = true; }; function returnFalse() { return false; } function returnTrue() { return true; } // jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding // http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html 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和mouseleave; // Checks if an event happened on an element within another element // Used in jQuery.event.special.mouseenter and mouseleave handlers var withinElement = function( event ) { // Check if mouse(over|out) are still within the same parent element var parent = event.relatedTarget; // FiwithinElementrefox sometimes assigns relatedTarget a XUL element // which we cannot access the parentNode property of try { // Traverse up the tree while ( parent && parent !== this ) { parent = parent.parentNode; }; if ( parent !== this ) { // set the correct event type event.type = event.data; // handle event if we actually just moused on to a non sub-element jQuery.event.handle.apply( this, arguments ); } // assuming we've left the element since we most likely mousedover a xul element } catch(e) { } }, // In case of event delegation, we only need to rename the event.type, // liveHandler will take care of the rest. delegate = function( event ) { event.type = event.data; jQuery.event.handle.apply( this, arguments ); }; // Create mouseenter and mouseleave events jQuery.each({ mouseenter: "mouseover", mouseleave: "mouseout" }, function( orig, fix ) { jQuery.event.special[ orig ] = { //setup就是绑定事件 setup: function( data ) { jQuery.event.add( this, fix, data && data.selector ? delegate : withinElement, orig ); }, //teardown就是取消事件 teardown: function( data ) { jQuery.event.remove( this, fix, data && data.selector ? delegate : withinElement ); } }; }); // submit delegation if ( !jQuery.support.submitBubbles ) { jQuery.event.special.submit = { //绑定事件 setup: function( data, namespaces ) { if ( this.nodeName && this.nodeName.toLowerCase() !== "form" ) { jQuery.event.add(this, "click.specialSubmit", function( e ) { var elem = e.target, type = elem.type; if ( (type === "submit" || type === "image") && jQuery( elem ).closest("form").length ) { e.liveFired = undefined; return trigger( "submit", this, arguments ); } }); jQuery.event.add(this, "keypress.specialSubmit", function( e ) { var elem = e.target, type = elem.type; if ( (type === "text" || type === "password") && jQuery( elem ).closest("form").length && e.keyCode === 13 ) { e.liveFired = undefined; return trigger( "submit", this, arguments ); } }); } else { return false; } }, //取消事件; teardown: function( namespaces ) { jQuery.event.remove( this, ".specialSubmit" ); } }; } // setup和teardown这些东西自己添加扩展事件, 就是闭包闭包又是闭包,一层一层一层又是一层; // change delegation, happens here so we have bind. if ( !jQuery.support.changeBubbles ) { var changeFilters, getVal = function( elem ) { var type = elem.type, val = elem.value; if ( type === "radio" || type === "checkbox" ) { val = elem.checked; } else if ( type === "select-multiple" ) { val = elem.selectedIndex > -1 ? jQuery.map( elem.options, function( elem ) { return elem.selected; }).join("-") : ""; } else if ( elem.nodeName.toLowerCase() === "select" ) { val = elem.selectedIndex; } return val; }, testChange = function testChange( e ) { var elem = e.target, data, val; if ( !rformElems.test( elem.nodeName ) || elem.readOnly ) { return; } data = jQuery._data( elem, "_change_data" ); val = getVal(elem); // the current data will be also retrieved by beforeactivate if ( e.type !== "focusout" || elem.type !== "radio" ) { jQuery._data( elem, "_change_data", val ); } if ( data === undefined || val === data ) { return; } if ( data != null || val ) { e.type = "change"; e.liveFired = undefined; return jQuery.event.trigger( e, arguments[1], elem ); } }; jQuery.event.special.change = { filters: { focusout: testChange, beforedeactivate: testChange, click: function( e ) { var elem = e.target, type = elem.type; if ( type === "radio" || type === "checkbox" || elem.nodeName.toLowerCase() === "select" ) { return testChange.call( this, e ); } }, // Change has to be called before submit // Keydown will be called before keypress, which is used in submit-event delegation keydown: function( e ) { var elem = e.target, type = elem.type; if ( (e.keyCode === 13 && elem.nodeName.toLowerCase() !== "textarea") || (e.keyCode === 32 && (type === "checkbox" || type === "radio")) || type === "select-multiple" ) { return testChange.call( this, e ); } }, // Beforeactivate happens also before the previous element is blurred // with this event you can't trigger a change event, but you can store // information beforeactivate: function( e ) { var elem = e.target; jQuery._data( elem, "_change_data", getVal(elem) ); } }, //绑定事件 setup: function( data, namespaces ) { if ( this.type === "file" ) { return false; } for ( var type in changeFilters ) { jQuery.event.add( this, type + ".specialChange", changeFilters[type] ); } return rformElems.test( this.nodeName ); }, //取消事件 teardown: function( namespaces ) { jQuery.event.remove( this, ".specialChange" ); return rformElems.test( this.nodeName ); } }; changeFilters = jQuery.event.special.change.filters; // Handle when the input is .focus()'d changeFilters.focus = changeFilters.beforeactivate; } function trigger( type, elem, args ) { args[0].type = type; return jQuery.event.handle.apply( elem, args ); } //修复浏览器fousein和fouseout支持; // Create "bubbling" focus and blur events if ( document.addEventListener ) { jQuery.each({ focus: "focusin", blur: "focusout" }, function( orig, fix ) { jQuery.event.special[ fix ] = { setup: function() { this.addEventListener( orig, handler, true ); }, teardown: function() { this.removeEventListener( orig, handler, true ); } }; function handler( e ) { e = jQuery.event.fix( e ); e.type = fix; return jQuery.event.handle.call( this, e ); } }); } //这个是继承到实例原型上面的代码; //利用闭包实现了bind和 one; 减少代码量; jQuery.each(["bind", "one"], function( i, name ) { jQuery.fn[ name ] = function( type, data, fn ) { // Handle object literals //处理传进来的是对象的情况, 调用对应的方法; 一接口的多种实用方法; if ( typeof type === "object" ) { for ( var key in type ) { this[ name ](key, data, type[key], fn); }; return this; }; //修正参数 data , fn; //data为什么要===false $("div").bind("click",false,function() {});怎么办哇; if ( jQuery.isFunction( data ) || data === false ) { fn = data; data = undefined; }; //初始化绑定的函数 //如果是one的话就是重新定义事件函数, 这个事件函数也是一个闭包, 引用了fn, 需要强调的是 $.proxy的作用是设置匿名事件函数的guid和fn一样;; var handler = name === "one" ? jQuery.proxy( fn, function( event ) { jQuery( this ).unbind( event, handler ); return fn.apply( this, arguments ); }) : fn; //对unload的事件进行优化, 本身unload的事件就是不能一直挂在元素上面的; if ( type === "unload" && name !== "one" ) { this.one( type, data, fn ); } else { for ( var i = 0, l = this.length; i < l; i++ ) { //调用工具, 现在的参数一定是对的, 里面就不用担心用户乱传参数进来了; jQuery.event.add( this[i], type, handler, data ); } } return this; }; }); // jQuery.fn.extend({ unbind: function( type, fn ) { // Handle object literals // 这个和unbind做一样的处理; if ( typeof type === "object" && !type.preventDefault ) { for ( var key in type ) { this.unbind(key, type[key]); }; } else { for ( var i = 0, l = this.length; i < l; i++ ) { //unbind; jQuery.event.remove( this[i], type, fn ); } } return this; }, //delegate也是调用live哇; delegate: function( selector, types, data, fn ) { return this.live( types, data, fn, selector ); }, //unbind是调用die,1.6以后的版本好像没有live和die了; undelegate: function( selector, types, fn ) { if ( arguments.length === 0 ) { return this.unbind( "live" ); } else { return this.die( types, null, fn, selector ); } }, trigger: function( type, data ) { return this.each(function() { jQuery.event.trigger( type, data, this ); }); }, //trigger 和triggerHandler的区别是 后者 触发了当前的第一个元素的对应事件, 而且阻止了默认操作和冒泡; triggerHandler: function( type, data ) { if ( this[0] ) { var event = jQuery.Event( type ); event.preventDefault(); event.stopPropagation(); jQuery.event.trigger( event, data, this[0] ); //事件对象有一个result, 说明 迭代执行事件的时候的返回值被保存到了event.result去; return event.result; }; }, //toggle和hover就是对click进行了封装而已; toggle: function( fn ) { // Save reference to arguments for access in closure var args = arguments, i = 1; // link all the functions, so any of them can unbind this click handler // 把这几个事件函数的guid设置成一样的数字,保证了使用unbind的时候可以取消这个click事件; // i从第一个开始迭代到最后一个; while ( i < args.length ) { jQuery.proxy( fn, args[ i++ ] ); }; /* 这个循环和for( var i=0; i<len ;i++); for( var i=0; i<len ;) {i++}这是一样的; while ( i < args.length ) { jQuery.proxy( fn, args[ i ] ); i++; }; */ //又用了一个闭包, 绑定这个了fn这个时间; return this.click( jQuery.proxy( fn, function( event ) { // Figure out which function to execute // i现在是总数; /* 0%4 ==>> 0 1%4 ==>> 1 2%4 ==>> 2 3%4 ==>> 3 4%4 ==>> 0 //内部用的_data jQuery._data = function ( elem, name, data ) { return jQuery.data( elem, name, data, true ); }; */ var lastToggle = ( jQuery._data( this, "lastToggle" + fn.guid ) || 0 ) % i; jQuery._data( this, "lastToggle" + fn.guid, lastToggle + 1 ); // Make sure that clicks stop //为什么要阻止默认事件哇; event.preventDefault(); //执行 // and execute the function return args[ lastToggle ].apply( this, arguments ) || false; })); }, hover: function( fnOver, fnOut ) { return this.mouseenter( fnOver ).mouseleave( fnOut || fnOver ); } }); var liveMap = { focus: "focusin", blur: "focusout", mouseenter: "mouseover", mouseleave: "mouseout" }; //两个闭包, 减少代码量; //这里面的i没有什么用, name才有用; jQuery.each(["live", "die"], function( i, name ) { jQuery.fn[ name ] = function( types, data, fn, origSelector /* Internal Use Only */ ) { var type, i = 0, match, namespaces, preType, //事件代理的text; 没有的话传进来默认为当前的元素的选择符; selector = origSelector || this.selector, //如果有origSelector就是 当前的元素, 否则 document?, ,为context绑定事件; context = origSelector ? this : jQuery( this.context ); //和bind unbind一样,提供object的方式传参; write less do more; if ( typeof types === "object" && !types.preventDefault ) { for ( var key in types ) { context[ name ]( key, data, types[key], selector ); }; return this; }; //处理参数, 实在不懂data有毛用哇; //$("body").live("click",function(){},"div"); //$("body").live("click","",function(){},"div"); if ( jQuery.isFunction( data ) ) { fn = data; data = undefined; }; //支持多事件的情况; $("body").live("click mousein mouseout","",function(){},"div"); types = (types || "").split(" "); while ( (type = types[ i++ ]) != null ) { // rnamespace = /\.(.*)$/; // 事件的命名空间, 如果你绑定了click事件,而且要区分click事件的类别分别不同情况触发,就可以使用命名空间; match = rnamespaces.exec( type ); namespaces = ""; if ( match ) { /* /dfd/.exec("eadfdsdfe.sdfsdfe"); ["dfd"] */ namespaces = match[0]; //命名空间 type = type.replace( rnamespaces, "" ); //类型 }; //系统没有hover事件,把hover事件替换成mouseenter和mouseleave; if ( type === "hover" ) { types.push( "mouseenter" + namespaces, "mouseleave" + namespaces ); continue; }; preType = type; //为了让事件冒泡吧,所以做了处理; /* var liveMap = { focus: "focusin", blur: "focusout", mouseenter: "mouseover", mouseleave: "mouseout" }; */ if ( type === "focus" || type === "blur" ) { types.push( liveMap[ type ] + namespaces ); type = type + namespaces; } else { //这个不靠谱吧,mouseenter和mouseleave 就chrome的低版本不支持啊, 为什么要全部使用mouseover和mouseout进行模拟呢; type = (liveMap[ type ] || type) + namespaces; }; //现在还在闭包内部,所以要根据情况判断是添加事件还是移除事件; if ( name === "live" ) { // bind live handler //context = origSelector ? this : jQuery( this.context );别忘记了context是this的引用或者其他对象的引用; for ( var j = 0, l = context.length; j < l; j++ ) { //要绑定的对象 //liveConvert("click",".class0 .wa") ==>> "click.`class0&`wa" //liveConvert("click",".class0 .wa #div") ==>> "click.`class0&`wa&#div" //内部自己约定了事件代理的描述; jQuery.event.add( context[j], "live." + liveConvert( type, selector ), //这个是事件的描述, 高版本的jQ把事件代理的描述和事件描述合并在一起了; //preType指的是未对用户传进来事件名字进行处理的事件名字; { data: data, selector: selector, handler: fn, origType: type, origHandler: fn, preType: preType } ); }; } else { // unbind live handler //这个直接使用unbind 这样好吗 john....; context.unbind( "live." + liveConvert( type, selector ), fn ); }; }; return this; }; //live的总结 : 事件的代理是在live处理的,使用了live绑定的元素绑定的事件默认是live开头, 后面就是懂得自然懂了。 比如:live."click.`class0&`wa&#div" }); //liveHandler也是挺简单, 主流程是根据事件对象的 事件描述对象 匹配出符合命名空间的绑定函数, 然后让他执行; function liveHandler( event ) { var stop, maxLevel, related, match, handleObj, elem, j, i, l, data, close, namespace, ret, elems = [], selectors = [], events = jQuery._data( this, eventKey ); if ( typeof events === "function" ) { events = events.events; } // Make sure we avoid non-left-click bubbling in Firefox (#3861) and disabled elements in IE (#6911) if ( event.liveFired === this || !events || !events.live || event.target.disabled || event.button && event.type === "click" ) { return; } //匹配合适的命名空间哇; if ( event.namespace ) { namespace = new RegExp("(^|\\.)" + event.namespace.split(".").join("\\.(?:.*\\.)?") + "(\\.|$)"); } event.liveFired = this; //复制一个事件描述对象的数组; var live = events.live.slice(0); for ( j = 0; j < live.length; j++ ) { handleObj = live[j]; //命名空间符合event.type 就把这个函数保存起来; if ( handleObj.origType.replace( rnamespaces, "" ) === event.type ) { selectors.push( handleObj.selector ); } else { live.splice( j--, 1 ); } } //这个返回的是所有匹配的元素; match = jQuery( event.target ).closest( selectors, event.currentTarget ); //这个是双重循环,过滤合适的element for ( i = 0, l = match.length; i < l; i++ ) { close = match[i]; for ( j = 0; j < live.length; j++ ) { handleObj = live[j]; if ( close.selector === handleObj.selector && (!namespace || namespace.test( handleObj.namespace )) ) { elem = close.elem; related = null; // Those two events require additional checking if ( handleObj.preType === "mouseenter" || handleObj.preType === "mouseleave" ) { event.type = handleObj.preType; related = jQuery( event.relatedTarget ).closest( handleObj.selector )[0]; } //把元素和事件对象保存起来; if ( !related || related !== elem ) { elems.push({ elem: elem, handleObj: handleObj, level: close.level }); } } } } //一个一个执行, event下的result属性依然保存着上一个事件的返回值; for ( i = 0, l = elems.length; i < l; i++ ) { match = elems[i]; if ( maxLevel && match.level > maxLevel ) { break; } event.currentTarget = match.elem; event.data = match.handleObj.data; event.handleObj = match.handleObj; ret = match.handleObj.origHandler.apply( match.elem, arguments ); //这个和handle里面的代码重复了, jQ高版本做了优化; if ( ret === false || event.isPropagationStopped() ) { maxLevel = match.level; if ( ret === false ) { stop = false; } if ( event.isImmediatePropagationStopped() ) { break; } } } return stop; }; //提供给事件代理用的; function liveConvert( type, selector ) { return (type && type !== "*" ? type + "." : "") + selector.replace(rperiod, "`").replace(rspace, "&"); }; //shortcut, 提供实例方法上面快捷方式调用 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").split(" "), function( i, name ) { // Handle event binding jQuery.fn[ name ] = function( data, fn ) { if ( fn == null ) { fn = data; data = null; } return arguments.length > 0 ? this.bind( name, data, fn ) : this.trigger( name ); }; if ( jQuery.attrFn ) { jQuery.attrFn[ name ] = true; } }); var runtil = /Until$/, rparentsprev = /^(?:parents|prevUntil|prevAll)/, // Note: This RegExp should be improved, or likely pulled from Sizzle rmultiselector = /,/, isSimple = /^.[^:#\[\.,]*$/, slice = Array.prototype.slice, //POS这个应该是position的意思, 这个是直接引用Sizzle里面的方法; POS = jQuery.expr.match.POS, // methods guaranteed to produce a unique set when starting from a unique set guaranteedUnique = { children: true, contents: true, next: true, prev: true }; jQuery.fn.extend({ find: function( selector ) { var ret = this.pushStack( "", "find", selector ), length = 0; for ( var i = 0, l = this.length; i < l; i++ ) { length = ret.length; //可以猜出 //selector : 用户传进来的选择器字符串; //这个是上下文context //ret : 执行完毕会把结果push到ret里面去; jQuery.find( selector, this[i], ret ); if ( i > 0 ) { // Make sure that the results are unique //双层循环去重, 和underscore.unique一样的; for ( var n = length; n < ret.length; n++ ) { for ( var r = 0; r < length; r++ ) { if ( ret[r] === ret[n] ) { ret.splice(n--, 1); break; } } } } } return ret; }, //这个返回的不是布尔值哦, 不过你可以通过判断返回值的length进行判断; has: function( target ) { var targets = jQuery( target ); //this.filter是对数组进行过滤 return this.filter(function() { //迭代传进来的选择器, 如果this有包含这个选择器就返回ture; for ( var i = 0, l = targets.length; i < l; i++ ) { if ( jQuery.contains( this, targets[i] ) ) { return true; } } }); }, //not的返回值也是一个jQ实例,通过pushStaack方法把当前的实例保存到新实例的pre属性; not: function( selector ) { return this.pushStack( winnow(this, selector, false), "not", selector); }, //同上,这个是实例过滤, 要调用迭代要用grep方法 (知识点: jQinstance.filter 和 jQinstance.grep filter: function( selector ) { return this.pushStack( winnow(this, selector, true), "filter", selector ); }, // 这个返回的是布尔值; is: function( selector ) { return !!selector && jQuery.filter( selector, this ).length > 0; }, closest: function( selectors, context ) { var ret = [], i, l, cur = this[0]; //分了两种情况, selector是array的情况下, seelctor不是字符串的情况下, 第一次听所closet可以传数组的情况,传数组的情况用的不多;; if ( jQuery.isArray( selectors ) ) { var match, selector, matches = {}, level = 1; if ( cur && selectors.length ) { for ( i = 0, l = selectors.length; i < l; i++ ) { selector = selectors[i]; if ( !matches[selector] ) { matches[selector] = jQuery.expr.match.POS.test( selector ) ? jQuery( selector, context || this.context ) : selector; } } while ( cur && cur.ownerDocument && cur !== context ) { for ( selector in matches ) { match = matches[selector]; if ( match.jquery ? match.index(cur) > -1 : jQuery(cur).is(match) ) { ret.push({ selector: selector, elem: cur, level: level }); } } cur = cur.parentNode; level++; } } return ret; }; // /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)(?![^\[]*\])(?![^\(]*\))/ var pos = POS.test( selectors ) ? jQuery( selectors, context || this.context ) : null; for ( i = 0, l = this.length; i < l; i++ ) { //又要遍历一遍自己; cur = this[i]; //高版本的浏览器都有内置的matchSelcotor方法,会更快的说; //在当前的元素一个一个往上跑,一个个查找, pos指的是这个选择器的所有元素; while ( cur ) { //一般都有pos出来的的元素; if ( pos ? pos.index(cur) > -1 : jQuery.find.matchesSelector(cur, selectors) ) { ret.push( cur ); break; } else { //如果没找到就到父级,除非当前的cur到了指定的上下文或者document就跳出去,说明没东西了嘛; cur = cur.parentNode; if ( !cur || !cur.ownerDocument || cur === context ) { break; } } } }; //通过工具方法取得唯一; ret = ret.length > 1 ? jQuery.unique(ret) : ret; return this.pushStack( ret, "closest", selectors ); }, // Determine the position of an element within // the matched set of elements index: function( elem ) { // if ( !elem || typeof elem === "string" ) { return jQuery.inArray( this[0], // If it receives a string, the selector is used // If it receives nothing, the siblings are used //elem是string的情况 //没有ele的情况,默认elem为当前元素父级的所有子元素; elem ? jQuery( elem ) : this.parent().children() ); } // Locate the position of the desired element return jQuery.inArray( // If it receives a jQuery object, the first element is used elem.jquery ? elem[0] : elem, this ); }, //话说die跑哪里去了哇; add: function( selector, context ) { var set = typeof selector === "string" ? jQuery( selector, context ) : jQuery.makeArray( selector ), all = jQuery.merge( this.get(), set ); return this.pushStack( isDisconnected( set[0] ) || isDisconnected( all[0] ) ? all : jQuery.unique( all ) ); }, //还有addSelf这个鸟东西哇, andSelf: function() { return this.add( this.prevObject ); } }); // A painfully simple check to see if an element is disconnected // from a document (should be improved, where feasible). function isDisconnected( node ) { return !node || !node.parentNode || node.parentNode.nodeType === 11; }; jQuery.each({ parent: function( elem ) { var parent = elem.parentNode; return parent && parent.nodeType !== 11 ? parent : null; }, parents: function( elem ) { // jQuery.dir 就是方向的意思, 这个元素的某一个方向上对应的元素; return jQuery.dir( elem, "parentNode" ); }, parentsUntil: function( elem, i, until ) { //utlil这个要在dir里面进行特别的对待; return jQuery.dir( elem, "parentNode", until ); }, next: function( elem ) { return jQuery.nth( elem, 2, "nextSibling" ); }, prev: function( elem ) { return jQuery.nth( elem, 2, "previousSibling" ); }, nextAll: function( elem ) { return jQuery.dir( elem, "nextSibling" ); }, prevAll: function( elem ) { return jQuery.dir( elem, "previousSibling" ); }, nextUntil: function( elem, i, until ) { return jQuery.dir( elem, "nextSibling", until ); }, prevUntil: function( elem, i, until ) { return jQuery.dir( elem, "previousSibling", until ); }, siblings: function( elem ) { return jQuery.sibling( elem.parentNode.firstChild, elem ); }, children: function( elem ) { return jQuery.sibling( elem.firstChild ); }, contents: function( elem ) { return jQuery.nodeName( elem, "iframe" ) ? //标准浏览器都有这个属性 //IE下进行特别的对待; elem.contentDocument || elem.contentWindow.document : // 1: 并不是没有用pushStack ,pushStack在闭包里面被统一处理了, // 2: makeArray可以合并伪数组对象 jQuery.makeArray( elem.childNodes ); } }, function( name, fn ) { //这种写法主要是为了减少代码量; //每一个each都会生成一个闭包; jQuery.fn[ name ] = function( until, selector ) { var ret = jQuery.map( this, fn, until ), // The variable 'args' was introduced in // https://github.com/jquery/jquery/commit/52a0238 // to work around a bug in Chrome 10 (Dev) and should be removed when the bug is fixed. // http://code.google.com/p/v8/issues/detail?id=1050 args = slice.call(arguments); //有until的有三种,parentUtil, nextUntil和 preUtil; if ( !runtil.test( name ) ) { selector = until; }; //又可以对传进来的字符串进行适配; if ( selector && typeof selector === "string" ) { ret = jQuery.filter( selector, ret ); }; //又是取唯一; ret = this.length > 1 && !guaranteedUnique[ name ] ? jQuery.unique( ret ) : ret; //为什么要取反? if ( (this.length > 1 || rmultiselector.test( selector )) && rparentsprev.test( name ) ) { ret = ret.reverse(); }; //pushStack 在这里进行了统一处理; return this.pushStack( ret, name, args.join(",") ); }; }); //这个是继承到jQ的工具方法; jQuery.extend({ filter: function( expr, elems, not ) { //特殊处理; if ( not ) { expr = ":not(" + expr + ")"; } //优化了只有一个选中元素的情况, 全部;用了Sizzle的东东; return elems.length === 1 ? jQuery.find.matchesSelector(elems[0], expr) ? [ elems[0] ] : [] : jQuery.find.matches(expr, elems); }, dir: function( elem, dir, until ) { var matched = [], //找到的元素; cur = elem[ dir ]; //找到该方向上的所有元素, 有加上util的特殊判断; //有当前元素, 可能nextSibling到了最后了, 所以没有了cur //到了document节点哥也不玩了 //没有util就到头 //元素类型为节点元素 while ( cur && cur.nodeType !== 9 && (until === undefined || cur.nodeType !== 1 || !jQuery( cur ).is( until )) ) { if ( cur.nodeType === 1 ) { matched.push( cur ); } cur = cur[dir]; } return matched; }, //nth相当于是index,不过nth是从第一个开始的,firstChilde是1,而且只返回一个,很简单的接口, 不过这个循环卧槽...; // 开始的元素 //第几个开始 //方向; // $.nth( $("body").parent().get(0), null, document.documentElement); nth: function( cur, result, dir, elem ) { result = result || 1; var num = 0; for ( ; cur; cur = cur[dir] ) { if ( cur.nodeType === 1 && ++num === result ) { break; } } return cur; }, //这个为什么不是siblings, 但是返回的是一个数组哇; // $.sibling($("div").get(0)) sibling: function( n, elem ) { var r = []; //result包含了当前的n(nodeElement); for ( ; n; n = n.nextSibling ) { if ( n.nodeType === 1 && n !== elem ) { r.push( n ); } } return r; } }); // Implement the identical functionality for filter and not //你搜索winnow ,引用了这个函数就只有not和filter; //可能是function ,可能是元素节点 ,也可能是字符串; // not是传进来的keep是false, 是排除的关系, filter传进来的是true, 这个是过滤合适的关系; // 本身not和filter可以传多种参数,所以单独拿出来比较靠谱; function winnow( elements, qualifier, keep ) { //如果传进来的事函数,就把当前的 if ( jQuery.isFunction( qualifier ) ) { //grep相对于是数组的filter方法 return jQuery.grep(elements, function( elem, i ) { //改变上下文, 参数为index和elem ,如果这个回调返回ture就过滤掉, //不返回或者返回false就不过滤掉; var retVal = !!qualifier.call( elem, i, elem ); return retVal === keep; }); //传element的不多吧; } else if ( qualifier.nodeType ) { return jQuery.grep(elements, function( elem, i ) { return (elem === qualifier) === keep; }); } else if ( typeof qualifier === "string" ) { //把所有的节点元素过滤出来.... var filtered = jQuery.grep(elements, function( elem ) { return elem.nodeType === 1; }); // isSimple = ^.[^:#\[\.,]*$/; // 传进来的class的情况下; if ( isSimple.test( qualifier ) ) { /* //从第二个集合中获取匹配选择器的元素; $.filter("#ipt",$("input")) $.filter = function ( expr, elems, not ) { if ( not ) { expr = ":not(" + expr + ")"; } return elems.length === 1 ? jQuery.find.matchesSelector(elems[0], expr) ? [ elems[0] ] : [] : jQuery.find.matches(expr, elems); }; */ return jQuery.filter(qualifier, filtered, !keep); } else { qualifier = jQuery.filter( qualifier, filtered ); }; }; //$.filter 和 $.grep要分清楚.... //$.filter第一个参数是字符串或者元素,是提供给$的实例用的,操作选中的元素并过滤出合适的子集, //$.grep的参数是回调;相对于Array.filter,根据回调的返回值产生集合; /*$.filter可以看成是$.grep的子集 可以写成 $.filter = function(str,elems) { var result = []; $.grep(elem,function(e, i) { return e.matchSelector(str); }); } */ return jQuery.grep(elements, function( elem, i ) { return (jQuery.inArray( elem, qualifier ) >= 0) === keep; }); }; var rinlinejQuery = / jQuery\d+="(?:\d+|null)"/g, rleadingWhitespace = /^\s+/, rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig, rtagName = /<([\w:]+)/, rtbody = /<tbody/i, rhtml = /<|&#?\w+;/, rnocache = /<(?:script|object|embed|option|style)/i, // checked="checked" or checked (html5) rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i, wrapMap = { option: [ 1, "<select multiple='multiple'>", "</select>" ], legend: [ 1, "<fieldset>", "</fieldset>" ], thead: [ 1, "<table>", "</table>" ], tr: [ 2, "<table><tbody>", "</tbody></table>" ], td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ], col: [ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ], area: [ 1, "<map>", "</map>" ], _default: [ 0, "", "" ] }; wrapMap.optgroup = wrapMap.option; wrapMap.tbody = wrapMap.tfoot = wrapMap.colgroup = wrapMap.caption = wrapMap.thead; wrapMap.th = wrapMap.td; // IE can't serialize <link> and <script> tags normally if ( !jQuery.support.htmlSerialize ) { wrapMap._default = [ 1, "div<div>", "</div>" ]; } jQuery.fn.extend({ text: function( text ) { //对于传进来的是函数进行处理; if ( jQuery.isFunction(text) ) { return this.each(function(i) { var self = jQuery( this ); self.text( text.call(this, i, self.text()) ); }); }; //对于传进来的不是对象,而且不是undefined进行处理; if ( typeof text !== "object" && text !== undefined ) { //通过createTextNode的方式新建文本节点; return this.empty().append( (this[0] && this[0].ownerDocument || document).createTextNode( text ) ); }; //最后是走没有传参数或者null的情况; return jQuery.text( this ); }, wrapAll: function( html ) { //又是处理是functoin的情况; if ( jQuery.isFunction( html ) ) { return this.each(function(i) { jQuery(this).wrapAll( html.call(this, i) ); }); }; //元素一定要存在; if ( this[0] ) { // The elements to wrap the target around //$(".hehe").wrapAll("<div></div><div>22222</div>") 此时的<div>22222</div>这些元素不生效; var wrap = jQuery( html, this[0].ownerDocument ).eq(0).clone(true); //可能是在DocumentFragment的情况下; if ( this[0].parentNode ) { wrap.insertBefore( this[0] ); }; /* $("body").map(function(){ return $("<div>"); }); jQueryInstance.map = function ( callback ) { return this.pushStack( jQuery.map(this, function( elem, i ) { return callback.call( elem, i, elem ); })); }; */ //$("body").wrapAll( $("<div><span>1111</span><span>2222</span></div>") ); //要找到wrap内部第一个底层元素, 然后把当前的this加到该层;, 利用了wrap是返回一个新的jq数组; wrap.map(function() { var elem = this; while ( elem.firstChild && elem.firstChild.nodeType === 1 ) { elem = elem.firstChild; }; return elem; }).append(this); } return this; }, //$("body").wrapInner("<div>") wrapInner: function( html ) { //处理了函数; if ( jQuery.isFunction( html ) ) { return this.each(function(i) { jQuery(this).wrapInner( html.call(this, i) ); }); } return this.each(function() { //保存当前元素的内容; var self = jQuery( this ), contents = self.contents(); //把当前的元素包用传进来的html进行包含; 所以说对这个元素进行wrapAll相当于选择这个元素的子元素,然后wrapInner; if ( contents.length ) { contents.wrapAll( html ); //没子元素直接append,相当于直接innerHTML; } else { self.append( html ); }; }); }, //wrap是wrapAll所有的this;相当于把当前的所有元素添加一个父级; //$("<div>1111</div><div>2222</div>").appendTo( $("body") ).wrap("<span></span>"); wrap: function( html ) { return this.each(function() { jQuery( this ).wrapAll( html ); }); }, //unwrap和wrap是相反的; unwrap: function() { return this.parent().each(function() { if ( !jQuery.nodeName( this, "body" ) ) { //把当前替换成当前元素的子元素; jQuery( this ).replaceWith( this.childNodes ); } }).end(); }, //调用domManip减少代码量和统一管理参数;; append: function() { return this.domManip(arguments, true, function( elem ) { //watch expressions; if ( this.nodeType === 1 ) { this.appendChild( elem ); }; }); }, //内部也调用了domManip prepend: function() { return this.domManip(arguments, true, function( elem ) { if ( this.nodeType === 1 ) { this.insertBefore( elem, this.firstChild ); } }); }, ////domManip就是属性系统中的access啊, 主要的作用就是统一处理各种个样的参数; before: function() { //if(1){console.log(1)}else if(1){console.log(2)},就打了一个1哦,不要想太多; //$("body").before( $("<div>") );在body的前面加了个DIV元素; if ( this[0] && this[0].parentNode ) { return this.domManip(arguments, false, function( elem ) { this.parentNode.insertBefore( elem, this ); }); //没有parentNode情况就是在内存中,不在页面里面 } else if ( arguments.length ) { var set = jQuery(arguments[0]); //直接弄成jQ的伪数组对象,然后把set这个jQ对象后面添加了this这个伪数组哇; set.push.apply( set, this.toArray() ); return this.pushStack( set, "before", arguments ); }; }, //内部也调用了domManip after: function() { if ( this[0] && this[0].parentNode ) { return this.domManip(arguments, false, function( elem ) { this.parentNode.insertBefore( elem, this.nextSibling ); }); //没有parentNode情况就是在内存中,不在页面里面 } else if ( arguments.length ) { var set = this.pushStack( this, "after", arguments ); set.push.apply( set, jQuery(arguments[0]).toArray() ); return set; }; }, // keepData is for internal use only--do not document remove: function( selector, keepData ) { for ( var i = 0, elem; (elem = this[i]) != null; i++ ) { //没有selector就是把当前的给删了, 语义上应该是要匹配selector元素,把匹配到的删了 //都是里面没有对selector处理, 没啥用啊,我勒个去; if ( !selector || jQuery.filter( selector, [ elem ] ).length ) { if ( !keepData && elem.nodeType === 1 ) { //删除缓存数据 jQuery.cleanData( elem.getElementsByTagName("*") ); jQuery.cleanData( [ elem ] ); }; //删除元素; if ( elem.parentNode ) { elem.parentNode.removeChild( elem ); }; } } //链式调用; return this; }, empty: function() { //迭代当前元素; for ( var i = 0, elem; (elem = this[i]) != null; i++ ) { //删除内部元素的数据 // Remove element nodes and prevent memory leaks if ( elem.nodeType === 1 ) { jQuery.cleanData( elem.getElementsByTagName("*") ); }; //迭代,直到把所有的元素删除,为什么不用innerHTML = "";这种写法; // Remove any remaining nodes while ( elem.firstChild ) { elem.removeChild( elem.firstChild ); }; }; return this; }, //复制缓存的data和事件, 深度复制缓存的data和数据; /* 只要有传一个参数,那么第二次参数就是和第一个参数一样; */ clone: function( dataAndEvents, deepDataAndEvents ) { dataAndEvents = dataAndEvents == null ? true : dataAndEvents; deepDataAndEvents = deepDataAndEvents == null ? dataAndEvents : deepDataAndEvents; return this.map( function () { //$("body").clone(true,true); 第一个参数是指复制这个元素的事件, 第二个参数为深层次的复制事件 return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); }); /* //少写了几行代码; this.each( function () { return jQuery.clone( this, dataAndEvents, deepDataAndEvents ); }); return this; */ }, html: function( value ) { debugger //get if ( value === undefined ) { return this[0] && this[0].nodeType === 1 ? // rinlinejQuery = / jQuery\d+="(?:\d+|null)"/gIE67DOM属性和HTML属性不分所以要replace; this[0].innerHTML.replace(rinlinejQuery, "") : null; //get // See if we can take a shortcut and just use innerHTML //rnocache = /<(?:script|object|embed|option|style)/i } else if ( typeof value === "string" && !rnocache.test( value ) && (jQuery.support.leadingWhitespace || !rleadingWhitespace.test( value )) && // rtagName = /<([\w:]+)/; /* wrapMap = { option: [ 1, "<select multiple='multiple'>", "</select>" ], legend: [ 1, "<fieldset>", "</fieldset>" ], thead: [ 1, "<table>", "</table>" ], tr: [ 2, "<table><tbody>", "</tbody></table>" ], td: [ 3, "<table><tbody><tr>", "</tr></tbody></table>" ], col: [ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ], area: [ 1, "<map>", "</map>" ], _default: [ 0, "", "" ] }; */ !wrapMap[ (rtagName.exec( value ) || ["", ""])[1].toLowerCase() ] ) { //rxhtmlTag = /<(?!area|br|col|embed|hr|img|input|link|meta|param)(([\w:]+)[^>]*)\/>/ig //非单标签的单标签给他替换成双标签了; value = value.replace(rxhtmlTag, "<$1></$2>"); //如果有传这些的标签就不走这边; //$("body").html("<tr>") ==>> !wrapMap[ (rtagName.exec( value ) || ["", ""])[1].toLowerCase() ] ) === false try { for ( var i = 0, l = this.length; i < l; i++ ) { // Remove element nodes and prevent memory leaks if ( this[i].nodeType === 1 ) { //删除事件防止内存的泄露和缓存在$.cache中的数据; jQuery.cleanData( this[i].getElementsByTagName("*") ); this[i].innerHTML = value; } } // If using innerHTML throws an exception, use the fallback method } catch(e) { this.empty().append( value ); } } else if ( jQuery.isFunction( value ) ) { //处理是函数的情况; this.each(function(i){ var self = jQuery( this ); self.html( value.call(this, i, self.html()) ); }); } else { //最后回退到这里; this.empty().append( value ); } return this; }, replaceWith: function( value ) { if ( this[0] && this[0].parentNode ) { // Make sure that the elements are removed from the DOM before they are inserted // this can help fix replacing a parent with child elements //处理是函数的情况; if ( jQuery.isFunction( value ) ) { //return的是还是this哦,不是value; return this.each(function(i) { var self = jQuery(this), old = self.html(); self.replaceWith( value.call( this, i, old ) ); }); }; //把这个元素删除, 这个元素可能在fragement中或者DOM节点上面; if ( typeof value !== "string" ) { value = jQuery( value ).detach(); }; //迭代每一个; return this.each(function() { var next = this.nextSibling, parent = this.parentNode; //删除当前, 根据元素的节点所在位置 增加新元素 jQuery( this ).remove(); if ( next ) { jQuery(next).before( value ); } else { jQuery(parent).append( value ); }; }); } else { return this.pushStack( jQuery(jQuery.isFunction(value) ? value() : value), "replaceWith", value ); } }, detach: function( selector ) { return this.remove( selector, true ); }, //通过处理传进来的是字符串的情况转化成jQ对象, 或者传进来的是函数的情况, 对传进来的参数进行统一处理,然后放到回调里面去; //如果传进来了script标签就把script标签执行了; domManip: function( args, table, callback ) { var results, first, fragment, parent, value = args[0], scripts = []; //rchecked = /checked\s*(?:[^=]|=\s*.checked.)/i; //chrome还有这个毛病.... // We can't cloneNode fragments that contain checked, in WebKit if ( !jQuery.support.checkClone && arguments.length === 3 && typeof value === "string" && rchecked.test( value ) ) { return this.each(function() { jQuery(this).domManip( args, table, callback, true ); }); }; //如果是函数就处理; if ( jQuery.isFunction(value) ) { return this.each(function(i) { var self = jQuery(this); //$("body").append(function(i,html){console.log(i+html)}),懂得自然懂哇; args[0] = value.call(this, i, table ? self.html() : undefined); self.domManip( args, table, callback ); }); }; if ( this[0] ) { parent = value && value.parentNode; //用户传进来的就是fragment; // If we're in a fragment, just use that instead of building a new one if ( jQuery.support.parentNode && parent && parent.nodeType === 11 && parent.childNodes.length === this.length ) { results = { fragment: parent }; } else { // jQuery.buildFragment( ["aa","bb"], this, scripts ); 你可以看看这个东东的用处; results = jQuery.buildFragment( args, this, scripts ); }; fragment = results.fragment; if ( fragment.childNodes.length === 1 ) { first = fragment = fragment.firstChild; } else { first = fragment.firstChild; }; //fisrt是fragment的第一个元素; if ( first ) { table = table && jQuery.nodeName( first, "tr" ); //又是执行回调了; for ( var i = 0, l = this.length, lastIndex = l - 1; i < l; i++ ) { callback.call( table ? //如果传进来的是tr元素就进行特殊处理; root(this[i], first) : this[i], // Make sure that we do not leak memory by inadvertently discarding // the original fragment (which might have attached data) instead of // using it; in addition, use the original fragment object for the last // item instead of first because it can end up being emptied incorrectly // in certain situations (Bug #8070). // Fragments from the fragment cache must always be cloned and never used // in place. // 是将this有多个,就复制fragment, 如果多次的话, 就直接从build的cahce缓存里面读取; results.cacheable || (l > 1 && i < lastIndex) ? jQuery.clone( fragment, true, true ) : fragment ); } } if ( scripts.length ) { //这个会新建一script标签然后把js语句放进来,后来又会把scirpt标签删除; jQuery.each( scripts, evalScript ); } } return this; } }); function root( elem, cur ) { return jQuery.nodeName(elem, "table") ? (elem.getElementsByTagName("tbody")[0] || elem.appendChild(elem.ownerDocument.createElement("tbody"))) : elem; } function cloneCopyEvent( src, dest ) { //元素一定要是节点类型 而且元素还要有缓存的数据; if ( dest.nodeType !== 1 || !jQuery.hasData( src ) ) { return; }; var internalKey = jQuery.expando, oldData = jQuery.data( src ), curData = jQuery.data( dest, oldData ); // Switch to use the internal data object, if it exists, for the next // stage of data copying //有了jQ的缓存系统,复制事件很简单了 if ( (oldData = oldData[ internalKey ]) ) { var events = oldData.events; curData = curData[ internalKey ] = jQuery.extend({}, oldData); if ( events ) { //复制事件 delete curData.handle; curData.events = {}; //迭代手动添加事件; for ( var type in events ) { for ( var i = 0, l = events[ type ].length; i < l; i++ ) { jQuery.event.add( dest, type, events[ type ][ i ], events[ type ][ i ].data ); } } } } } //cloneFixAttributes是给IE用的; function cloneFixAttributes(src, dest) { // We do not need to do anything for non-Elements if ( dest.nodeType !== 1 ) { return; } var nodeName = dest.nodeName.toLowerCase(); // clearAttributes removes the attributes, which we don't want, // but also removes the attachEvent events, which we *do* want dest.clearAttributes(); // mergeAttributes, in contrast, only merges back on the // original attributes, not the events dest.mergeAttributes(src); // IE6-8 fail to clone children inside object elements that use // the proprietary classid attribute value (rather than the type // attribute) to identify the type of content to display if ( nodeName === "object" ) { dest.outerHTML = src.outerHTML; } else if ( nodeName === "input" && (src.type === "checkbox" || src.type === "radio") ) { // IE6-8 fails to persist the checked state of a cloned checkbox // or radio button. Worse, IE6-7 fail to give the cloned element // a checked appearance if the defaultChecked value isn't also set if ( src.checked ) { dest.defaultChecked = dest.checked = src.checked; } // IE6-7 get confused and end up setting the value of a cloned // checkbox/radio button to an empty string instead of "on" if ( dest.value !== src.value ) { dest.value = src.value; } // IE6-8 fails to return the selected option to the default selected // state when cloning options } else if ( nodeName === "option" ) { dest.selected = src.defaultSelected; // IE6-8 fails to set the defaultValue to the correct value when // cloning other types of input fields } else if ( nodeName === "input" || nodeName === "textarea" ) { dest.defaultValue = src.defaultValue; } // Event data gets referenced instead of copied if the expando // gets copied too dest.removeAttribute( jQuery.expando ); } //$.buildFragment(["<div></div>","<div></div>","<div></div>","<div></div>"]).fragment jQuery.buildFragment = function( args, nodes, scripts ) { var fragment, cacheable, cacheresults, doc = (nodes && nodes[0] ? nodes[0].ownerDocument || nodes[0] : document); // Only cache "small" (1/2 KB) HTML strings that are associated with the main document // Cloning options loses the selected state, so don't cache them // IE 6 doesn't like it when you put <object> or <embed> elements in a fragment // Also, WebKit does not clone 'checked' attributes on cloneNode, so don't cache if ( args.length === 1 && typeof args[0] === "string" && args[0].length < 512 && doc === document && args[0].charAt(0) === "<" && !rnocache.test( args[0] ) && (jQuery.support.checkClone || !rchecked.test( args[0] )) ) { //缓存不是很必须的,提高性能; cacheable = true; cacheresults = jQuery.fragments[ args[0] ]; if ( cacheresults ) { if ( cacheresults !== 1 ) { fragment = cacheresults; } } }; //主要还是调用$.clean方法 if ( !fragment ) { fragment = doc.createDocumentFragment(); jQuery.clean( args, doc, fragment, scripts ); } if ( cacheable ) { jQuery.fragments[ args[0] ] = cacheresults ? fragment : 1; } return { fragment: fragment, cacheable: cacheable }; }; jQuery.fragments = {}; jQuery.each({ appendTo: "append", prependTo: "prepend", insertBefore: "before", insertAfter: "after", replaceAll: "replaceWith" }, function( name, original ) { jQuery.fn[ name ] = function( selector ) { var ret = [], //转化为jQ元素; insert = jQuery( selector ), //this当前元素 parent = this.length === 1 && this[0].parentNode; //如果有当前元 if ( parent && parent.nodeType === 11 && parent.childNodes.length === 1 && insert.length === 1 ) { insert[ original ]( this[0] ); return this; } else { //走这边就是fragment的情况 for ( var i = 0, l = insert.length; i < l; i++ ) { var elems = (i > 0 ? this.clone(true) : this).get(); jQuery( insert[i] )[ original ]( elems ); ret = ret.concat( elems ); }; return this.pushStack( ret, name, insert.selector ); } }; }); jQuery.extend({ clone: function( elem, dataAndEvents, deepDataAndEvents ) { var clone = elem.cloneNode(true), srcElements, destElements, i; //jQuery.support.noCloneEvent标准浏览器是不复制事件的,IE浏览器就会走这里面; //特别处理IE会复制event, 并且如果把clone的绑定的事件删除, 原来的事件也没有了; if ( !jQuery.support.noCloneEvent && (elem.nodeType === 1 || elem.nodeType === 11) && !jQuery.isXMLDoc(elem) ) { // IE copies events bound via attachEvent when using cloneNode. // Calling detachEvent on the clone will also remove the events // from the original. In order to get around this, we use some // proprietary methods to clear the events. Thanks to MooTools // guys for this hotness. // Using Sizzle here is crazy slow, so we use getElementsByTagName // instead; srcElements = elem.getElementsByTagName("*"); destElements = clone.getElementsByTagName("*"); // Weird iteration because IE will replace the length property // with an element if you are cloning the body and one of the // elements on the page has a name or id of "length" // ie的问题 , name或者id的值是"length"; for ( i = 0; srcElements[i]; ++i ) { cloneFixAttributes( srcElements[i], destElements[i] ); }; cloneFixAttributes( elem, clone ); } // Copy the events from the original to the clone if ( dataAndEvents ) { cloneCopyEvent( elem, clone ); if ( deepDataAndEvents && "getElementsByTagName" in elem ) { srcElements = elem.getElementsByTagName("*"); destElements = clone.getElementsByTagName("*"); if ( srcElements.length ) { for ( i = 0; srcElements[i]; ++i ) { cloneCopyEvent( srcElements[i], destElements[i] ); } } } } // Return the cloned set return clone; }, //jQuery.clean("111") ==》》 ["1", "1", "1"]; //jQuery.clean(["bbb"]) ==>> ["bbb"]; //jQuery.clean(["<td></td>"]) ==>> [<td></td>] DOM元素出来了; /* $.clean(["<div></div><script>console.log(1)</script>"],null, document.createDocumentFragment(),sc=[]); ==>> [<div></div>,<script>console.log(1)</script>] ==>> sc === [<script>console.log(1)</script>] */ /* $.clean(["<div><script>console.log(0)</script></div><script>console.log(1)</script>"],null, document.createDocumentFragment(),sc=[]); ==>> [<div></div>, <script>console.log(0)</script>, <script>console.log(1)</script>]; */ clean: function( elems, context, fragment, scripts ) { context = context || document; // !context.createElement fails in IE with an error but returns typeof 'object' if ( typeof context.createElement === "undefined" ) { context = context.ownerDocument || context[0] && context[0].ownerDocument || document; } //需要返回的值 var ret = []; //循环传进来的元素,你也可以传字符串, 但是没啥效果; for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) { if ( typeof elem === "number" ) { elem += ""; } if ( !elem ) { continue; } //这个if Else是处理忠于用户输入的问题; // rhtml = /<|&#?\w+;/; // Convert html string into DOM nodes if ( typeof elem === "string" && !rhtml.test( elem ) ) { elem = context.createTextNode( elem ); } else if ( typeof elem === "string" ) { //debugger; // Fix "XHTML"-style tags in all browsers elem = elem.replace(rxhtmlTag, "<$1></$2>"); // Trim whitespace, otherwise indexOf won't work as expected var tag = (rtagName.exec( elem ) || ["", ""])[1].toLowerCase(), wrap = wrapMap[ tag ] || wrapMap._default, depth = wrap[0], div = context.createElement("div"); // 生成节点,如果你jQuery.clean(["<td></td>"])会变成这样 // "<table><tbody><tr><td></td></tr></tbody></table>" // Go to html and back, then peel off extra wrappers div.innerHTML = wrap[1] + elem + wrap[2]; // 获取正确的DOM深度, 获取正确的元素; // Move to the right depth while ( depth-- ) { div = div.lastChild; }; // IE会自己新加table到这个元素; // Remove IE's autoinserted <tbody> from table fragments if ( !jQuery.support.tbody ) { // String was a <table>, *may* have spurious <tbody> var hasBody = rtbody.test(elem), tbody = tag === "table" && !hasBody ? div.firstChild && div.firstChild.childNodes : // String was a bare <thead> or <tfoot> wrap[1] === "<table>" && !hasBody ? div.childNodes : []; for ( var j = tbody.length - 1; j >= 0 ; --j ) { if ( jQuery.nodeName( tbody[ j ], "tbody" ) && !tbody[ j ].childNodes.length ) { tbody[ j ].parentNode.removeChild( tbody[ j ] ); } } } //IE会自己处理掉开头的空格; // IE completely kills leading whitespace when innerHTML is used if ( !jQuery.support.leadingWhitespace && rleadingWhitespace.test( elem ) ) { div.insertBefore( context.createTextNode( rleadingWhitespace.exec(elem)[0] ), div.firstChild ); } //div是目前暂时用的一个容器; elem = div.childNodes; }; //如果是单个元素, 就直接push进去,优化 if ( elem.nodeType ) { ret.push( elem ); } else { //多个使用merge合并; ret = jQuery.merge( ret, elem ); } } //如果有传fragment进来就把结果result放进来,有script就把script放进来; if ( fragment ) { for ( i = 0; ret[i]; i++ ) { //如果放进来的元素有script标签, 就把script提取出来到script这个数组; if ( scripts && jQuery.nodeName( ret[i], "script" ) && (!ret[i].type || ret[i].type.toLowerCase() === "text/javascript") ) { scripts.push( ret[i].parentNode ? ret[i].parentNode.removeChild( ret[i] ) : ret[i] ); } else { //如果是放进来的是除了script以外的节点元素,就把这个元素下的script标签提取出来放到ret的后面 if ( ret[i].nodeType === 1 ) { //splice( 从第几个开始, 要删除的元素, 要增加的元素); ret.splice.apply( ret, [i + 1, 0].concat(jQuery.makeArray(ret[i].getElementsByTagName("script"))) ); }; fragment.appendChild( ret[i] ); } } } return ret; }, /* jQ1.4的cleanData: function cleanData( elems ) { for ( var i = 0, elem, id; (elem = elems[i]) != null; i++ ) { if ( !jQuery.noData[elem.nodeName.toLowerCase()] && (id = elem[expando]) ) { delete jQuery.cache[ id ]; }; }; }; */ cleanData: function( elems ) { var data, id, cache = jQuery.cache, internalKey = jQuery.expando, special = jQuery.event.special, deleteExpando = jQuery.support.deleteExpando; /* jQuery.noData == {embed: true, object: "clsid:D27CDB6E-AE6D-11cf-96B8-444553540000", applet: true}; */ for ( var i = 0, elem; (elem = elems[i]) != null; i++ ) { if ( elem.nodeName && jQuery.noData[elem.nodeName.toLowerCase()] ) { continue; } id = elem[ jQuery.expando ]; if ( id ) { data = cache[ id ] && cache[ id ][ internalKey ]; //不但要删events还要删除,事件,避免元素删除了,事件还在, 减少不必要的内存占用; if ( data && data.events ) { for ( var type in data.events ) { if ( special[ type ] ) { jQuery.event.remove( elem, type ); // This is a shortcut to avoid jQuery.event.remove's overhead } else { jQuery.removeEvent( elem, type, data.handle ); } } //又是内存泄漏的问题; // Null the DOM reference to avoid IE6/7/8 leak (#7054) if ( data.handle ) { data.handle.elem = null; } } //IE的兼容; if ( deleteExpando ) { delete elem[ jQuery.expando ]; } else if ( elem.removeAttribute ) { elem.removeAttribute( jQuery.expando ); } //缓存的id删除; delete cache[ id ]; } } } }); //新建scirpt标签, 或者直接加载远程的js文件, 然后删除; function evalScript( i, elem ) { if ( elem.src ) { jQuery.ajax({ url: elem.src, async: false, dataType: "script" }); } else { jQuery.globalEval( elem.text || elem.textContent || elem.innerHTML || "" ); }; if ( elem.parentNode ) { elem.parentNode.removeChild( elem ); }; }; //ralpha和ropacity都是给IE用的; var ralpha = /alpha\([^)]*\)/i, ropacity = /opacity=([^)]*)/, //camelize; rdashAlpha = /-([a-z])/ig, rupper = /([A-Z])/g, //匹配时否包含了px; rnumpx = /^-?\d+(?:px)?$/i, //开头为什么有一个-; rnum = /^-?\d/, //解决元素隐藏的时候不能获取width或者高的情况; cssShow = { position: "absolute", visibility: "hidden", display: "block" }, cssWidth = [ "Left", "Right" ], cssHeight = [ "Top", "Bottom" ], curCSS, getComputedStyle, currentStyle, fcamelCase = function( all, letter ) { return letter.toUpperCase(); }; jQuery.fn.css = function( name, value ) { // Setting 'undefined' is a no-op if ( arguments.length === 2 && value === undefined ) { return this; } //debugger; //通过access统一管理参数 return jQuery.access( this, name, value, true, function( elem, name, value ) { //调用的不一样, 如果value存在就走$.style,否则通过$.css获取 return value !== undefined ? jQuery.style( elem, name, value ) : jQuery.css( elem, name ); }); }; jQuery.extend({ // Add in style property hooks for overriding the default // behavior of getting and setting a style property cssHooks: { opacity: { get: function( elem, computed ) { if ( computed ) { // We should always get a number back from opacity var ret = curCSS( elem, "opacity", "opacity" ); return ret === "" ? "1" : ret; } else { return elem.style.opacity; } } } }, // Exclude the following css properties to add px cssNumber: { "zIndex": true, "fontWeight": true, "opacity": true, "zoom": true, "lineHeight": true }, // Add in properties whose names you wish to fix before // setting or getting the value cssProps: { // normalize float css property "float": jQuery.support.cssFloat ? "cssFloat" : "styleFloat" }, // Get and set the style property on a DOM Node style: function( elem, name, value, extra ) { // Don't set styles on text and comment nodes if ( !elem || elem.nodeType === 3 || elem.nodeType === 8 || !elem.style ) { return; } // Make sure that we're working with the right name var ret, origName = jQuery.camelCase( name ), //直接获取elem.style样式属性; style = elem.style, hooks = jQuery.cssHooks[ origName ]; //修正属性,如果$("body").style("float","xx") 那么名字会根据浏览器进行修正; name = jQuery.cssProps[ origName ] || origName; // Check if we're setting a value if ( value !== undefined ) { // Make sure that NaN and null values aren't set. See: #7116 if ( typeof value === "number" && isNaN( value ) || value == null ) { return; } // If a number was passed in, add 'px' to the (except for certain CSS properties) if ( typeof value === "number" && !jQuery.cssNumber[ origName ] ) { value += "px"; } /* 如果是宽高和透明度的情况就走hook; { height : {get:fn,set:fn}, width : {get:fn,set:fn}, opacity : {get:fn,set:fn} } */ // If a hook was provided, use that value, otherwise just set the specified value if ( !hooks || !("set" in hooks) || (value = hooks.set( elem, value )) !== undefined ) { // Wrapped to prevent IE from throwing errors when 'invalid' values are provided // Fixes bug #5509 try { style[ name ] = value; } catch(e) {} }; /* //这段话也可以写成; if ( !hooks || !("set" in hooks) ) { var value = hooks.set( elem, value ); if( value == undefined ) { try { style[ name ] = value; } catch(e) {}; }; }else{ try { style[ name ] = value; } catch(e) {}; }; */ } else { // If a hook was provided get the non-computed value from there if ( hooks && "get" in hooks && (ret = hooks.get( elem, false, extra )) !== undefined ) { return ret; } /* //这段话也可以写成; if ( hooks && "get" in hooks ) { ret = hooks.get( elem, false, extra ); if( ret == undefined ) { return style[ name ]; } }; return style[ name ]; */ // Otherwise just get the value from the style object return style[ name ]; } }, css: function( elem, name, extra ) { //这个和style一样, 如果有 // Make sure that we're working with the right name var ret, origName = jQuery.camelCase( name ), hooks = jQuery.cssHooks[ origName ]; name = jQuery.cssProps[ origName ] || origName; //如果hooks里面有get 就走hooks的get, 否则通过curCSS获取样式; // If a hook was provided get the computed value from there if ( hooks && "get" in hooks && (ret = hooks.get( elem, true, extra )) !== undefined ) { return ret; // Otherwise, if a way to get the computed value exists, use that } else if ( curCSS ) { //curCSS = getComputedStyle || currentStyle return curCSS( elem, name, origName ); }; }, //隐藏的元素无法获取widthHeight或者其他的属性; // A method for quickly swapping in/out CSS properties to get correct calculations swap: function( elem, options, callback ) { var old = {}; // Remember the old values, and insert the new ones for ( var name in options ) { old[ name ] = elem.style[ name ]; elem.style[ name ] = options[ name ]; } callback.call( elem ); // Revert the old values for ( name in options ) { elem.style[ name ] = old[ name ]; } }, camelCase: function( string ) { return string.replace( rdashAlpha, fcamelCase ); } }); // DEPRECATED, Use jQuery.css() instead jQuery.curCSS = jQuery.css; //减少代码量; jQuery.each(["height", "width"], function( i, name ) { jQuery.cssHooks[ name ] = { get: function( elem, computed, extra ) { var val; //computed一定要是真的,否则就根本不走; if ( computed ) { if ( elem.offsetWidth !== 0 ) { val = getWH( elem, name, extra ); } else { jQuery.swap( elem, cssShow, function() { val = getWH( elem, name, extra ); }); } // val < 0是什么情况; if ( val <= 0 ) { val = curCSS( elem, name, name ); if ( val === "0px" && currentStyle ) { val = currentStyle( elem, name, name ); }; if ( val != null ) { // Should return "auto" instead of 0, use 0 for // temporary backwards-compat return val === "" || val === "auto" ? "0px" : val; }; }; if ( val < 0 || val == null ) { val = elem.style[ name ]; // Should return "auto" instead of 0, use 0 for // temporary backwards-compat return val === "" || val === "auto" ? "0px" : val; }; return typeof val === "string" ? val : val + "px"; } }, set: function( elem, value ) { if ( rnumpx.test( value ) ) { // ignore negative width and height values #1599 value = parseFloat(value); if ( value >= 0 ) { return value + "px"; } } else { return value; } } }; }); //IE的jQuery.support.opacity才存在; if ( !jQuery.support.opacity ) { jQuery.cssHooks.opacity = { get: function( elem, computed ) { // IE uses filters for opacity // IE的测试的滤镜给他检测出来除以一百,否则根据computed返回1或者0 return ropacity.test((computed && elem.currentStyle ? elem.currentStyle.filter : elem.style.filter) || "") ? (parseFloat(RegExp.$1) / 100) + "" : computed ? "1" : ""; }, set: function( elem, value ) { var style = elem.style; // IE has trouble with opacity if it does not have layout // Force it by setting the zoom level style.zoom = 1; // Set the alpha filter to set the opacity var opacity = jQuery.isNaN(value) ? "" : "alpha(opacity=" + value * 100 + ")", filter = style.filter || ""; style.filter = ralpha.test(filter) ? filter.replace(ralpha, opacity) : style.filter + ' ' + opacity; } }; } //标志浏览器的获取计算后样式 if ( document.defaultView && document.defaultView.getComputedStyle ) { getComputedStyle = function( elem, newName, name ) { var ret, defaultView, computedStyle; name = name.replace( rupper, "-$1" ).toLowerCase(); if ( !(defaultView = elem.ownerDocument.defaultView) ) { return undefined; } if ( (computedStyle = defaultView.getComputedStyle( elem, null )) ) { ret = computedStyle.getPropertyValue( name ); //如果元素不含在document里面的话, 就通过style获取样式, 说明如果元素如果不在document里面的话, 元素的计算后样式获取不到 /* var div = document.createElement("div") getComputedStyle(div)["display"] ==>> null; div.style.height = 100 getComputedStyle(div)["height"] null div.style.height ==》》 100px */ if ( ret === "" && !jQuery.contains( elem.ownerDocument.documentElement, elem ) ) { ret = jQuery.style( elem, name ); } } return ret; }; } if ( document.documentElement.currentStyle ) { //IE获取样式的兼容; currentStyle = function( elem, name ) { var left, ret = elem.currentStyle && elem.currentStyle[ name ], rsLeft = elem.runtimeStyle && elem.runtimeStyle[ name ], style = elem.style; // From the awesome hack by Dean Edwards // http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291 // If we're not dealing with a regular pixel number // but a number that has a weird ending, we need to convert it to pixels if ( !rnumpx.test( ret ) && rnum.test( ret ) ) { // Remember the original values left = style.left; // Put in the new values to get a computed value out if ( rsLeft ) { elem.runtimeStyle.left = elem.currentStyle.left; } style.left = name === "fontSize" ? "1em" : (ret || 0); ret = style.pixelLeft + "px"; // Revert the changed values style.left = left; if ( rsLeft ) { elem.runtimeStyle.left = rsLeft; } } return ret === "" ? "auto" : ret; }; } curCSS = getComputedStyle || currentStyle; /* cssShow = { position: "absolute", visibility: "hidden", display: "block" }, cssWidth = [ "Left", "Right" ], cssHeight = [ "Top", "Bottom" ], */ function getWH( elem, name, extra ) { var which = name === "width" ? cssWidth : cssHeight, val = name === "width" ? elem.offsetWidth : elem.offsetHeight; //如果是border-box就返回offsetWidth或者offsetHeight; if ( extra === "border" ) { return val; }; jQuery.each( which, function() { //不传extra获取是padding-box; if ( !extra ) { val -= parseFloat(jQuery.css( elem, "padding" + this )) || 0; }; //传margin是offset + margin的值; if ( extra === "margin" ) { val += parseFloat(jQuery.css( elem, "margin" + this )) || 0; } else { //最后的情况是传进来的true, val要减去padding 在减去border; val -= parseFloat(jQuery.css( elem, "border" + this + "Width" )) || 0; } }); return val; }; //jQuery.expr是sizzle里面的东东; if ( jQuery.expr && jQuery.expr.filters ) { jQuery.expr.filters.hidden = function( elem ) { var width = elem.offsetWidth, height = elem.offsetHeight; return (width === 0 && height === 0) || (!jQuery.support.reliableHiddenOffsets && (elem.style.display || jQuery.css( elem, "display" )) === "none"); }; jQuery.expr.filters.visible = function( elem ) { return !jQuery.expr.filters.hidden( elem ); }; } //匹配URL的那种空格; var r20 = /%20/g, rbracket = /\[\]$/, //回车加换行,或者单单回车(for mac); rCRLF = /\r?\n/g, //是否有# rhash = /#.*$/, rheaders = /^(.*?):\s*(.*?)\r?$/mg, // IE leaves an \r character at EOL rinput = /^(?:color|date|datetime|email|hidden|month|number|password|range|search|tel|text|time|url|week)$/i, rnoContent = /^(?:GET|HEAD)$/, rprotocol = /^\/\//, rquery = /\?/, // "<div>11</div><script>console.log(1)</script><div>11</div>".match(/<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi); // <script>console.log(1)</script>会被匹配出来; rscript = /<script\b[^<]*(?:(?!<\/script>)<[^<]*)*<\/script>/gi, rselectTextarea = /^(?:select|textarea)/i, rspacesAjax = /\s+/, rts = /([?&])_=[^&]*/, rurl = /^(\w+:)\/\/([^\/?#:]+)(?::(\d+))?/, // Keep a copy of the old load method _load = jQuery.fn.load, /* Prefilters * 1) They are useful to introduce custom dataTypes (see ajax/jsonp.js for an example) * 2) These are called: * - BEFORE asking for a transport * - AFTER param serialization (s.data is a string if s.processData is true) * 3) key is the dataType * 4) the catchall symbol "*" can be used * 5) execution will start with transport dataType and THEN continue down to "*" if needed */ prefilters = {}, /* Transports bindings * 1) key is the dataType * 2) the catchall symbol "*" can be used * 3) selection will start with transport dataType and THEN go to "*" if needed */ transports = {}; // Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport // prefilters or transports; // "json jsonp" "script" "script" XML请求包装函数; function addToPrefiltersOrTransports( structure ) { // 又是一个闭包; // dataTypeExpression is optional and defaults to "*" return function( dataTypeExpression, func ) { //debugger; if ( typeof dataTypeExpression !== "string" ) { func = dataTypeExpression; dataTypeExpression = "*"; } if ( jQuery.isFunction( func ) ) { // rspacesAjax = /\s+/; var dataTypes = dataTypeExpression.toLowerCase().split( rspacesAjax ), i = 0, length = dataTypes.length, dataType, list, placeBefore; // For each dataType in the dataTypeExpression // json jsonp script 或者是 *; for(; i < length; i++ ) { dataType = dataTypes[ i ]; // We control if we're asked to add before // any existing element // 可能dataTypes是这样的 +json jsonp; 那么这个placeBefore就是ture, 这个回调会被放到了list最前排; placeBefore = /^\+/.test( dataType ); if ( placeBefore ) { dataType = dataType.substr( 1 ) || "*"; } list = structure[ dataType ] = structure[ dataType ] || []; // then we add to the structure accordingly // 保存回调方法; list[ placeBefore ? "unshift" : "push" ]( func ); } } }; } //Base inspection function for prefilters and transports function inspectPrefiltersOrTransports( structure, options, originalOptions, jXHR, dataType /* internal */, inspected /* internal */ ) { dataType = dataType || options.dataTypes[ 0 ]; inspected = inspected || {}; inspected[ dataType ] = true; var list = structure[ dataType ], i = 0, length = list ? list.length : 0, executeOnly = ( structure === prefilters ), selection; for(; i < length && ( executeOnly || !selection ); i++ ) { selection = list[ i ]( options, originalOptions, jXHR ); // If we got redirected to another dataType // we try there if not done already if ( typeof selection === "string" ) { if ( inspected[ selection ] ) { selection = undefined; } else { options.dataTypes.unshift( selection ); selection = inspectPrefiltersOrTransports( structure, options, originalOptions, jXHR, selection, inspected ); } } } // If we're only executing or nothing was selected // we try the catchall dataType if not done already if ( ( executeOnly || !selection ) && !inspected[ "*" ] ) { selection = inspectPrefiltersOrTransports( structure, options, originalOptions, jXHR, "*", inspected ); } // unnecessary when only executing (prefilters) // but it'll be ignored by the caller in that case return selection; } jQuery.fn.extend({ load: function( url, params, callback ) { if ( typeof url !== "string" && _load ) { return _load.apply( this, arguments ); // Don't do a request if no elements are being requested } else if ( !this.length ) { return this; } var off = url.indexOf( " " ); if ( off >= 0 ) { var selector = url.slice( off, url.length ); url = url.slice( 0, off ); } // Default to a GET request var type = "GET"; // If the second parameter was provided if ( params ) { // If it's a function if ( jQuery.isFunction( params ) ) { // We assume that it's the callback callback = params; params = null; // Otherwise, build a param string } else if ( typeof params === "object" ) { params = jQuery.param( params, jQuery.ajaxSettings.traditional ); type = "POST"; } } var self = this; // Request the remote document jQuery.ajax({ url: url, type: type, dataType: "html", data: params, // Complete callback (responseText is used internally) complete: function( jXHR, status, responseText ) { // Store the response as specified by the jXHR object responseText = jXHR.responseText; // If successful, inject the HTML into all the matched elements if ( jXHR.isResolved() ) { // #4825: Get the actual response in case // a dataFilter is present in ajaxSettings jXHR.done(function( r ) { responseText = r; }); // See if a selector was specified self.html( selector ? // Create a dummy div to hold the results jQuery("<div>") // inject the contents of the document in, removing the scripts // to avoid any 'Permission Denied' errors in IE .append(responseText.replace(rscript, "")) // Locate the specified elements .find(selector) : // If not, just inject the full result responseText ); } if ( callback ) { self.each( callback, [ responseText, status, jXHR ] ); } } }); return this; }, /* <form id="form" action="form"> <input type="text" name="ipt0" id="ipt0" value="111"/> <input type="text" name="ipt1" id="ipt1" value="222"/> </form> ==>> $("#form").serializeArray(); */ serialize: function() { return jQuery.param( this.serializeArray() ); }, serializeArray: function() { return this.map(function(){ return this.elements ? jQuery.makeArray( this.elements ) : this; }) .filter(function(){ return this.name && !this.disabled && ( this.checked || rselectTextarea.test( this.nodeName ) || rinput.test( this.type ) ); }) .map(function( i, elem ){ var val = jQuery( this ).val(); return val == null ? null : jQuery.isArray( val ) ? jQuery.map( val, function( val, i ){ return { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; }) : { name: elem.name, value: val.replace( rCRLF, "\r\n" ) }; }).get(); } }); // Attach a bunch of functions for handling common AJAX events // 利用闭包减少代码量; jQuery.each( "ajaxStart ajaxStop ajaxComplete ajaxError ajaxSuccess ajaxSend".split( " " ), function( i, o ){ jQuery.fn[ o ] = function( f ){ return this.bind( o, f ); }; } ); jQuery.each( [ "get", "post" ], function( i, method ) { jQuery[ method ] = function( url, data, callback, type ) { // shift arguments if data argument was omitted if ( jQuery.isFunction( data ) ) { type = type || callback; callback = data; data = null; } return jQuery.ajax({ type: method, url: url, data: data, success: callback, dataType: type }); }; } ); jQuery.extend({ getScript: function( url, callback ) { return jQuery.get( url, null, callback, "script" ); }, getJSON: function( url, data, callback ) { return jQuery.get( url, data, callback, "json" ); }, ajaxSetup: function( settings ) { /* setting : { jsonp : "callback", jsonpCallback : fn }, setting : { accepts : { script : "text/javascript, application/javascript" }, contents : { script : /javascript/ <<==是一个正则; }, converters : function( text ) { jQuery.globalEval( text ); return text; } } */ //debugger; jQuery.extend( true, jQuery.ajaxSettings, settings ); if ( settings.context ) { jQuery.ajaxSettings.context = settings.context; } }, ajaxSettings: { url: location.href, global: true, type: "GET", contentType: "application/x-www-form-urlencoded", processData: true, async: true, /* timeout: 0, data: null, dataType: null, username: null, password: null, cache: null, traditional: false, headers: {}, crossDomain: null, */ accepts: { xml: "application/xml, text/xml", html: "text/html", text: "text/plain", json: "application/json, text/javascript", "*": "*/*" }, contents: { xml: /xml/, html: /html/, json: /json/ }, responseFields: { xml: "responseXML", text: "responseText" }, // List of data converters // 1) key format is "source_type destination_type" (a single space in-between) // 2) the catchall symbol "*" can be used for source_type converters: { // Convert anything to text "* text": window.String, // Text to html (true = no transformation) "text html": true, // Evaluate text as a json expression "text json": jQuery.parseJSON, // Parse text as xml "text xml": jQuery.parseXML } }, //预传送器, 后面会初始化json和jsonp(包括回调的处理等), 标准xml的预传送器; ajaxPrefilter: addToPrefiltersOrTransports( prefilters ), //标准传送器, 包括ajax的传送器的执行操作, jsonp中需要添加script标签到界面的操作; ajaxTransport: addToPrefiltersOrTransports( transports ), // file///:C/本地协议的文件; // Main method /* cb = function(arg){console.log(arg)}; var url = "http://www.filltext.com/?callback=?"; $.getJSON( url, { 'callback' : "cb", 'rows': 5, 'fname': '{firstName}', 'lname': '{lastName}', 'tel': '{phone|format}', }); */ ajax: function( url, options ) { // If options is not an object, // we simulate pre-1.5 signature if ( typeof options !== "object" ) { options = url; url = undefined; } // Force options to be an object options = options || {}; var // Create the final options object s = jQuery.extend( true, {}, jQuery.ajaxSettings, options ), // Callbacks contexts // We force the original context if it exists // or take it from jQuery.ajaxSettings otherwise // (plain objects used as context get extended) callbackContext = ( s.context = ( "context" in options ? options : jQuery.ajaxSettings ).context ) || s, globalEventContext = callbackContext === s ? jQuery.event : jQuery( callbackContext ), // Deferreds deferred = jQuery.Deferred(), completeDeferred = jQuery._Deferred(), // Status-dependent callbacks statusCode = s.statusCode || {}, // Headers (they are sent all at once) requestHeaders = {}, // Response headers responseHeadersString, responseHeaders, // transport transport, // timeout handle timeoutTimer, // Cross-domain detection vars loc = document.location, protocol = loc.protocol || "http:", parts, // The jXHR state state = 0, // Loop variable i, //假的XMLHttpRequest, 因为xml的属性 不兼容, 为xhr包裹一层, 方便统一管理; // Fake xhr jXHR = { readyState: 0, // Caches the header setRequestHeader: function( name, value ) { if ( state === 0 ) { requestHeaders[ name.toLowerCase() ] = value; } return this; }, // Raw string getAllResponseHeaders: function() { return state === 2 ? responseHeadersString : null; }, // Builds headers hashtable if needed getResponseHeader: function( key ) { var match; if ( state === 2 ) { if ( !responseHeaders ) { responseHeaders = {}; while( ( match = rheaders.exec( responseHeadersString ) ) ) { responseHeaders[ match[1].toLowerCase() ] = match[ 2 ]; } } match = responseHeaders[ key.toLowerCase() ]; } return match || null; }, // Cancel the request abort: function( statusText ) { statusText = statusText || "abort"; if ( transport ) { transport.abort( statusText ); } done( 0, statusText ); return this; } }; // Callback for when everything is done // It is defined here because jslint complains if it is declared // at the end of the function (which would be more logical and readable) function done( status, statusText, responses, headers) { // Called once if ( state === 2 ) { return; } // State is "done" now state = 2; // Clear timeout if it exists if ( timeoutTimer ) { clearTimeout( timeoutTimer ); } // Dereference transport for early garbage collection // (no matter how long the jXHR object will be used) transport = undefined; // Cache response headers responseHeadersString = headers || ""; // Set readyState jXHR.readyState = status ? 4 : 0; var isSuccess, success, error, response = responses ? ajaxHandleResponses( s, jXHR, responses ) : undefined, lastModified, etag; // If successful, handle type chaining if ( status >= 200 && status < 300 || status === 304 ) { // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. if ( s.ifModified ) { if ( ( lastModified = jXHR.getResponseHeader( "Last-Modified" ) ) ) { jQuery.lastModified[ s.url ] = lastModified; } if ( ( etag = jXHR.getResponseHeader( "Etag" ) ) ) { jQuery.etag[ s.url ] = etag; }; }; // If not modified if ( status === 304 ) { statusText = "notmodified"; isSuccess = true; // If we have data } else { try { success = ajaxConvert( s, response ); statusText = "success"; isSuccess = true; } catch(e) { // We have a parsererror statusText = "parsererror"; error = e; }; }; } else { // We extract error from statusText // then normalize statusText and status for non-aborts error = statusText; if( status ) { statusText = "error"; if ( status < 0 ) { status = 0; } } }; // Set data for the fake xhr object jXHR.status = status; jXHR.statusText = statusText; // Success/Error if ( isSuccess ) { debugger; deferred.resolveWith( callbackContext, [ success, statusText, jXHR ] ); } else { deferred.rejectWith( callbackContext, [ jXHR, statusText, error ] ); } // Status-dependent callbacks jXHR.statusCode( statusCode ); statusCode = undefined; if ( s.global ) { globalEventContext.trigger( "ajax" + ( isSuccess ? "Success" : "Error" ), [ jXHR, s, isSuccess ? success : error ] ); } // Complete completeDeferred.resolveWith( callbackContext, [ jXHR, statusText ] ); if ( s.global ) { globalEventContext.trigger( "ajaxComplete", [ jXHR, s] ); // Handle the global AJAX counter if ( !( --jQuery.active ) ) { jQuery.event.trigger( "ajaxStop" ); } } }; //var def = {}; $.Deferred().promise(def) 把这个对象送到promise会为着对象继承(非真正意义上的继承,只是复制了属性而已)一个延迟对象的实例; // Attach deferreds deferred.promise( jXHR ); jXHR.success = jXHR.done; jXHR.error = jXHR.fail; jXHR.complete = completeDeferred.done; // Status-dependent callbacks jXHR.statusCode = function( map ) { if ( map ) { var tmp; if ( state < 2 ) { for( tmp in map ) { statusCode[ tmp ] = [ statusCode[tmp], map[tmp] ]; } } else { tmp = map[ jXHR.status ]; jXHR.then( tmp, tmp ); } } return this; }; //变量初始化完毕; // Remove hash character (#7531: and string promotion) // Add protocol if not provided (#5866: IE7 issue with protocol-less urls) // We also use the url parameter if available //去除空格; // rprotocol = /^\/\// 如果协议头是//就改成 ==》 当前协议头+// s.url = ( "" + ( url || s.url ) ).replace( rhash, "" ).replace( rprotocol, protocol + "//" ); // Extract dataTypes list // rspacesAjax = /\s+/; //把用户期望的返回类型保存起来,方便回调; s.dataTypes = jQuery.trim( s.dataType || "*" ).toLowerCase().split( rspacesAjax ); //判断是否跨域了哇; // Determine if a cross-domain request is in order if ( !s.crossDomain ) { parts = rurl.exec( s.url.toLowerCase() ); s.crossDomain = !!( parts && ( parts[ 1 ] != protocol || parts[ 2 ] != loc.hostname || ( parts[ 3 ] || ( parts[ 1 ] === "http:" ? 80 : 443 ) ) != ( loc.port || ( protocol === "http:" ? 80 : 443 ) ) ) ); }; // Convert data if not already a string if ( s.data && s.processData && typeof s.data !== "string" ) { //把json的数据转化成通过&拼接的数据; s.data = jQuery.param( s.data, s.traditional ); }; // Apply prefilters //prefilters = {JSON : fn, JSONP : fn, SCRIPT : fn}; //根据setting的dataType类型设置预处理的参数; inspectPrefiltersOrTransports( prefilters, s, options, jXHR ); // Uppercase the type s.type = s.type.toUpperCase(); // Determine if request has content // /^(?:GET|HEAD)$/ 没有发送数据就是没有content; s.hasContent = !rnoContent.test( s.type ); // Watch for a new set of requests //false // jQuery.active 目前等于 0; if ( s.global && jQuery.active++ === 0 ) { jQuery.event.trigger( "ajaxStart" ); }; // More options handling for requests with no content if ( !s.hasContent ) { // If data is available, append data to url if ( s.data ) { // s.url ==>> "http://www.filltext.com/?callback=jQuery15003316175821237266_1420601952773" // s.data ==>> "callback=cb&rows=5&fname=%7BfirstName%7D&lname=%7BlastName%7D&tel=%7Bphone%7Cformat%7D" s.url += ( rquery.test( s.url ) ? "&" : "?" ) + s.data; //jsonp会改变callback为jQ自己定义的callback然后在执行成功的时候才执行用户传进来的callback; //最后变成了 ==>> http://www.filltext.com/?callback=jQuery15003316175821237266_1420601952773&callback=cb&rows=5&fname=%7BfirstName%7D&lname=%7BlastName%7D&tel=%7Bphone%7Cformat%7D }; // Add anti-cache in url if needed if ( s.cache === false ) { var ts = jQuery.now(), // try replacing _= if it is there; // rts = /([?&])_=[^&]*/; 把 ?_=替换成?=times 或者是 #_=替换成#_=times, 而且后面不是&, 避免出现别的错误; /* "sdfsdfdsf?_=abcd".replace(rts, "$1_="+jQuery.now()) ==>> "sdfsdfdsf?_=1420614479567" "sdfsdfdsf#_=abcd".replace(rts, "$1_="+jQuery.now()) ==>> "sdfsdfdsf#_=abcd" */ ret = s.url.replace( rts, "$1_=" + ts ); // 给最后添加一个时间戳; // if nothing was replaced, add timestamp to the end s.url = ret + ( (ret === s.url ) ? ( rquery.test( s.url ) ? "&" : "?" ) + "_=" + ts : "" ); }; }; //JSON是不走这边的; // Set the correct header, if data is being sent if ( s.data && s.hasContent && s.contentType !== false || options.contentType ) { requestHeaders[ "content-type" ] = s.contentType; } // // Set the If-Modified-Since and/or If-None-Match header, if in ifModified mode. if ( s.ifModified ) { if ( jQuery.lastModified[ s.url ] ) { requestHeaders[ "if-modified-since" ] = jQuery.lastModified[ s.url ]; } if ( jQuery.etag[ s.url ] ) { requestHeaders[ "if-none-match" ] = jQuery.etag[ s.url ]; } } // 根据用户需求的请求头, 服务器可以返回期望的类型; // Set the Accepts header for the server, depending on the dataType requestHeaders.accept = s.dataTypes[ 0 ] && s.accepts[ s.dataTypes[0] ] ? s.accepts[ s.dataTypes[0] ] + ( s.dataTypes[ 0 ] !== "*" ? ", */*; q=0.01" : "" ) : s.accepts[ "*" ]; // Check for headers option for ( i in s.headers ) { requestHeaders[ i.toLowerCase() ] = s.headers[ i ]; }; // 执行before的事件,这个是全局的; // Allow custom headers/mimetypes and early abort if ( s.beforeSend && ( s.beforeSend.call( callbackContext, jXHR, s ) === false || state === 2 ) ) { // Abort if not done already done( 0, "abort" ); // Return false jXHR = false; } else { // Install callbacks on deferreds for ( i in { success: 1, error: 1, complete: 1 } ) { //把用户的回调安装到延迟对象; //jXHR.success( s.success ); //jXHR.complete( s.complete ); //jXHR.error( s.error ); jXHR[ i ]( s[ i ] ); } // Get transport //获取传送器, 根据传送器的设置发送数据, 这个里面会执行get,post或者新增script的操作; transport = inspectPrefiltersOrTransports( transports, s, options, jXHR ); // If no transport, we auto-abort if ( !transport ) { done( -1, "No Transport" ); } else { // Set state as sending state = jXHR.readyState = 1; // Send global event // 触发全局的ajaxSend事件;参数为JXHR, s; if ( s.global ) { globalEventContext.trigger( "ajaxSend", [ jXHR, s ] ); } //开启一定定时器,如果是异步而且超时的话就触发超时的事件; // Timeout if ( s.async && s.timeout > 0 ) { timeoutTimer = setTimeout( function(){ jXHR.abort( "timeout" ); }, s.timeout ); } //JSONP的传送器有一个是send,一个是abort; try { transport.send( requestHeaders, done ); } catch (e) { //如果出了错; // Propagate exception as error if not done if ( status < 2 ) { done( -1, e ); // Simply rethrow otherwise } else { jQuery.error( e ); } } } } return jXHR; }, // Serialize an array of form elements or a set of // key/values into a query string param: function( a, traditional ) { var s = [], add = function( key, value ) { // If value is a function, invoke it and return its value value = jQuery.isFunction( value ) ? value() : value; s[ s.length ] = encodeURIComponent( key ) + "=" + encodeURIComponent( value ); }; // Set traditional to true for jQuery <= 1.3.2 behavior. if ( traditional === undefined ) { traditional = jQuery.ajaxSettings.traditional; } // If an array was passed in, assume that it is an array of form elements. if ( jQuery.isArray( a ) || a.jquery ) { // Serialize the form elements jQuery.each( a, function() { add( this.name, this.value ); } ); } else { // If traditional, encode the "old" way (the way 1.3.2 or older // did it), otherwise encode params recursively. for ( var prefix in a ) { buildParams( prefix, a[ prefix ], traditional, add ); } } // Return the resulting serialization return s.join( "&" ).replace( r20, "+" ); } }); function buildParams( prefix, obj, traditional, add ) { if ( jQuery.isArray( obj ) && obj.length ) { // Serialize array item. jQuery.each( obj, function( i, v ) { if ( traditional || rbracket.test( prefix ) ) { // Treat each array item as a scalar. add( prefix, v ); } else { // If array item is non-scalar (array or object), encode its // numeric index to resolve deserialization ambiguity issues. // Note that rack (as of 1.0.0) can't currently deserialize // nested arrays properly, and attempting to do so may cause // a server error. Possible fixes are to modify rack's // deserialization algorithm or to provide an option or flag // to force array serialization to be shallow. buildParams( prefix + "[" + ( typeof v === "object" || jQuery.isArray(v) ? i : "" ) + "]", v, traditional, add ); } }); } else if ( !traditional && obj != null && typeof obj === "object" ) { // If we see an array here, it is empty and should be treated as an empty // object if ( jQuery.isArray( obj ) || jQuery.isEmptyObject( obj ) ) { add( prefix, "" ); // Serialize object item. } else { jQuery.each( obj, function( k, v ) { buildParams( prefix + "[" + k + "]", v, traditional, add ); }); } } else { // Serialize scalar item. add( prefix, obj ); } } // This is still on the jQuery object... for now // Want to move this to jQuery.ajax some day jQuery.extend({ // Counter for holding the number of active queries active: 0, // Last-Modified header cache for next request lastModified: {}, etag: {} }); /* Handles responses to an ajax request: * - sets all responseXXX fields accordingly * - finds the right dataType (mediates between content-type and expected dataType) * - returns the corresponding response */ function ajaxHandleResponses( s, jXHR, responses ) { var contents = s.contents, dataTypes = s.dataTypes, responseFields = s.responseFields, ct, type, finalDataType, firstDataType; // Fill responseXXX fields for( type in responseFields ) { if ( type in responses ) { jXHR[ responseFields[type] ] = responses[ type ]; } } // Remove auto dataType and get content-type in the process while( dataTypes[ 0 ] === "*" ) { dataTypes.shift(); if ( ct === undefined ) { ct = jXHR.getResponseHeader( "content-type" ); } } // Check if we're dealing with a known content-type if ( ct ) { for ( type in contents ) { if ( contents[ type ] && contents[ type ].test( ct ) ) { dataTypes.unshift( type ); break; } } } // Check to see if we have a response for the expected dataType if ( dataTypes[ 0 ] in responses ) { finalDataType = dataTypes[ 0 ]; } else { // Try convertible dataTypes for ( type in responses ) { if ( !dataTypes[ 0 ] || s.converters[ type + " " + dataTypes[0] ] ) { finalDataType = type; break; } if ( !firstDataType ) { firstDataType = type; } } // Or just use first one finalDataType = finalDataType || firstDataType; } // If we found a dataType // We add the dataType to the list if needed // and return the corresponding response if ( finalDataType ) { if ( finalDataType !== dataTypes[ 0 ] ) { dataTypes.unshift( finalDataType ); } return responses[ finalDataType ]; } } // Chain conversions given the request and the original response function ajaxConvert( s, response ) { // Apply the dataFilter if provided if ( s.dataFilter ) { response = s.dataFilter( response, s.dataType ); } var dataTypes = s.dataTypes, converters = s.converters, i, length = dataTypes.length, tmp, // Current and previous dataTypes current = dataTypes[ 0 ], prev, // Conversion expression conversion, // Conversion function conv, // Conversion functions (transitive conversion) conv1, conv2; // For each dataType in the chain for( i = 1; i < length; i++ ) { // Get the dataTypes prev = current; current = dataTypes[ i ]; // If current is auto dataType, update it to prev if( current === "*" ) { current = prev; // If no auto and dataTypes are actually different } else if ( prev !== "*" && prev !== current ) { // Get the converter conversion = prev + " " + current; conv = converters[ conversion ] || converters[ "* " + current ]; // If there is no direct converter, search transitively if ( !conv ) { conv2 = undefined; for( conv1 in converters ) { tmp = conv1.split( " " ); if ( tmp[ 0 ] === prev || tmp[ 0 ] === "*" ) { conv2 = converters[ tmp[1] + " " + current ]; if ( conv2 ) { conv1 = converters[ conv1 ]; if ( conv1 === true ) { conv = conv2; } else if ( conv2 === true ) { conv = conv1; } break; } } } } // If we found no converter, dispatch an error if ( !( conv || conv2 ) ) { jQuery.error( "No conversion from " + conversion.replace(" "," to ") ); } // If found converter is not an equivalence if ( conv !== true ) { // Convert with 1 or 2 converters accordingly response = conv ? conv( response ) : conv2( conv1(response) ); } } } return response; } var jsc = jQuery.now(), jsre = /(\=)\?(&|$)|()\?\?()/i; // Default jsonp settings jQuery.ajaxSetup({ jsonp: "callback", jsonpCallback: function() { return jQuery.expando + "_" + ( jsc++ ); } }); // Detect, normalize options and install callbacks for jsonp requests jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, dataIsString /* internal */ ) { dataIsString = ( typeof s.data === "string" ); if ( s.dataTypes[ 0 ] === "jsonp" || originalSettings.jsonpCallback || originalSettings.jsonp != null || s.jsonp !== false && ( jsre.test( s.url ) || dataIsString && jsre.test( s.data ) ) ) { var responseContainer, jsonpCallback = s.jsonpCallback = jQuery.isFunction( s.jsonpCallback ) ? s.jsonpCallback() : s.jsonpCallback, previous = window[ jsonpCallback ], url = s.url, data = s.data, replace = "$1" + jsonpCallback + "$2"; if ( s.jsonp !== false ) { url = url.replace( jsre, replace ); if ( s.url === url ) { if ( dataIsString ) { data = data.replace( jsre, replace ); } if ( s.data === data ) { // Add callback manually url += (/\?/.test( url ) ? "&" : "?") + s.jsonp + "=" + jsonpCallback; } } } s.url = url; s.data = data; window[ jsonpCallback ] = function( response ) { responseContainer = [ response ]; }; s.complete = [ function() { // Set callback back to previous value window[ jsonpCallback ] = previous; // Call if it was a function and we have a response if ( previous) { if ( responseContainer && jQuery.isFunction( previous ) ) { window[ jsonpCallback ] ( responseContainer[ 0 ] ); } } else { // else, more memory leak avoidance try{ delete window[ jsonpCallback ]; } catch( e ) {} } }, s.complete ]; // Use data converter to retrieve json after script execution s.converters["script json"] = function() { if ( ! responseContainer ) { jQuery.error( jsonpCallback + " was not called" ); } return responseContainer[ 0 ]; }; // force json dataType s.dataTypes[ 0 ] = "json"; // Delegate to script return "script"; } } ); // Install script dataType jQuery.ajaxSetup({ accepts: { script: "text/javascript, application/javascript" }, contents: { script: /javascript/ }, converters: { "text script": function( text ) { jQuery.globalEval( text ); return text; } } }); // Handle cache's special case and global jQuery.ajaxPrefilter( "script", function( s ) { if ( s.cache === undefined ) { s.cache = false; } if ( s.crossDomain ) { s.type = "GET"; s.global = false; } } ); // Bind script tag hack transport //这个是跨域的传送器哇; jQuery.ajaxTransport( "script", function(s) { // This transport only deals with cross domain requests // 如果请求的url发生改变,就默认用jsonp方式(script标签)进行加载; if ( s.crossDomain ) { var script, head = document.getElementsByTagName( "head" )[ 0 ] || document.documentElement; return { send: function( _, callback ) { script = document.createElement( "script" ); //async是标准浏览器所支持的东西; //defer是IE支持的异步加载方式; script.async = "async"; //字符串编码; if ( s.scriptCharset ) { script.charset = s.scriptCharset; } //script和link标签只有在添加到dom才发送请求哇; script.src = s.url; // Attach handlers for all browsers // 事件还是先加载 script.onload = script.onreadystatechange = function( _, isAbort ) { if ( !script.readyState || /loaded|complete/.test( script.readyState ) ) { // Handle memory leak in IE // IE的内存泄露问题; script.onload = script.onreadystatechange = null; // Remove the script if ( head && script.parentNode ) { head.removeChild( script ); } // Dereference the script script = undefined; //因为是JSONP的方式, 就直接返回200的状态和 success的姿态; // Callback if not abort if ( !isAbort ) { callback( 200, "success" ); } } }; // Use insertBefore instead of appendChild to circumvent an IE6 bug. // This arises when a base node is used (#2709 and #4378). head.insertBefore( script, head.firstChild ); }, abort: function() { if ( script ) { script.onload( 0, 1 ); } } }; } } ); var // Next active xhr id xhrId = jQuery.now(), // active xhrs xhrs = {}, // #5280: see below xhrUnloadAbortInstalled, // 用来临时用的; // XHR used to determine supports properties testXHR; // Create the request object // (This is still attached to ajaxSettings for backward compatibility) jQuery.ajaxSettings.xhr = window.ActiveXObject ? /* Microsoft failed to properly * implement the XMLHttpRequest in IE7 (can't request local files), * so we use the ActiveXObject when it is available * Additionally XMLHttpRequest can be disabled in IE7/IE8 so * we need a fallback. */ function() { if ( window.location.protocol !== "file:" ) { try { return new window.XMLHttpRequest(); } catch( xhrError ) {} } try { return new window.ActiveXObject("Microsoft.XMLHTTP"); } catch( activeError ) {} } : // For all other browsers, use the standard XMLHttpRequest object function() { return new window.XMLHttpRequest(); }; // Test if we can create an xhr object try { testXHR = jQuery.ajaxSettings.xhr(); } catch( xhrCreationException ) {}; //测试是否支持XHR这个东西; //Does this browser support XHR requests? jQuery.support.ajax = !!testXHR; /* 默认情况下,跨源请求不提供凭据(cookie、HTTP认证及客户端SSL证明等)。 通过将withCredentials属性设置为true,可以指定某个请求应该发送凭据。 如果服务器接收带凭据的请求,会用下面的HTTP头部来响应。 如果发送的是带凭据的请求,但服务器的相应中没有包含这个头部, 那么浏览器就不会把相应交给JavaScript(于是,responseText中将是空字符串,status的值为0, 而且会调用onerror()事件处理程序)。 另外,服务器还可以在Preflight响应中发送这个HTTP头部, 表示允许源发送带凭据的请求。 支持withCredentials属性的浏览器有Firefox 3.5+、Safari 4+和Chrome。IE10及更早版本都不支持。 */ // 是否支持跨域直接通过withCredentials进行判断; // Does this browser support crossDomain XHR requests jQuery.support.cors = testXHR && ( "withCredentials" in testXHR ); // No need for the temporary xhr anymore testXHR = undefined; // Create transport if the browser can provide an xhr if ( jQuery.support.ajax ) { //传进来的是setting; jQuery.ajaxTransport(function( s ) { // Cross domain only allowed if supported through XMLHttpRequest if ( !s.crossDomain || jQuery.support.cors ) { var callback; return { send: function( headers, complete ) { //避免错误的 // #5280: we need to abort on unload or IE will keep connections alive if ( !xhrUnloadAbortInstalled ) { xhrUnloadAbortInstalled = 1; jQuery(window).bind( "unload", function() { // Abort all pending requests jQuery.each( xhrs, function( _, xhr ) { if ( xhr.onreadystatechange ) { xhr.onreadystatechange( 1 ); } } ); } ); } // Get a new xhr var xhr = s.xhr(), handle; // Open the socket // Passing null username, generates a login popup on Opera (#2865) if ( s.username ) { xhr.open( s.type, s.url, s.async, s.username, s.password ); } else { xhr.open( s.type, s.url, s.async ); } // Requested-With header // Not set for crossDomain requests with no content // (see why at http://trac.dojotoolkit.org/ticket/9486) // Won't change header if already provided // 设置头; if ( !( s.crossDomain && !s.hasContent ) && !headers["x-requested-with"] ) { headers[ "x-requested-with" ] = "XMLHttpRequest"; } // Need an extra try/catch for cross domain requests in Firefox 3 try { jQuery.each( headers, function( key, value ) { xhr.setRequestHeader( key, value ); } ); } catch( _ ) {}; // Do send the request // This may raise an exception which is actually // handled in jQuery.ajax (so no try/catch here) // POST或者是GET,所以要判断一下; xhr.send( ( s.hasContent && s.data ) || null ); //cancel when I use arguments (0,1 ), kind like : callback(0,1); // Listener callback = function( _, isAbort ) { // Was never called and is aborted or complete if ( callback && ( isAbort || xhr.readyState === 4 ) ) { //执行成功了就不用了; // Only called once callback = 0; // Do not keep as active anymore if ( handle ) { xhr.onreadystatechange = jQuery.noop; delete xhrs[ handle ]; } // If it's an abort //cance和send放在一起, 一个接口, 多个使用; if ( isAbort ) { // Abort it manually if needed if ( xhr.readyState !== 4 ) { xhr.abort(); } } else { // Get info var status = xhr.status, statusText, responseHeaders = xhr.getAllResponseHeaders(), responses = {}, xml = xhr.responseXML; // Construct response list if ( xml && xml.documentElement /* #4958 */ ) { responses.xml = xml; } responses.text = xhr.responseText; // Firefox throws an exception(exception异常) when accessing // statusText for faulty cross-domain requests // 火狐会报异常在跨域请求的时候; try { statusText = xhr.statusText; } catch( e ) { // We normalize with Webkit giving an empty statusText statusText = ""; } // 修正各种浏览器的状态码; // Filter status for non standard behaviours status = // Opera returns 0 when it should be 304 // Webkit returns 0 for failing cross-domain no matter the real status status === 0 ? ( // Webkit, Firefox: filter out faulty cross-domain requests !s.crossDomain || statusText ? ( // Opera: filter out real aborts #6060 responseHeaders ? 304 : 0 ) : // We assume 302 but could be anything cross-domain related 302 ) : ( // IE sometimes returns 1223 when it should be 204 (see #1450) status == 1223 ? 204 : status ); // Call complete //有responseXML和 //responseText两种值; //返回的返回头; complete( status, statusText, responses, responseHeaders ); } } }; // if we're in sync mode or it's in cache // and has been retrieved directly (IE6 & IE7) // we need to manually fire the callback if ( !s.async || xhr.readyState === 4 ) { callback(); } else { // Add to list of active xhrs handle = xhrId++; xhrs[ handle ] = xhr; xhr.onreadystatechange = callback; } }, abort: function() { if ( callback ) { callback(0,1); } } }; } }); } //保存默认的显示设置的变量; var elemdisplay = {}, // rfxtypes = /^(?:toggle|show|hide)$/, //开头是+-这样的符号 //包含数字.- //这个应该就是符号,符号可以是百分比; rfxnum = /^([+\-]=)?([\d+.\-]+)([a-z%]*)$/i, timerId, fxAttrs = [ // height animations [ "height", "marginTop", "marginBottom", "paddingTop", "paddingBottom" ], // width animations [ "width", "marginLeft", "marginRight", "paddingLeft", "paddingRight" ], // opacity animations [ "opacity" ] ]; jQuery.fn.extend({ /* $("#div1").hide(); $("#div1").show(); $._data($("#div1")[0]) ==>> Object {olddisplay: "block"} 所谓的olddisplay永远只有一个值; */ show: function( speed, easing, callback ) { var elem, display; //有传深度进来就调用animate if ( speed || speed === 0 ) { return this.animate( genFx("show", 3), speed, easing, callback); } else { //直接通过display方式进行直接显示或者隐藏; for ( var i = 0, j = this.length; i < j; i++ ) { elem = this[i]; display = elem.style.display; // Reset the inline display of this element to learn if it is // being hidden by cascaded rules or not // 不可见变成可见,!jQuery._data(elem, "olddisplay")只有第一次才走这边; // 如果没有没有被$(el).hide()过就没有olddisplay的, 就直接让元素根据样式表的默认样式进行显示; if ( !jQuery._data(elem, "olddisplay") && display === "none" ) { display = elem.style.display = ""; }; // Set elements which have been overridden with display: none // in a stylesheet to whatever the default browser style is // for such an element // 如果元素设置成display=""以后, 而且默认样式还是none, 就获取默认样式保存到私有数据缓存系统中; if ( display === "" && jQuery.css( elem, "display" ) === "none" ) { jQuery._data(elem, "olddisplay", defaultDisplay(elem.nodeName)); }; }; // Set the display of most of the elements in a second loop // to avoid the constant reflow // 这个可以直接放在上面的循环, 不过为了避免常量重渲染, 才把这个放在第二个循环里面 for ( i = 0; i < j; i++ ) { elem = this[i]; display = elem.style.display; //是空或者是none就给他展示默认的样式; if ( display === "" || display === "none" ) { elem.style.display = jQuery._data(elem, "olddisplay") || ""; } } return this; } }, hide: function( speed, easing, callback ) { if ( speed || speed === 0 ) { return this.animate( genFx("hide", 3), speed, easing, callback); } else { for ( var i = 0, j = this.length; i < j; i++ ) { var display = jQuery.css( this[i], "display" ); //如果这个元素是隐藏状态的话, 而且没有保存原来的显示值, 会把这个元素最初的显示值保存起来; if ( display !== "none" && !jQuery._data( this[i], "olddisplay" ) ) { jQuery._data( this[i], "olddisplay", display ); } } // Set the display of the elements in a second loop // to avoid the constant reflow // 隐藏起来; for ( i = 0; i < j; i++ ) { this[i].style.display = "none"; }; return this; } }, // Save the old toggle function _toggle: jQuery.fn.toggle, toggle: function( fn, fn2, callback ) { var bool = typeof fn === "boolean"; //卧槽, 这个是跑到click那个toggle去了; if ( jQuery.isFunction(fn) && jQuery.isFunction(fn2) ) { this._toggle.apply( this, arguments ); //fn === null || fn === undefined, 不传参数的走这个else; } else if ( fn == null || bool ) { this.each(function() { //有就根据不二值展示, 没有根据当前进行显示和隐藏; var state = bool ? fn : jQuery(this).is(":hidden"); jQuery(this)[ state ? "show" : "hide" ](); }); } else { //别的参数调用动画去了; this.animate(genFx("toggle", 3), fn, fn2, callback); }; return this; }, fadeTo: function( speed, to, easing, callback ) { return this.filter(":hidden").css("opacity", 0).show().end() .animate({opacity: to}, speed, easing, callback); }, /* $("#div1").animate({height:40},1000,"swing",function(){console.log(1)}) $("#div1").animate({height:400},1000,"swing",function(){console.log(1)}) */ animate: function( prop, speed, easing, callback ) { /* 参数被处理后会是这个样子的, 因为本身就是回调是函数形式, 动画形式是字符串形式, 经过时间是数字形式, 所以处理还是挺简单的; complete: function () { if ( opt.queue !== false ) { jQuery(this).dequeue(); } if ( jQuery.isFunction( opt.old ) ) { opt.old.call( this ); } }, duration: 1000, easing: "swing", old: function (){console.log(1)} */ var optall = jQuery.speed(speed, easing, callback); if ( jQuery.isEmptyObject( prop ) ) { return this.each( optall.complete ); }; //默认的queue是undefined, 这个是动画大体上形式; return this[ optall.queue === false ? "each" : "queue" ](function() { // XXX 'this' does not always have a nodeName when running the // test suite //重新复制了一份opt了; var opt = jQuery.extend({}, optall), p, isElement = this.nodeType === 1, hidden = isElement && jQuery(this).is(":hidden"), self = this; for ( p in prop ) { //要对属性转驼峰; var name = jQuery.camelCase( p ); if ( p !== name ) { prop[ name ] = prop[ p ]; delete prop[ p ]; p = name; }; // if ( prop[p] === "hide" && hidden || prop[p] === "show" && !hidden ) { return opt.complete.call(this); } /* jQ1.4就只有这两句; if ( ( p === "height" || p === "width" ) && this.style ) { //保存原来的display属性和overflow属性; // Store display property opt.display = jQuery.css(this, "display"); // Make sure that nothing sneaks out opt.overflow = this.style.overflow; }; */ if ( isElement && ( p === "height" || p === "width" ) ) { //sneaks out 渐隐;偷偷溜走; // Make sure that nothing sneaks out // Record all 3 overflow attributes because IE does not // change the overflow attribute when overflowX and // overflowY are set to the same value // 记录这个属性因为IE改变overflow是不改变overflowX和overflowY为相同的值; opt.overflow = [ this.style.overflow, this.style.overflowX, this.style.overflowY ]; // Set display property to inline-block for height/width // animations on inline elements that are having width/height // animated // 要改变width或者height的话必须要是块元素, 所以我们让他变成块元素 //如果支持inline-block,就用inlineblock //否知使用inline并设置zoom为1, 触发元素的hasLayout; if ( jQuery.css( this, "display" ) === "inline" && jQuery.css( this, "float" ) === "none" ) { if ( !jQuery.support.inlineBlockNeedsLayout ) { this.style.display = "inline-block"; } else { var display = defaultDisplay(this.nodeName); // inline-level elements accept inline-block; // block-level elements need to be inline with layout if ( display === "inline" ) { this.style.display = "inline-block"; } else { this.style.display = "inline"; this.style.zoom = 1; } } } } //$("#div1").animate({height:[1000, "swing"]},1000,function(){console.log(1)}) //$("#div1").animate({height:[40, "swing"]},1000,function(){console.log(1)}) if ( jQuery.isArray( prop[p] ) ) { // Create (if needed) and add to specialEasing (opt.specialEasing = opt.specialEasing || {})[p] = prop[p][1]; prop[p] = prop[p][0]; } }; //触发layout? if ( opt.overflow != null ) { this.style.overflow = "hidden"; } //需要改变的值保存到curAnim里面去; opt.curAnim = jQuery.extend({}, prop); //根据需要改变属性的对象迭代 jQuery.each( prop, function( name, val ) { /* self : 当前元素; opt : { complete : fn, curAnim : { height : "400px" }, duration : 1000, easing : "swing", old : fn, overflow : ["","",""] } */ var e = new jQuery.fx( self, opt, name ); //如果是toggle或者说是hide或者是show的话; if ( rfxtypes.test(val) ) { e[ val === "toggle" ? hidden ? "show" : "hide" : val ]( prop ); } else { var parts = rfxnum.exec(val), start = e.cur() || 0; //处理参数, 最后把实际的参数放到动画实例; if ( parts ) { var end = parseFloat( parts[2] ), unit = parts[3] || "px"; // We need to compute starting value // 单位可能是%, em 等不常用的单位; if ( unit !== "px" ) { //设置为需要的单位的最终值; jQuery.style( self, name, (end || 1) + unit); /*计算 //e.cur()而是根据用户的unit的最终值; end result -------- = -------- e.cur() start result = end/e.cur()*start; */ start = ((end || 1) / e.cur()) * start; //还原单位的初始值; jQuery.style( self, name, start + unit); }; // If a +=/-= token was provided, we're doing a relative animation if ( parts[1] ) { end = ((parts[1] === "-=" ? -1 : 1) * end) + start; }; //直接放到fx的对列, 让他跑就好了; e.custom( start, end, unit ); } else { e.custom( start, val, "" ); } } }); // For JS strict compliance return true; }); }, stop: function( clearQueue, gotoEnd ) { var timers = jQuery.timers; //如果有clearQueue, 把queue队列清空 if ( clearQueue ) { this.queue([]); }; //把jQ下的timers时间列表给删除 this.each(function() { // go in reverse order so anything added to the queue during the loop is ignored for ( var i = timers.length - 1; i >= 0; i-- ) { if ( timers[i].elem === this ) { if (gotoEnd) { // force the next step to be the last timers[i](true); }; timers.splice(i, 1); }; }; }); // start the next in the queue if the last step wasn't forced if ( !gotoEnd ) { this.dequeue(); } return this; } }); /* JSON.stringify(genFx("show", 1),null,4) "{ "height": "show", "marginTop": "show", "marginBottom": "show", "paddingTop": "show", "paddingBottom": "show" }"; JSON.stringify(genFx("show", 3),null,4) "{ "height": "show", "marginTop": "show", "marginBottom": "show", "paddingTop": "show", "paddingBottom": "show", "width": "show", "marginLeft": "show", "marginRight": "show", "paddingLeft": "show", "paddingRight": "show", "opacity": "show" }" */ function genFx( type, num ) { var obj = {}; /* fxAttrs = [ // height animations [ "height", "marginTop", "marginBottom", "paddingTop", "paddingBottom" ], // width animations [ "width", "marginLeft", "marginRight", "paddingLeft", "paddingRight" ], // opacity animations [ "opacity" ] ] */ jQuery.each( fxAttrs.concat.apply([], fxAttrs.slice(0,num)), function() { obj[ this ] = type; }); return obj; } // Generate shortcuts for custom animations /* 比如你要让一个元素slideDown 那么这个元素的height,margintop marginbottom paddingtop padding-bottom都要一个个变小, 为这个元素的这几个属性添加变小的定时器; */ jQuery.each({ slideDown: genFx("show", 1), slideUp: genFx("hide", 1), slideToggle: genFx("toggle", 1), fadeIn: { opacity: "show" }, fadeOut: { opacity: "hide" }, fadeToggle: { opacity: "toggle" } }, function( name, props ) { jQuery.fn[ name ] = function( speed, easing, callback ) { return this.animate( props, speed, easing, callback ); }; }); //初始化变量, 用户可以传 number(动画时间), string(动画形式), function(动画完成的回调) jQuery.extend({ speed: function( speed, easing, fn ) { var opt = speed && typeof speed === "object" ? jQuery.extend({}, speed) : { complete: fn || !fn && easing || jQuery.isFunction( speed ) && speed, duration: speed, easing: fn && easing || easing && !jQuery.isFunction(easing) && easing }; opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration : opt.duration in jQuery.fx.speeds ? jQuery.fx.speeds[opt.duration] : jQuery.fx.speeds._default; // Queueing opt.old = opt.complete; opt.complete = function() { if ( opt.queue !== false ) { jQuery(this).dequeue(); } if ( jQuery.isFunction( opt.old ) ) { opt.old.call( this ); } }; return opt; }, easing: { linear: function( p, n, firstNum, diff ) { return firstNum + diff * p; }, swing: function( p, n, firstNum, diff ) { return ((-Math.cos(p*Math.PI)/2) + 0.5) * diff + firstNum; } }, timers: [], //fx动画的函数, 很重要, 所有的动画开始结束都是基于这个函数的; fx: function( elem, options, prop ) { this.options = options; this.elem = elem; this.prop = prop; if ( !options.orig ) { options.orig = {}; } } }); jQuery.fx.prototype = { // Simple function for setting a style value // 更新元素的fx.prop为fx.now; update: function() { if ( this.options.step ) { this.options.step.call( this.elem, this.now, this ); } //opacity和默认动画 , 你也可以把为这个spep设置width或者高的运动step (jQuery.fx.step[this.prop] || jQuery.fx.step._default)( this ); }, // Get the current size // 通过$.css获取当前样式; cur: function() { if ( this.elem[this.prop] != null && (!this.elem.style || this.elem.style[this.prop] == null) ) { return this.elem[ this.prop ]; } var r = parseFloat( jQuery.css( this.elem, this.prop ) ); return r || 0; }, // Start an animation from one number to another custom: function( from, to, unit ) { var self = this, fx = jQuery.fx; this.startTime = jQuery.now(); this.start = from; this.end = to; this.unit = unit || this.unit || "px"; this.now = this.start; this.pos = this.state = 0; function t( gotoEnd ) { return self.step(gotoEnd); } t.elem = this.elem; //只要t返回ture那么times就会push这个step, !timerId时候才添加, 只要一个线程就好了, 提高性能; if ( t() && jQuery.timers.push(t) && !timerId ) { //timerId和fx是在同一个作用域的; timerId = setInterval(fx.tick, fx.interval); } }, // Simple 'show' function show: function() { // Remember where we started, so that we can go back to it later this.options.orig[this.prop] = jQuery.style( this.elem, this.prop ); this.options.show = true; // Begin the animation // Make sure that we start at a small width/height to avoid any // flash of content this.custom(this.prop === "width" || this.prop === "height" ? 1 : 0, this.cur()); // Start by showing the element jQuery( this.elem ).show(); }, // Simple 'hide' function hide: function() { // Remember where we started, so that we can go back to it later this.options.orig[this.prop] = jQuery.style( this.elem, this.prop ); this.options.hide = true; // Begin the animation this.custom(this.cur(), 0); }, // Each step of an animation //如果动画完成了就返回ture, 否知返回false step: function( gotoEnd ) { //假设当前的这个属性的动画完成了 var t = jQuery.now(), done = true; //jQ的动画是根据时间进行的, 所以这边要判断一下时间是否超过预期的时间; if ( gotoEnd || t >= this.options.duration + this.startTime ) { this.now = this.end; this.pos = this.state = 1; this.update(); this.options.curAnim[ this.prop ] = true; //this.options保存了原来的opt参数, 只要有一个属性动画没完成,动画就是未完成; for ( var i in this.options.curAnim ) { if ( this.options.curAnim[i] !== true ) { done = false; }; }; // 如果动画完成了就把这个元素原来的属性赋值到元素; if ( done ) { // Reset the overflow if ( this.options.overflow != null && !jQuery.support.shrinkWrapBlocks ) { var elem = this.elem, options = this.options; jQuery.each( [ "", "X", "Y" ], function (index, value) { elem.style[ "overflow" + value ] = options.overflow[index]; } ); }; // Hide the element if the "hide" operation was done if ( this.options.hide ) { jQuery(this.elem).hide(); }; // 把结果值再赋值一遍; // Reset the properties, if the item has been hidden or shown if ( this.options.hide || this.options.show ) { for ( var p in this.options.curAnim ) { jQuery.style( this.elem, p, this.options.orig[p] ); }; }; // done的话就走complete; // Execute the complete function this.options.complete.call( this.elem ); }; // return false; } else { // 根据经过的时间表示进度; var n = t - this.startTime; this.state = n / this.options.duration; // // Perform the easing function, defaults to swing var specialEasing = this.options.specialEasing && this.options.specialEasing[this.prop]; var defaultEasing = this.options.easing || (jQuery.easing.swing ? "swing" : "linear"); //进度 //经过的时间 //总的时间; this.pos = jQuery.easing[specialEasing || defaultEasing](this.state, n, 0, 1, this.options.duration); //计算结果的值; this.now = this.start + ((this.end - this.start) * this.pos); // debugger; // Perform the next step of the animation // 把值更新到元素上; this.update(); } return true; } }; jQuery.extend( jQuery.fx, { //这个tick是一个定时器, 只要有要运动元素, 这个定时器都在跑 tick: function() { var timers = jQuery.timers; for ( var i = 0; i < timers.length; i++ ) { //timers保存的是每一个元素改变样式的step闭包, 如果元素的执行完成就会返回false, 那么这个改变的线程就会被删除; if ( !timers[i]() ) { timers.splice(i--, 1); }; }; //如果没有了就会清空timers, fx.stopstop就在这个方法的后面; if ( !timers.length ) { jQuery.fx.stop(); }; }, interval: 13, stop: function() { clearInterval( timerId ); timerId = null; }, speeds: { slow: 600, fast: 200, // Default speed _default: 400 }, //fx.step 这个可以添加; step: { opacity: function( fx ) { jQuery.style( fx.elem, "opacity", fx.now ); }, _default: function( fx ) { if ( fx.elem.style && fx.elem.style[ fx.prop ] != null ) { fx.elem.style[ fx.prop ] = (fx.prop === "width" || fx.prop === "height" ? Math.max(0, fx.now) : fx.now) + fx.unit; } else { fx.elem[ fx.prop ] = fx.now; } } } }); if ( jQuery.expr && jQuery.expr.filters ) { jQuery.expr.filters.animated = function( elem ) { return jQuery.grep(jQuery.timers, function( fn ) { return elem === fn.elem; }).length; }; } function defaultDisplay( nodeName ) { //缓冲elemdisplay, 优化,避免下一次再跑同样的标签; if ( !elemdisplay[ nodeName ] ) { var elem = jQuery("<" + nodeName + ">").appendTo("body"), display = elem.css("display"); elem.remove(); if ( display === "none" || display === "" ) { display = "block"; }; elemdisplay[ nodeName ] = display; }; return elemdisplay[ nodeName ]; }; //匹配标签是table或者是td或者是th这种标签; var rtable = /^t(?:able|d|h)$/i, //匹配是body或者是html标签; rroot = /^(?:body|html)$/i; //jQ真的很细心, 工作和生活其实都要这样才能有成就啊; //getBoundingClientRect虽然是ie的东西,但是在任何浏览器都是全兼容的, 所以可以放心使用, 通过这个东东计算宽高很快的; if ( "getBoundingClientRect" in document.documentElement ) { //返回相对于body的top和left; jQuery.fn.offset = function( options ) { // var elem = this[0], box; //如果有传参数, 就是设置值; if ( options ) { return this.each(function( i ) { jQuery.offset.setOffset( this, options, i ); }); }; //不存在elem还玩个毛; if ( !elem || !elem.ownerDocument ) { return null; } //如果是body进行特殊判断; if ( elem === elem.ownerDocument.body ) { //是body的话直接返回body的left和top,默认的的8pxmargin return jQuery.offset.bodyOffset( elem ); }; //获取这个元素相对于这个界面的left和top; try { box = elem.getBoundingClientRect(); } catch(e) {}; var doc = elem.ownerDocument, docElem = doc.documentElement; // Make sure we're not dealing with a disconnected DOM node // 如果box这个没返回, 或者box的ownerDocument不包含这个box[in the fragmentDocument : document.createDocumentFragment()] if ( !box || !jQuery.contains( docElem, elem ) ) { //如果这个元素不在dom里面也有top和left吗? return box ? { top: box.top, left: box.left } : { top: 0, left: 0 }; }; // var body = doc.body, win = getWindow(doc), //IE的documentElement是不显示的, 只有body; clientTop = docElem.clientTop || body.clientTop || 0, clientLeft = docElem.clientLeft || body.clientLeft || 0, scrollTop = (win.pageYOffset || jQuery.support.boxModel && docElem.scrollTop || body.scrollTop ), scrollLeft = (win.pageXOffset || jQuery.support.boxModel && docElem.scrollLeft || body.scrollLeft), //去除border的高度, 因为滚动条包含border的; top = box.top + scrollTop - clientTop, left = box.left + scrollLeft - clientLeft; return { top: top, left: left }; }; } else { //没有getBoundingClientRect jQuery.fn.offset = function( options ) { var elem = this[0]; if ( options ) { return this.each(function( i ) { jQuery.offset.setOffset( this, options, i ); }); } if ( !elem || !elem.ownerDocument ) { return null; } if ( elem === elem.ownerDocument.body ) { return jQuery.offset.bodyOffset( elem ); } //获得关于offset相关的浏览器特征 jQuery.offset.initialize(); var computedStyle, offsetParent = elem.offsetParent, prevOffsetParent = elem, doc = elem.ownerDocument, docElem = doc.documentElement, body = doc.body, defaultView = doc.defaultView, prevComputedStyle = defaultView ? defaultView.getComputedStyle( elem, null ) : elem.currentStyle, //从当前元素的offset开始(再重复一次, offset是从border开始的(不包含border,即margin-box); top = elem.offsetTop, left = elem.offsetLeft; //不用getBoundingClientRect获取offset很麻烦; //迭代父级, 而不是迭代offsetParent哦, 因为parentNode如果有scroll的话计算出来的offsetLeft或者offsetTop不准确), 直到body或者html标签; //迭代每一个父级,对于每一个父级的scroll和border进行特殊处理, while ( (elem = elem.parentNode) && elem !== body && elem !== docElem ) { //fix的标签是根据界面定位的, 一定要例外处理, 否则计算出的值有误;; if ( jQuery.offset.supportsFixedPosition && prevComputedStyle.position === "fixed" ) { break; }; computedStyle = defaultView ? defaultView.getComputedStyle(elem, null) : elem.currentStyle; //因为我们要计算的是相对界面的top和left, 所以要把对所有有滚动的父级进行处理(减去处理); top -= elem.scrollTop; left -= elem.scrollLeft; //对有定位的offsetParent才处理,(要弄懂offsetParent的元素是有定位的元素,比如absolute或者是relative的元素); if ( elem === offsetParent ) { top += elem.offsetTop; left += elem.offsetLeft; //offset应该返回的是border-box,但在一些表格元素却没有计算它们的border值,需要自行添加 //offset是指从当前的margin-box(包含margin)到父级的border-box(包含border-box),有些浏览器的offset不包含border, 要注意, 所以也要特殊处理; //又对table进行特殊处理 if ( jQuery.offset.doesNotAddBorder && !(jQuery.offset.doesAddBorderForTableAndCells && rtable.test(elem.nodeName)) ) { top += parseFloat( computedStyle.borderTopWidth ) || 0; left += parseFloat( computedStyle.borderLeftWidth ) || 0; }; prevOffsetParent = offsetParent; //把offsetParent给offsetParent offsetParent = elem.offsetParent; } //修正safari的错误 if ( jQuery.offset.subtractsBorderForOverflowNotVisible && computedStyle.overflow !== "visible" ) { top += parseFloat( computedStyle.borderTopWidth ) || 0; left += parseFloat( computedStyle.borderLeftWidth ) || 0; }; prevComputedStyle = computedStyle; } //最后加上body的偏移 if ( prevComputedStyle.position === "relative" || prevComputedStyle.position === "static" ) { top += body.offsetTop; left += body.offsetLeft; } //如果元素使用了fix定位, 要加上最大滚动距离; if ( jQuery.offset.supportsFixedPosition && prevComputedStyle.position === "fixed" ) { top += Math.max( docElem.scrollTop, body.scrollTop ); left += Math.max( docElem.scrollLeft, body.scrollLeft ); } return { top: top, left: left }; }; } jQuery.offset = { //一些兼容检测, 坑多, 太多了; initialize: function() { var body = document.body, container = document.createElement("div"), innerDiv, checkDiv, table, td, bodyMarginTop = parseFloat( jQuery.css(body, "marginTop") ) || 0, html = "<div style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;'><div></div></div><table style='position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;' cellpadding='0' cellspacing='0'><tr><td></td></tr></table>"; jQuery.extend( container.style, { position: "absolute", top: 0, left: 0, margin: 0, border: 0, width: "1px", height: "1px", visibility: "hidden" } ); container.innerHTML = html; body.insertBefore( container, body.firstChild ); innerDiv = container.firstChild; checkDiv = innerDiv.firstChild; td = innerDiv.nextSibling.firstChild.firstChild; this.doesNotAddBorder = (checkDiv.offsetTop !== 5); this.doesAddBorderForTableAndCells = (td.offsetTop === 5); checkDiv.style.position = "fixed"; checkDiv.style.top = "20px"; // safari subtracts parent border width here which is 5px this.supportsFixedPosition = (checkDiv.offsetTop === 20 || checkDiv.offsetTop === 15); checkDiv.style.position = checkDiv.style.top = ""; innerDiv.style.overflow = "hidden"; innerDiv.style.position = "relative"; this.subtractsBorderForOverflowNotVisible = (checkDiv.offsetTop === -5); this.doesNotIncludeMarginInBodyOffset = (body.offsetTop !== bodyMarginTop); body.removeChild( container ); body = container = innerDiv = checkDiv = table = td = null; jQuery.offset.initialize = jQuery.noop; }, bodyOffset: function( body ) { var top = body.offsetTop, left = body.offsetLeft; //兼容检测; jQuery.offset.initialize(); //offsetLeft offsetTop默认就是不包含当前元素的margin, 可能向前的浏览器不给力; if ( jQuery.offset.doesNotIncludeMarginInBodyOffset ) { top += parseFloat( jQuery.css(body, "marginTop") ) || 0; left += parseFloat( jQuery.css(body, "marginLeft") ) || 0; } return { top: top, left: left }; }, //这个还是jQ的工具方法; setOffset: function( elem, options, i ) { var position = jQuery.css( elem, "position" ); //如果元素没有指定position的值, 那么默认的返回为static; // set position first, in-case top/left are set even on static elem if ( position === "static" ) { elem.style.position = "relative"; }; //获取默认值; var curElem = jQuery( elem ), curOffset = curElem.offset(), curCSSTop = jQuery.css( elem, "top" ), curCSSLeft = jQuery.css( elem, "left" ), //如果有个定位的值是auto; calculatePosition = (position === "absolute" && jQuery.inArray('auto', [curCSSTop, curCSSLeft]) > -1), props = {}, curPosition = {}, curTop, curLeft; // 需要计算值, 这个值是通过有定位的父级的position计算出相对的值; // need to be able to calculate position if either top or left is auto and position is absolute if ( calculatePosition ) { curPosition = curElem.position(); }; // 通过计算获取的值优先用, 否者用计算后样式; curTop = calculatePosition ? curPosition.top : parseInt( curCSSTop, 10 ) || 0; curLeft = calculatePosition ? curPosition.left : parseInt( curCSSLeft, 10 ) || 0; // 判断是不是函数 if ( jQuery.isFunction( options ) ) { options = options.call( elem, i, curOffset ); }; // if (options.top != null) { // 这里是通过$.css设置定位, 所以有个恒等式 : 设置后的相对界面的位置 - 设置前元素相对界面的位置 = 设置后元素相对父级的位置 - 设置前元素相对父级的位置 // 我们要求的是设置后相对父级的位置, 把这个东东倒一下就好了; //我们要设置的offset, 相对界面的位置 //用户设置的值 //相对界面的定位 //相对父级的值; props.top = (options.top - curOffset.top) + curTop; } if (options.left != null) { props.left = (options.left - curOffset.left) + curLeft; } //可以传一个using的方法, 会把元素和 最后的样式作为参数传进去; if ( "using" in options ) { options.using.call( elem, props ); } else { curElem.css( props ); } } }; jQuery.fn.extend({ // position是获取相对有定位父级的位置; position: function() { if ( !this[0] ) { return null; } var elem = this[0], // Get *real* offsetParent offsetParent = this.offsetParent(), // Get correct offsets offset = this.offset(), //如果是html或者body就把left和top设置为0, 不要忘记了元素的定位是包含margin的,这个很重要,而且子元素的定位是根据父级的padding-box进行设置的; //元素的offset是从border-box开始的 parentOffset = rroot.test(offsetParent[0].nodeName) ? { top: 0, left: 0 } : offsetParent.offset(); // Subtract element margins // note: when an element has margin: auto the offsetLeft and marginLeft // are the same in Safari causing offset.left to incorrectly be 0 offset.top -= parseFloat( jQuery.css(elem, "marginTop") ) || 0; offset.left -= parseFloat( jQuery.css(elem, "marginLeft") ) || 0; // Add offsetParent borders parentOffset.top += parseFloat( jQuery.css(offsetParent[0], "borderTopWidth") ) || 0; parentOffset.left += parseFloat( jQuery.css(offsetParent[0], "borderLeftWidth") ) || 0; // Subtract the two offsets return { //元素的offset是从border-box开始的 //position是指这个元素的margin定点到padding-box(包含padding)的距离, 所以要计算该元素和父级元素的offset, 将这两个元素的offset相减, 当前的值是包含了 当前元素的margin的且 不包含父级元素border的,所以要重新计算 //这里最好画图,要么头会晕, 而且个个属性的定义要弄清楚; top: offset.top - parentOffset.top, left: offset.left - parentOffset.left }; }, offsetParent: function() { return this.map(function() { var offsetParent = this.offsetParent || document.body; //如果元素的定位为static而且不是根元素, 重新定义offsetParent, 应该是某些浏览器会返回position为static的offstParent; while ( offsetParent && (!rroot.test(offsetParent.nodeName) && jQuery.css(offsetParent, "position") === "static") ) { offsetParent = offsetParent.offsetParent; }; return offsetParent; }); } }); // Create scrollLeft and scrollTop methods jQuery.each( ["Left", "Top"], function( i, name ) { var method = "scroll" + name; //减少代码而设置的闭包; //scrollLeft; //scrollTop jQuery.fn[ method ] = function(val) { var elem = this[0], win; //参数检测; if ( !elem ) { return null; } //设置值; if ( val !== undefined ) { // Set the scroll offset return this.each(function() { win = getWindow( this ); //如果是window就是设置滚动的值; if ( win ) { win.scrollTo( !i ? val : jQuery(win).scrollLeft(), i ? val : jQuery(win).scrollTop() ); } else { //this.scrollTop, this.scrollLeft; this[ method ] = val; } }); } else { // 获取 win = getWindow( elem ); // Return the scroll offset //浏览器的能力检测; return win ? ("pageXOffset" in win) ? win[ i ? "pageYOffset" : "pageXOffset" ] : jQuery.support.boxModel && win.document.documentElement[ method ] || win.document.body[ method ] : //直接返回 elem[ method ]; } }; }); //你不能传一个nodeType为1的元素进来; function getWindow( elem ) { return jQuery.isWindow( elem ) ? elem : //documentNodeType ==》》 9 elem.nodeType === 9 ? elem.defaultView || elem.parentWindow : false; } // Create innerHeight, innerWidth, outerHeight and outerWidth methods jQuery.each([ "Height", "Width" ], function( i, name ) { //又是一个闭包; var type = name.toLowerCase(); // innerHeight and innerWidth jQuery.fn["inner" + name] = function() { //innerWidth是包含padding的, 引用css的方法, 传的是padding return this[0] ? parseFloat( jQuery.css( this[0], type, "padding" ) ) : null; }; // outerHeight and outerWidth // 获取的是borderBox的宽或者高度,可以传true获取的是包含margin的宽和高; jQuery.fn["outer" + name] = function( margin ) { return this[0] ? parseFloat( jQuery.css( this[0], type, margin ? "margin" : "border" ) ) : null; }; //type为 width或者是height; jQuery.fn[ type ] = function( size ) { // Get window width or height var elem = this[0]; //避免错误; if ( !elem ) { return size == null ? null : this; } //对函数进行处理; if ( jQuery.isFunction( size ) ) { return this.each(function( i ) { var self = jQuery( this ); //引用自身, 把计算后值传到回调方法里; self[ type ]( size.call( this, i, self[ type ]() ) ); }); }; // 是window的话 if ( jQuery.isWindow( elem ) ) { // Everyone else use document.documentElement or document.body depending on Quirks vs Standards mode // 3rd condition allows Nokia support, as it supports the docElem prop but not CSS1Compat //标准浏览器的用户宽和高是就是html标签的宽高; var docElemProp = elem.document.documentElement[ "client" + name ]; //标准浏览器会走第一个 return elem.document.compatMode === "CSS1Compat" && docElemProp || //IE的诡异模式会走这一个 //这个就不知道什么情况了; elem.document.body[ "client" + name ] || docElemProp; // Get document width or height } else if ( elem.nodeType === 9 ) { // Either scroll[Width/Height] or offset[Width/Height], whichever is greater //计算documentElemnt的最大值; return Math.max( elem.documentElement["client" + name], elem.body["scroll" + name], elem.documentElement["scroll" + name], elem.body["offset" + name], elem.documentElement["offset" + name] ); // Get or set width or height on the element //这个是获取; } else if ( size === undefined ) { var orig = jQuery.css( elem, type ), ret = parseFloat( orig ); //parseFlaot是NaN应该是auto这种情况; return jQuery.isNaN( ret ) ? orig : ret; // Set the width or height on the element (default to pixels if value is unitless) //最后走设置 } else { //width或者是高; return this.css( type, typeof size === "string" ? size : size + "px" ); } }; }); })(window);
Sizzle被我拿掉了;
END, 祝自己周末愉快( ¯(∞)¯ )