jQuery源码阅读(十四)---aJax 模块与异步队列联系

来源:互联网 发布:冰箱 知乎 编辑:程序博客网 时间:2024/06/06 01:42

关于ajaxjQuery做了封装,并且考虑了很多浏览器兼容问题,以及跨域问题。当然,这种异步操作离不开我们之前分析的延迟对象。所以这一次,主要是对ajax模块中对于延迟对象的应用进行分析。

$.ajax函数

先来分析$.ajax()函数。
在这里,我必须贴一张图来展示下ajax源码的整体框架。此图引用于高云(jQuery技术内幕第13章)
这里写图片描述

上图清楚得描述了整个ajax过程中的一系列流程,同时是按照源码顺序列出来的。根据上图,我们也能大体还原出来一个ajax源码框架。

通常,我们调ajax时,比较常见的就是 $.ajax({ url: 'XX.php', success: function(){ } })这种形式, 但也有$.ajax('XX.php').done(function(){ }).fail(function(){ })这种形式。所以,不难想象,$.ajax()返回的也是一个延迟对象。即上图上面最后返回的jqXHR对象。而这个jqXHR对象又类似于我们原生XMLHttpRequest对象,需要有自己的一些方法和属性,因此,可以利用延迟对象的promise(arguments)方法来扩展延迟对象。

function( url, options){    //创建延迟对象    var deferred = $.Deferred();    //创建一个回调对象,用于最终完成时,触发完成回调函数。    var completeDeferred = $.Callbacks('once memory')    jqXHR = {        //拥有一些XMLHttpRequest的属性和方法,比如readyState、 setRequestHeader等等。        readyState: 0,        setRequestHeader: function(){        }       }    deferred.promise(jqXHR);    //此时jqXHR是一个延迟对象,并且拥有自己特有的属性和方法    //将我们常用的success、error、complete这些回调方法分别与延迟对象的回调列表对应起来。    jqXHR.success = jqXHR.done;    jqXHR.error = jqXHR.fail;    jqXHR.complete = completeDeferred.add;    return jqXHR;}

上面这部分框架,只是创建jqXHR对象,并将其与延迟对象的各个列表联系起来,并最终返回这个延迟对象的过程。那么,问题来了,到底随着请求的产生,发送及响应,是如何触发回调函数的?

我们再回过头看上面的图,最后当transport.send( requestHeaders, done );执行之后,这个函数主要执行了xhr.send();这个函数,然后监听readyStateChange事件,由xhr.responseText或者xhr.responseXML判断,有响应数据时,便会触发send中的回调函数done
因为transport.send针对了script和 其他数据类型两种情况,我们这里只讨论后一种情况。其transport.send中的部分源码如下:

//创建XMLHttpRequest对象//打开socket连接:xhr.open()if ( s.username ) {    xhr.open( s.type, s.url, s.async, s.username, s.password );} else {        xhr.open( s.type, s.url, s.async );}//设置HTTP请求头信息//接着是发送请求:xhr.send(),如果是post方法,有参数xhr.send( ( s.hasContent && s.data ) || null );//下面的思想,跟我们用原生XMLHttpRequest对象实现aJax是一样的。也是通过监听readyState是否变化的事件从而触发回调函数//如果是同步ajax请求或者状态已经完成,那么手动执行回调函数if ( !s.async || xhr.readyState === 4 ) {    callback();} else {    //如果是异步,并且状态还未变成已完成,那么通过监听readystatechange事件来触发回调函数    handle = ++xhrId;    if ( xhrOnUnloadAbort ) {        // 这里主要是在IE浏览器中        if ( !xhrCallbacks ) {            xhrCallbacks = {};            //因为在IE浏览器中,关闭浏览器时,仍然保持着连接,所以需要手动将所有的ajax请求回调函数取消。            jQuery( window ).unload( xhrOnUnloadAbort );        }        xhrCallbacks[ handle ] = callback;    }    //不是IE浏览器时,监听readystatechange事件    xhr.onreadystatechange = callback;}

那么回调函数callback 是干嘛的?

//isAbort表示是否取消请求function( _, isAbort ) {    var status,    statusText,    responseHeaders,    responses,    xml;    // Firefox throws exceptions when accessing properties    // of an xhr when a network error occured    // http://helpful.knobsdials.com/index.php/Component_returned_failure_code:_0x80040111_(NS_ERROR_NOT_AVAILABLE)    try {        // Was never called and is aborted or complete        if ( callback && ( isAbort || xhr.readyState === 4 ) ) {            // Only called once            callback = undefined;            // Do not keep as active anymore            if ( handle ) {                xhr.onreadystatechange = jQuery.noop;                if ( xhrOnUnloadAbort ) {                    delete xhrCallbacks[ handle ];                }            }            // If it's an abort                if ( isAbort ) {                    // Abort it manually if needed                    if ( xhr.readyState !== 4 ) {                        xhr.abort();                    }                } else {                    status = xhr.status;                    responseHeaders = xhr.getAllResponseHeaders();                    responses = {};                    xml = xhr.responseXML;                    // Construct response list                    if ( xml && xml.documentElement /* #4958 */ ) {                        responses.xml = xml;                    }                    // When requesting binary data, IE6-9 will throw an exception                    // on any attempt to access responseText (#11426)                    try {                        responses.text = xhr.responseText;                    } catch( _ ) {                    }                    // Firefox throws an 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 behaviors                    // If the request is local and we have data: assume a success                    // (success with no data won't get notified, that's the best we                    // can do given current implementations)                    if ( !status && s.isLocal && !s.crossDomain ) {                        status = responses.text ? 200 : 404;                        // IE - #1450: sometimes returns 1223 when it should be 204                 } else if ( status === 1223 ) {                        status = 204;                }            }        }    } catch( firefoxAccessException ) {        if ( !isAbort ) {            complete( -1, firefoxAccessException );        }    }    //如果得到响应数据,触发complete函数,这个函数是外部传过来的函数,也就是我们外部的done回调函数    if ( responses ) {        complete( status, statusText, responses, responseHeaders );    }}

done函数中,主要根据status来判断响应是否成功,如果成功,触发jqXHR的成功回调列表中的回调;如果失败,则触发jqXHR的失败回调列表中的回调,除了这些处理,还会触发complete回调函数,同时会根据情况触发各种ajax全局函数。

function done( status, nativeStatusText, responses, headers ) {    //如果state为2,说明这个回调已经执行过了,不会再执行第二次了    if ( state === 2 ) {        return;    }    state = 2;    // Clear timeout if it exists    if ( timeoutTimer ) {        clearTimeout( timeoutTimer );    }    // Dereference transport for early garbage collection    // (no matter how long the jqXHR object will be used)    transport = undefined;    // Cache response headers    responseHeadersString = headers || "";    // Set readyState    jqXHR.readyState = status > 0 ? 4 : 0;    var isSuccess,        success,        error,        statusText = nativeStatusText,        response = responses ? ajaxHandleResponses( s, jqXHR, responses ) : undefined,        lastModified,        etag;    // 下面这部分代码主要是根据status状态,来判断是否请求成功。    //请求成功时,isSuccess标志就被设为true    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 = jqXHR.getResponseHeader( "Last-Modified" ) ) ) {                jQuery.lastModified[ ifModifiedKey ] = lastModified;            }            if ( ( etag = jqXHR.getResponseHeader( "Etag" ) ) ) {                jQuery.etag[ ifModifiedKey ] = 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 ( !statusText || status ) {            statusText = "error";            if ( status < 0 ) {                status = 0;            }        }    }    // Set data for the fake xhr object    jqXHR.status = status;    jqXHR.statusText = "" + ( nativeStatusText || statusText );    //由上面得到的请求成功与否的状态isSuccess    //请求成功,触发deferred.resolveWith(),使该延迟对象处于resolve状态,执行成功回调列表里的回调函数    //请求失败,触发deferred.rejectWith(),使该延迟对象处于reject状态,执行失败回调列表里的回调函数    if ( isSuccess ) {        deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] );    } else {        deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] );    }    //Status-dependent callbacks    jqXHR.statusCode( statusCode );    statusCode = undefined;    if ( fireGlobals ) {        //触发ajax全局事件,ajaxSuccess或者 ajaxError事件        globalEventContext.trigger( "ajax" + ( isSuccess ? "Success" : "Error" ),                [ jqXHR, s, isSuccess ? success : error ] );    }    //执行completeDeferred回调对象的fireWith()方法,执行回调列表中的回调函数    completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] );    if ( fireGlobals ) {        globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] );        // Handle the global AJAX counter        if ( !( --jQuery.active ) ) {            jQuery.event.trigger( "ajaxStop" );        }    }}

上面done函数主要是通过触发延迟对象的resolveWithrejectWith方法来执行我们添加的success: function(){ }error: function(){ }方法,并且通过触发completeDeferred回调对象的fireWith()方法来执行我们添加的complete: function(){ }方法,那么我们在参数中添加的方法是如何加到回调列表中去的?

jQuery中是这么做的:

for ( i in { success: 1, error: 1, complete: 1 } ) {    jqXHR[ i ]( s[ i ] );}

通过对{ success: 1, error: 1, complete: 1 }遍历,分别执行jqXHR.success, jqXHR.error, jqXHR.complete方法。这三个方法在前面也讲过了,分别对应到deferred对象和回调对象的done, failadd方法。所以这个操作就是将s.success, s.error, s.complete添加到对应回调列表中的。那么s.success, s.error, s.complete分别是什么呢?
其中

//option是我们传入到ajax中的配置选项对象s = jQuery.ajaxSetup( {}, options )//s就是 我们传的配置选项与jQuery源码中默认的配置的组合,里面有我们传入的url,没传url的话默认就是自身的href(这是在jQuery源码默认的配置选项中设置的); 还有我们传入的success回调函数, error回调函数以及complete函数。因此,上面说的s.success, s.error和s.complete其实就是我们传入的回调函数。

这样在done函数中,触发resolveWith或者rejectWith,就会执行对应列表中的回调了,也就是我们传的回调函数。complete也是同样的道理。

这样,就实现了ajax在请求的过程中,根据响应的成功与否,触发不同的回调列表中的函数,以此来实现异步请求的目的。这个过程很复杂,且比较绕,并且期间处理了很多很多情况,比如设置头部,处理跨域,这些都还没深入,只是从异步队列角度来分析,如果就实现了异步,旨在对异步队列应用有更深的理解。

原创粉丝点击