jQ做爲javascript的庫( ▼-▼ ), 盡善盡美, 代碼優美, 值得學習。 這一週日常上班沒啥事也看jQ1.5的代碼, 今天週六差很少看完了(Sizzle部分還沒看), 從新看了一下, 又有不少新東西;javascript
相對與1.4版本的ajax部分, 整個進行了重寫, 實在是坑爹, 如今還有不少沒弄懂, ajax能夠很是簡單地:php
var xhr = new XMLHttpReques || new window.ActiveXObject("Microsoft.XMLHTTP");
也能夠一樣能夠寫幾千行, 全部的$.get(), $.post(), $.getJSON(), $.getScript().... 都是$.ajax的shortcut, 全部最後都是經過$.ajax 這個方法統一處理, 並且多了傳送器這東西, 一會兒以爲好酷炫, 並且查資料查到司徒大神2011就開始瞭解這些東西, 本身實在差太多了, 你們都要加油才行;css
//匹配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裏面, 根據先進先出的方式一次次執行, 並且傳的參數也挺人性化無論你各個參數的排序怎麼樣, 最後都會自動修正, 正常的是這樣的html
$("#div1").animate({height:40},1000,"swing",function(){console.log(1)})
也對用戶傳進來的非px的單位, 好比em, %進行了參數轉化, 經過實例化一個動畫對象放到$.timers這個數組裏面, 只要數組有東西, 就開着定時器一直跑, 你能夠添加本身的動畫方法, 由於自己jQ就提供了 "swing" 和 "linear"兩種方式, $.easing;html5
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; } }
一個是線性, 就是速度不變的狀況下在固定時間到達終點, 一個是先慢後快再慢這種狀況; 話說好像有科學證實全部人對漸漸消失和漸漸顯示都有很好印象, 不會太唐突;java
像speed這些, 默認的動畫讓他normal就是400毫秒本身跑, 快的就是200毫秒, 慢的就是800毫秒:node
speeds: { slow: 600, fast: 200, // Default speed _default: 400 }
這些很是簡單, 真正可貴在代碼裏面:react
//保存默認的顯示設置的變量; 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的各個寬高值不清楚的看這個會比較麻煩:jquery
//匹配標籤是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了;c++
/*! * 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, 祝本身週末愉快( ¯(∞)¯ )