您的位置:首页 > Web前端 > JQuery

jQuery-AJAX模块解析-request部分

2015-05-12 14:34 435 查看
说好的讲解ajax模块来着的,讲好的事我从来不骗人~(虽然拖了比较久,不过这是迟来的爱

)

首先来看下传统的Ajax请求代码:

function doAjax(config){
var url = config.url,
complete = config.complete,
xhr = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject('Microsoft.XMLHTTP');
xhr.open('post', url);
xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.onreadystatechange = function() {
if (xhr.readyState == 4) {
if (xhr.status == 200) {
complete(xhr.responseText);
}
}
}
xhr.send(config.data || null);
return xhr;
}


Ajax请求的流程一般为:
1、使用new XMLHttpRequest()或new ActiveXObject()插件的方式创建xhr对象。

2、通过xhr.open(...)的方式建立连接。

3、使用xhr.setRequestHeader(..)设置请求头信息

4、监听onreadystatechange事件,注册回调函数处理数据。

5、xhr.send(...)发生请求。

上面就是ajax的简单封装了,不过这么简陋的代码投入到生产中一般会被人打死。可以优化的地方太多,比如:提前浏览器特性检测、良好的API接口,对不同业务需求进行Ajax请求缓存优化等等,再如我们实际开发中会遇到很多问题:
跨域
json的格式
dataType
AJAX乱码问题
页面缓存
状态的跟踪
不同平台兼容
相信任何一个经验丰富的开发者都有遇到过上面的问题,每一个问题都令人头疼,所以得感谢jQuery提供的Ajax模块(jQuery大法好~)。
看下jQuery的ajax请求的优雅写法:

$.post(url, params).then(function(){
console.log('done');
}, function(){
console.log('fail');
});
不错!jQuery的接口留的就是这么优雅。真想给jQuery点32个赞~

首先因为jQuery的模块写的比较复杂(1200多行呢。。。),所以本篇blog只讲解Ajax请求的部分,response部分留到下次讲。

以下仅代表本人拙见,有错误或疑问之处欢迎之处~

jQuery的ajax的request部分应该有:预过滤器(ajaxPrefilter)、请求分发器(ajaxTransport)、json和script类型处理、jsonp的实现、请求头信息的处理和jQuery.ajax这个主方法。

先看ajaxPrefilter和ajaxTransport的构造器:

//addToPrefiltersOrTransports作为jQuery.ajaxPrefilter和jQuery.ajaxTransport的构造器,他们的方法都是通过addToPrefiltersOrTransports来构建一个闭包生成的函数,用于维持传进来的structure
// Base "constructor" for jQuery.ajaxPrefilter and jQuery.ajaxTransport
function addToPrefiltersOrTransports( structure ) {

// dataTypeExpression is optional and defaults to "*"
return function( dataTypeExpression, func ) {

if ( typeof dataTypeExpression !== "string" ) {
func = dataTypeExpression;
dataTypeExpression = "*";
}

var dataType,
i = 0,
dataTypes = dataTypeExpression.toLowerCase().match( rnotwhite ) || [];

if ( jQuery.isFunction( func ) ) {
// For each dataType in the dataTypeExpression
while ( (dataType = dataTypes[i++]) ) {
// Prepend if requested
if ( dataType.charAt( 0 ) === "+" ) {
dataType = dataType.slice( 1 ) || "*";
(structure[ dataType ] = structure[ dataType ] || []).unshift( func );

// Otherwise append
} else {
//structure的dataType列表中push对应的过滤器处理函数来
(structure[ dataType ] = structure[ dataType ] || []).push( func );
}
}
}
};
}
ajaxPrefilter的API文档是这么写的:描述: 在每个请求之前被发送和
$.ajax()
处理它们前处理,设置自定义Ajax选项或修改现有选项。

ajaxTransport的API文档是这么写的:描述: 创建一个对象,用于处理Ajax数据的实际传输。

很明显可以看到这两个方法只是分别往对应的prefilters和transports的dataType中添加对应的处理函数func。

而他们进行探测的方法是inspectPrefiltersOrTransports:

//inspectPrefiltersOrTransports是一个prefilters和transports的探测器
// Base inspection function for prefilters and transports
function inspectPrefiltersOrTransports( structure, options, originalOptions, jqXHR ) {
//options为$.ajaxSettings
var inspected = {},
//是否探测transports
seekingTransport = ( structure === transports );

function inspect( dataType ) {
var selected;
//防止重复探测
inspected[ dataType ] = true;
//fire该structure[ dataType ]中的所有处理函数
jQuery.each( structure[ dataType ] || [], function( _, prefilterOrFactory ) {
var dataTypeOrTransport = prefilterOrFactory( options, originalOptions, jqXHR );
if ( typeof dataTypeOrTransport === "string" && !seekingTransport && !inspected[ dataTypeOrTransport ] ) {
options.dataTypes.unshift( dataTypeOrTransport );
//递归探测处理函数返回的dataType,防止后添加的处理函数没有执行
inspect( dataTypeOrTransport );
return false;
} else if ( seekingTransport ) {
//如果dataTypeOrTransport可转为true,则停止探测
return !( selected = dataTypeOrTransport );
}
});
return selected;
}

return inspect( options.dataTypes[ 0 ] ) || !inspected[ "*" ] && inspect( "*" );
}
ajaxPrefilter和ajaxTransport初始化给部分类型添加了对应的过滤器和分发器,如下:
// Handle cache's special case and global
//处理缓存选项和设置跨域请求方式、禁止fire全局events
jQuery.ajaxPrefilter( "script", function( s ) {
if ( s.cache === undefined ) {
s.cache = false;
}
if ( s.crossDomain ) {
s.type = "GET";
s.global = false;
}
});
// Detect, normalize options and install callbacks for jsonp requests
//像普通的方式处理json、jsonp格式的数据
jQuery.ajaxPrefilter( "json jsonp", function( s, originalSettings, jqXHR ) {

var callbackName, overwritten, responseContainer,
jsonProp = s.jsonp !== false && ( rjsonp.test( s.url ) ?
"url" :
typeof s.data === "string" && !( s.contentType || "" ).indexOf("application/x-www-form-urlencoded") && rjsonp.test( s.data ) && "data"
);

// Handle iff the expected data type is "jsonp" or we have a parameter to set
if ( jsonProp || s.dataTypes[ 0 ] === "jsonp" ) {

//略...

// Delegate to script
return "script";
}
});

jQuery.ajaxTransport(function( options ) {
// Cross domain only allowed if supported through XMLHttpRequest
//返回一个处理非跨域的transport对象
if ( !options.crossDomain || support.cors ) {

var callback;

return {
send: function( headers, complete ) {
//略...
},

abort: function() {
if ( callback ) {
callback( undefined, true );
}
}
};
}
});

// Bind script tag hack transport
jQuery.ajaxTransport( "script", function(s) {

// This transport only deals with cross domain requests
//返回一个处理跨域的transport对象
if ( s.crossDomain ) {

var script,
head = document.head || jQuery("head")[0] || document.documentElement;

return {

send: function( _, callback ) {

//略...
},

abort: function() {
if ( script ) {
script.onload( undefined, true );
}
}
};
}
});


jQuery
c537
提供了一系列的预过滤器和分发器来处理常见dataType(如:script、json、jsonp)和跨域问题,在ajax发起请求之前做过滤处理,然后获取正确的transport对象来进行分发请求。

以上就是ajax的预过滤器和请求分发器了,jQuery通过这种预处理的方式将不同dataType的请求都提供了统一的处理接口,通过inspect transports获得正确的transport对象,然后使用统一的send接口发送请求,使用hack的方式处理了跨域请求,对内部$.ajax主方法完全解耦分离,还顺带给用户提供了过滤器和分发器功能。真是高明。

ajax的头信息就简单的说下好了,jqXHR(fake xhr)提供了一系列的方法来设置、获取头信息,另外在ajaxSettings中提供了cache选项来设置是否从缓存中获取数据。

而ajax主方法自从jQuery1.5之后就使用了Deferred延迟对象重写了:

ajax: function( url, options ) {

// If url is an object, simulate pre-1.5 signature
if ( typeof url === "object" ) {
options = url;
url = undefined;
}

// Force options to be an object
options = options || {};

var //一大堆变量我给删了
s = jQuery.ajaxSetup( {}, options ),
// Callbacks context
callbackContext = s.context || s,
// Context for global events is callbackContext if it is a DOM node or jQuery collection
globalEventContext = s.context && ( callbackContext.nodeType || callbackContext.jquery ) ?
jQuery( callbackContext ) :
jQuery.event,
// Deferreds,主延迟对象,将jqXHR做成一个promise对象
deferred = jQuery.Deferred(),
completeDeferred = jQuery.Callbacks("once memory"),
// Fake xhr
jqXHR = {
//一大堆方法我给删了
};

// Attach deferreds
//给jqXHR提供方便的API接口给用户
deferred.promise( jqXHR ).complete = completeDeferred.add;
jqXHR.success = jqXHR.done;
jqXHR.error = jqXHR.fail;

// Remove hash character (#7531: and string promotion)
// Add protocol if not provided (#5866: IE7 issue with protocol-less urls)
// Handle falsy url in the settings object (#10093: consistency with old signature)
// We also use the url parameter if available
s.url = ( ( url || s.url || ajaxLocation ) + "" ).replace( rhash, "" ).replace( rprotocol, ajaxLocParts[ 1 ] + "//" );

// Alias method option to type as per ticket #12004
s.type = options.method || options.type || s.method || s.type;

// Extract dataTypes list
s.dataTypes = jQuery.trim( s.dataType || "*" ).toLowerCase().match( rnotwhite ) || [ "" ];

// A cross-domain request is in order when we have a protocol:host:port mismatch
if ( s.crossDomain == null ) {
parts = rurl.exec( s.url.toLowerCase() );
s.crossDomain = !!( parts &&
( parts[ 1 ] !== ajaxLocParts[ 1 ] || parts[ 2 ] !== ajaxLocParts[ 2 ] ||
( parts[ 3 ] || ( parts[ 1 ] === "http:" ? "80" : "443" ) ) !==
( ajaxLocParts[ 3 ] || ( ajaxLocParts[ 1 ] === "http:" ? "80" : "443" ) ) )
);
}

// Convert data if not already a string
if ( s.data && s.processData && typeof s.data !== "string" ) {
s.data = jQuery.param( s.data, s.traditional );
}
//进行ajax过滤处理
// Apply prefilters
inspectPrefiltersOrTransports( prefilters, s, options, jqXHR );

// If request was aborted inside a prefilter, stop there
if ( state === 2 ) {
return jqXHR;
}

// We can fire global events as of now if asked to
// Don't fire events if jQuery.event is undefined in an AMD-usage scenario (#15118)
//是否触发全局事件
fireGlobals = jQuery.event && s.global;

// Watch for a new set of requests
//一组ajax请求时,在第一个ajax发起时触发ajaxStart事件
if ( fireGlobals && jQuery.active++ === 0 ) {
jQuery.event.trigger("ajaxStart");
}

// Uppercase the type
s.type = s.type.toUpperCase();

//设置头信息和判断缓存的也给我删了

// Allow custom headers/mimetypes and early abort
if ( s.beforeSend && ( s.beforeSend.call( callbackContext, jqXHR, s ) === false || state === 2 ) ) {
// Abort if not done already and return
return jqXHR.abort();
}

// aborting is no longer a cancellation
strAbort = "abort";

// Install callbacks on deferreds
//添加ajaxSettings中配置的对应函数
for ( i in { success: 1, error: 1, complete: 1 } ) {
jqXHR[ i ]( s[ i ] );
}

// Get transport
//获得transport对象
transport = inspectPrefiltersOrTransports( transports, s, options, jqXHR );

// If no transport, we auto-abort
if ( !transport ) {
done( -1, "No Transport" );
} else {
jqXHR.readyState = 1;

// Send global event
//触发ajaxSend事件
if ( fireGlobals ) {
globalEventContext.trigger( "ajaxSend", [ jqXHR, s ] );
}
// Timeout
if ( s.async && s.timeout > 0 ) {
timeoutTimer = setTimeout(function() {
jqXHR.abort("timeout");
}, s.timeout );
}

try {
state = 1;
//done方法作为请求完成的complete函数调用
transport.send( requestHeaders, done );
} catch ( e ) {
// Propagate exception as error if not done
if ( state < 2 ) {
done( -1, e );
// Simply rethrow otherwise
} else {
throw e;
}
}
}

// Callback for when everything is done
function done( status, nativeStatusText, responses, headers ) {
var isSuccess, success, error, response, modified,
statusText = nativeStatusText;

//response的处理操作又给我删了
// Success/Error
//fire对应的回调列表
if ( isSuccess ) {
deferred.resolveWith( callbackContext, [ success, statusText, jqXHR ] );
} else {
deferred.rejectWith( callbackContext, [ jqXHR, statusText, error ] );
}

// Status-dependent callbacks
jqXHR.statusCode( statusCode );
statusCode = undefined;

//触发ajaxSuccess或者ajaxError事件
if ( fireGlobals ) {
globalEventContext.trigger( isSuccess ? "ajaxSuccess" : "ajaxError",
[ jqXHR, s, isSuccess ? success : error ] );
}

// Complete
completeDeferred.fireWith( callbackContext, [ jqXHR, statusText ] );

//触发ajaxComplete事件
if ( fireGlobals ) {
globalEventContext.trigger( "ajaxComplete", [ jqXHR, s ] );
// Handle the global AJAX counter
if ( !( --jQuery.active ) ) {
jQuery.event.trigger("ajaxStop");
}
}
}
//返回jqXHR对象,该对象是一个Promise对象
return jqXHR;
}
首先给jqXHR添加对应的接口,设置头信息和进行过滤操作,然后获取正确的transport对象,使用transport的send方法发起请求,,最后返回jqXHR。在done方法中处理response,最后延迟对象触发成功或失败的函数列表。其中在对应的时机触发对应的全局事件。

以上就是jQuery的ajax方法比较完整的流程了。

ajax的response部分和其他的零散部分留到下次再讲。

允许转载,请带上出处http://blog.csdn.net/z905951024 by denied.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息